Merge "[Audiosharing] Fix hysteresis mode" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index ad84900..6393fdb 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -72,6 +72,7 @@
         "android.service.dreams.flags-aconfig-java",
         "android.service.notification.flags-aconfig-java",
         "android.service.appprediction.flags-aconfig-java",
+        "android.service.quickaccesswallet.flags-aconfig-java",
         "android.service.voice.flags-aconfig-java",
         "android.speech.flags-aconfig-java",
         "android.systemserver.flags-aconfig-java",
@@ -111,6 +112,7 @@
         "framework_graphics_flags_java_lib",
         "hwui_flags_java_lib",
         "interaction_jank_monitor_flags_lib",
+        "keystore2_flags_java-framework",
         "libcore_exported_aconfig_flags_lib",
         "libcore_readonly_aconfig_flags_lib",
         "libgui_flags_java_lib",
@@ -1774,3 +1776,18 @@
     aconfig_declarations: "aconfig_settingslib_flags",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// Quick Access Wallet
+aconfig_declarations {
+    name: "android.service.quickaccesswallet.flags-aconfig",
+    package: "android.service.quickaccesswallet",
+    exportable: true,
+    container: "system",
+    srcs: ["core/java/android/service/quickaccesswallet/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.service.quickaccesswallet.flags-aconfig-java",
+    aconfig_declarations: "android.service.quickaccesswallet.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 14e2387..b43905b 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -451,7 +451,7 @@
                     auto token = SurfaceComposerClient::getPhysicalDisplayToken(
                         event.header.displayId);
 
-                    auto firstDisplay = mBootAnimation->mDisplays.front();
+                    auto& firstDisplay = mBootAnimation->mDisplays.front();
                     if (token != firstDisplay.displayToken) {
                         // ignore hotplug of a secondary display
                         continue;
diff --git a/core/api/current.txt b/core/api/current.txt
index ea6d1a5..dd0c1d0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8075,8 +8075,10 @@
     method @NonNull @WorkerThread public android.os.Bundle getApplicationRestrictions(@Nullable android.content.ComponentName, String);
     method @Deprecated @Nullable public String getApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName);
     method @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public boolean getAutoTimeEnabled(@Nullable android.content.ComponentName);
+    method @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public int getAutoTimePolicy();
     method @Deprecated public boolean getAutoTimeRequired();
     method @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME_ZONE, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public boolean getAutoTimeZoneEnabled(@Nullable android.content.ComponentName);
+    method @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME_ZONE, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public int getAutoTimeZonePolicy();
     method @NonNull public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(@NonNull android.content.ComponentName);
     method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName);
     method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA, conditional=true) public boolean getCameraDisabled(@Nullable android.content.ComponentName);
@@ -8232,8 +8234,10 @@
     method @WorkerThread public void setApplicationRestrictions(@Nullable android.content.ComponentName, String, android.os.Bundle);
     method @Deprecated public void setApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName, @Nullable String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(value=android.Manifest.permission.SET_TIME, conditional=true) public void setAutoTimeEnabled(@Nullable android.content.ComponentName, boolean);
+    method @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") @RequiresPermission(value=android.Manifest.permission.SET_TIME, conditional=true) public void setAutoTimePolicy(int);
     method @Deprecated public void setAutoTimeRequired(@NonNull android.content.ComponentName, boolean);
     method @RequiresPermission(value=android.Manifest.permission.SET_TIME_ZONE, conditional=true) public void setAutoTimeZoneEnabled(@Nullable android.content.ComponentName, boolean);
+    method @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") @RequiresPermission(value=android.Manifest.permission.SET_TIME_ZONE, conditional=true) public void setAutoTimeZonePolicy(int);
     method public void setBackupServiceEnabled(@NonNull android.content.ComponentName, boolean);
     method public void setBluetoothContactSharingDisabled(@NonNull android.content.ComponentName, boolean);
     method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA, conditional=true) public void setCameraDisabled(@Nullable android.content.ComponentName, boolean);
@@ -8352,6 +8356,12 @@
     field public static final String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
     field public static final String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
     field public static final String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
+    field @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") public static final int AUTO_TIME_DISABLED = 1; // 0x1
+    field @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") public static final int AUTO_TIME_ENABLED = 2; // 0x2
+    field @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") public static final int AUTO_TIME_NOT_CONTROLLED_BY_POLICY = 0; // 0x0
+    field @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") public static final int AUTO_TIME_ZONE_DISABLED = 1; // 0x1
+    field @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") public static final int AUTO_TIME_ZONE_ENABLED = 2; // 0x2
+    field @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") public static final int AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY = 0; // 0x0
     field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int CONTENT_PROTECTION_DISABLED = 1; // 0x1
     field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int CONTENT_PROTECTION_ENABLED = 2; // 0x2
     field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY = 0; // 0x0
@@ -37819,6 +37829,7 @@
     field public static final String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
     field @Deprecated public static final String ACTION_FINGERPRINT_ENROLL = "android.settings.FINGERPRINT_ENROLL";
+    field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_FIRST_DAY_OF_WEEK_SETTINGS = "android.settings.FIRST_DAY_OF_WEEK_SETTINGS";
     field public static final String ACTION_HARD_KEYBOARD_SETTINGS = "android.settings.HARD_KEYBOARD_SETTINGS";
     field public static final String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
     field public static final String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
@@ -37839,6 +37850,7 @@
     field public static final String ACTION_MANAGE_SUPERVISOR_RESTRICTED_SETTING = "android.settings.MANAGE_SUPERVISOR_RESTRICTED_SETTING";
     field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES";
     field public static final String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS";
+    field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_MEASUREMENT_SYSTEM_SETTINGS = "android.settings.MEASUREMENT_SYSTEM_SETTINGS";
     field public static final String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS";
     field public static final String ACTION_NETWORK_OPERATOR_SETTINGS = "android.settings.NETWORK_OPERATOR_SETTINGS";
     field public static final String ACTION_NFCSHARING_SETTINGS = "android.settings.NFCSHARING_SETTINGS";
@@ -37849,12 +37861,14 @@
     field public static final String ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS = "android.settings.NOTIFICATION_LISTENER_DETAIL_SETTINGS";
     field public static final String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";
     field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_SETTINGS";
+    field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_NUMBERING_SYSTEM_SETTINGS = "android.settings.NUMBERING_SYSTEM_SETTINGS";
     field public static final String ACTION_PRINT_SETTINGS = "android.settings.ACTION_PRINT_SETTINGS";
     field public static final String ACTION_PRIVACY_SETTINGS = "android.settings.PRIVACY_SETTINGS";
     field public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_URI = "android.settings.PROCESS_WIFI_EASY_CONNECT_URI";
     field public static final String ACTION_QUICK_ACCESS_WALLET_SETTINGS = "android.settings.QUICK_ACCESS_WALLET_SETTINGS";
     field public static final String ACTION_QUICK_LAUNCH_SETTINGS = "android.settings.QUICK_LAUNCH_SETTINGS";
     field public static final String ACTION_REGIONAL_PREFERENCES_SETTINGS = "android.settings.REGIONAL_PREFERENCES_SETTINGS";
+    field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_REGION_SETTINGS = "android.settings.REGION_SETTINGS";
     field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
     field public static final String ACTION_REQUEST_MANAGE_MEDIA = "android.settings.REQUEST_MANAGE_MEDIA";
     field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = "android.settings.REQUEST_MEDIA_ROUTING_CONTROL";
@@ -37870,6 +37884,7 @@
     field public static final String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS";
     field @Deprecated public static final String ACTION_STORAGE_VOLUME_ACCESS_SETTINGS = "android.settings.STORAGE_VOLUME_ACCESS_SETTINGS";
     field public static final String ACTION_SYNC_SETTINGS = "android.settings.SYNC_SETTINGS";
+    field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_TEMPERATURE_UNIT_SETTINGS = "android.settings.TEMPERATURE_UNIT_SETTINGS";
     field public static final String ACTION_USAGE_ACCESS_SETTINGS = "android.settings.USAGE_ACCESS_SETTINGS";
     field public static final String ACTION_USER_DICTIONARY_SETTINGS = "android.settings.USER_DICTIONARY_SETTINGS";
     field public static final String ACTION_VOICE_CONTROL_AIRPLANE_MODE = "android.settings.VOICE_CONTROL_AIRPLANE_MODE";
@@ -40697,7 +40712,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.FillRequest> CREATOR;
     field public static final int FLAG_COMPATIBILITY_MODE_REQUEST = 2; // 0x2
     field public static final int FLAG_MANUAL_REQUEST = 1; // 0x1
-    field public static final int FLAG_SUPPORTS_FILL_DIALOG = 64; // 0x40
+    field @Deprecated @FlaggedApi("android.service.autofill.fill_dialog_improvements") public static final int FLAG_SUPPORTS_FILL_DIALOG = 64; // 0x40
   }
 
   public final class FillResponse implements android.os.Parcelable {
@@ -51391,6 +51406,7 @@
     method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl);
     method public default int getBufferTransformHint();
     method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.window.InputTransferToken getInputTransferToken();
+    method @FlaggedApi("com.android.window.flags.jank_api") @NonNull public default android.view.SurfaceControl.OnJankDataListenerRegistration registerOnJankDataListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.SurfaceControl.OnJankDataListener);
     method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
     method public default void setChildBoundingInsets(@NonNull android.graphics.Rect);
     method public default void setTouchableRegion(@Nullable android.graphics.Region);
@@ -51648,6 +51664,7 @@
     field public static final int DEADLINE = 13; // 0xd
     field public static final int DRAW_DURATION = 4; // 0x4
     field public static final int FIRST_DRAW_FRAME = 9; // 0x9
+    field @FlaggedApi("com.android.window.flags.jank_api") public static final int FRAME_TIMELINE_VSYNC_ID = 14; // 0xe
     field public static final int GPU_DURATION = 12; // 0xc
     field public static final int INPUT_HANDLING_DURATION = 1; // 0x1
     field public static final int INTENDED_VSYNC_TIMESTAMP = 10; // 0xa
@@ -53085,6 +53102,26 @@
     method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl);
   }
 
+  @FlaggedApi("com.android.window.flags.jank_api") public static class SurfaceControl.JankData {
+    method public long getActualAppFrameTimeNanos();
+    method public int getJankType();
+    method public long getScheduledAppFrameTimeNanos();
+    method public long getVsyncId();
+    field public static final int JANK_APPLICATION = 2; // 0x2
+    field public static final int JANK_COMPOSER = 1; // 0x1
+    field public static final int JANK_NONE = 0; // 0x0
+    field public static final int JANK_OTHER = 4; // 0x4
+  }
+
+  @FlaggedApi("com.android.window.flags.jank_api") public static interface SurfaceControl.OnJankDataListener {
+    method public void onJankDataAvailable(@NonNull java.util.List<android.view.SurfaceControl.JankData>);
+  }
+
+  @FlaggedApi("com.android.window.flags.jank_api") public static class SurfaceControl.OnJankDataListenerRegistration {
+    method public void flush();
+    method public void removeAfter(long);
+  }
+
   public static class SurfaceControl.Transaction implements java.io.Closeable android.os.Parcelable {
     ctor public SurfaceControl.Transaction();
     method @NonNull public android.view.SurfaceControl.Transaction addTransactionCommittedListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.SurfaceControl.TransactionCommittedListener);
@@ -54043,6 +54080,7 @@
     field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_GLOBAL_SAME_APPLICATION = 4096; // 0x1000
     field public static final int DRAG_FLAG_GLOBAL_URI_READ = 1; // 0x1
     field public static final int DRAG_FLAG_GLOBAL_URI_WRITE = 2; // 0x2
+    field @FlaggedApi("com.android.window.flags.supports_drag_assistant_to_multiwindow") public static final int DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START = 16384; // 0x4000
     field public static final int DRAG_FLAG_OPAQUE = 512; // 0x200
     field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG = 8192; // 0x2000
     field @Deprecated public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
@@ -56537,13 +56575,13 @@
     method public void notifyViewExited(@NonNull android.view.View, int);
     method public void notifyViewVisibilityChanged(@NonNull android.view.View, boolean);
     method public void notifyViewVisibilityChanged(@NonNull android.view.View, int, boolean);
-    method public void notifyVirtualViewsReady(@NonNull android.view.View, @NonNull android.util.SparseArray<android.view.autofill.VirtualViewFillInfo>);
+    method @Deprecated @FlaggedApi("android.service.autofill.fill_dialog_improvements") public void notifyVirtualViewsReady(@NonNull android.view.View, @NonNull android.util.SparseArray<android.view.autofill.VirtualViewFillInfo>);
     method public void registerCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
     method public void requestAutofill(@NonNull android.view.View);
     method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect);
     method public void setUserData(@Nullable android.service.autofill.UserData);
-    method public boolean showAutofillDialog(@NonNull android.view.View);
-    method public boolean showAutofillDialog(@NonNull android.view.View, int);
+    method @Deprecated @FlaggedApi("android.service.autofill.fill_dialog_improvements") public boolean showAutofillDialog(@NonNull android.view.View);
+    method @Deprecated @FlaggedApi("android.service.autofill.fill_dialog_improvements") public boolean showAutofillDialog(@NonNull android.view.View, int);
     method public void unregisterCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
     field public static final String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE";
     field public static final String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT";
@@ -56896,6 +56934,7 @@
     ctor public EditorInfo();
     method public int describeContents();
     method public void dump(android.util.Printer, String);
+    method @FlaggedApi("android.view.inputmethod.public_autofill_id_in_editorinfo") @Nullable public android.view.autofill.AutofillId getAutofillId();
     method @Nullable public CharSequence getInitialSelectedText(int);
     method @Nullable public android.view.inputmethod.SurroundingText getInitialSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int);
     method @Nullable public CharSequence getInitialTextAfterCursor(@IntRange(from=0) int, int);
@@ -56905,6 +56944,7 @@
     method @NonNull public java.util.List<java.lang.Class<? extends android.view.inputmethod.HandwritingGesture>> getSupportedHandwritingGestures();
     method @FlaggedApi("android.view.inputmethod.editorinfo_handwriting_enabled") public boolean isStylusHandwritingEnabled();
     method public final void makeCompatible(int);
+    method @FlaggedApi("android.view.inputmethod.public_autofill_id_in_editorinfo") public void setAutofillId(@Nullable android.view.autofill.AutofillId);
     method public void setInitialSurroundingSubText(@NonNull CharSequence, int);
     method public void setInitialSurroundingText(@NonNull CharSequence);
     method public void setInitialToolType(int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0e521c6..ed95fdd 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -709,8 +709,8 @@
     field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images";
     field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video";
     field public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED = "android:read_media_visual_user_selected";
-    field @FlaggedApi("android.permission.flags.platform_oxygen_saturation_enabled") public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation";
-    field @FlaggedApi("android.permission.flags.platform_skin_temperature_enabled") public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature";
+    field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation";
+    field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature";
     field public static final String OPSTR_READ_WRITE_HEALTH_DATA = "android:read_write_health_data";
     field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
     field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
@@ -1368,6 +1368,7 @@
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean packageHasActiveAdmins(String);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, android.Manifest.permission.PROVISION_DEMO_DEVICE}) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
+    method @FlaggedApi("android.app.admin.flags.remove_managed_profile_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean removeManagedProfile();
     method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -1377,7 +1378,8 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int);
     method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName);
-    method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
+    method @Deprecated @FlaggedApi("android.app.admin.flags.secondary_lockscreen_api_enabled") public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
+    method @FlaggedApi("android.app.admin.flags.secondary_lockscreen_api_enabled") public void setSecondaryLockscreenEnabled(boolean, @Nullable android.os.PersistableBundle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setUserProvisioningState(int, @NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public boolean shouldAllowBypassingDevicePolicyManagementRoleQualification();
     field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
@@ -8245,6 +8247,26 @@
     method public void onSetMain(boolean);
   }
 
+  @FlaggedApi("android.media.tv.flags.tif_extension_standardization") public final class TvInputServiceExtensionManager {
+    method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public int registerExtensionIBinder(@NonNull String, @NonNull android.os.IBinder);
+    field public static final String IBROADCAST_TIME = "android.media.tv.extension.time.BroadcastTime";
+    field public static final String ICAM_APP_INFO_SERVICE = "android.media.tv.extension.cam.ICamAppInfoService";
+    field public static final String ICLIENT_TOKEN = "android.media.tv.extension.clienttoken.IClientToken";
+    field public static final String IDATA_SERVICE_SIGNAL_INFO = "android.media.tv.extension.teletext.IDataServiceSignalInfo";
+    field public static final String IEVENT_MONITOR = "android.media.tv.extension.event.IEventMonitor";
+    field public static final String IHDMI_SIGNAL_INTERFACE = "android.media.tv.extension.signal.IHdmiSignalInterface";
+    field public static final String IOAD_UPDATE_INTERFACE = "android.media.tv.extension.oad.IOadUpdateInterface";
+    field public static final String IRATING_INTERFACE = "android.media.tv.extension.rating.IRatingInterface";
+    field public static final String IRECORDED_CONTENTS = "android.media.tv.extension.pvr.IRecordedContents";
+    field public static final String ISCAN_INTERFACE = "android.media.tv.extension.scan.IScanInterface";
+    field public static final String ISCREEN_MODE_SETTINGS = "android.media.tv.extension.screenmode.IScreenModeSettings";
+    field public static final String ISERVICE_LIST_EDIT_LISTENER = "android.media.tv.extension.servicedb.IServiceListEditListener";
+    field public static final int REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED = 2; // 0x2
+    field public static final int REGISTER_FAIL_NAME_NOT_STANDARDIZED = 1; // 0x1
+    field public static final int REGISTER_FAIL_REMOTE_EXCEPTION = 3; // 0x3
+    field public static final int REGISTER_SUCCESS = 0; // 0x0
+  }
+
   public abstract static class TvRecordingClient.RecordingCallback {
     method public void onEvent(String, String, android.os.Bundle);
   }
@@ -12531,8 +12553,19 @@
   }
 
   @FlaggedApi("android.security.aapm_api") public final class AdvancedProtectionManager {
+    method @NonNull public android.content.Intent createSupportIntent(@NonNull String, @Nullable String);
     method @NonNull @RequiresPermission(android.Manifest.permission.SET_ADVANCED_PROTECTION_MODE) public java.util.List<android.security.advancedprotection.AdvancedProtectionFeature> getAdvancedProtectionFeatures();
     method @RequiresPermission(android.Manifest.permission.SET_ADVANCED_PROTECTION_MODE) public void setAdvancedProtectionEnabled(boolean);
+    field @FlaggedApi("android.security.aapm_api") public static final String ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG = "android.security.advancedprotection.action.SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG";
+    field public static final String EXTRA_SUPPORT_DIALOG_FEATURE = "android.security.advancedprotection.extra.SUPPORT_DIALOG_FEATURE";
+    field public static final String EXTRA_SUPPORT_DIALOG_TYPE = "android.security.advancedprotection.extra.SUPPORT_DIALOG_TYPE";
+    field public static final String FEATURE_ID_DISALLOW_CELLULAR_2G = "android.security.advancedprotection.feature_disallow_2g";
+    field public static final String FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES = "android.security.advancedprotection.feature_disallow_install_unknown_sources";
+    field public static final String FEATURE_ID_DISALLOW_USB = "android.security.advancedprotection.feature_disallow_usb";
+    field public static final String FEATURE_ID_DISALLOW_WEP = "android.security.advancedprotection.feature_disallow_wep";
+    field public static final String FEATURE_ID_ENABLE_MTE = "android.security.advancedprotection.feature_enable_mte";
+    field public static final String SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION = "android.security.advancedprotection.type_blocked_interaction";
+    field public static final String SUPPORT_DIALOG_TYPE_DISABLED_SETTING = "android.security.advancedprotection.type_disabled_setting";
   }
 
 }
@@ -18794,6 +18827,10 @@
     method @Deprecated public void openFileChooser(android.webkit.ValueCallback<android.net.Uri>, String, String);
   }
 
+  public abstract static class WebChromeClient.FileChooserParams {
+    field @FlaggedApi("android.webkit.file_system_access") public static final long ENABLE_FILE_SYSTEM_ACCESS = 364980165L; // 0x15c127c5L
+  }
+
   public abstract class WebHistoryItem implements java.lang.Cloneable {
     method @Deprecated public abstract int getId();
   }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c8ecfa9..1192713 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2065,6 +2065,29 @@
     method public boolean isAidlHal();
   }
 
+  public final class MediaCodec {
+    method @FlaggedApi("android.media.codec.codec_availability") @NonNull public static java.util.List<android.media.MediaCodec.GlobalResourceInfo> getGloballyAvailableResources();
+    method @FlaggedApi("android.media.codec.codec_availability") @NonNull public java.util.List<android.media.MediaCodec.InstanceResourceInfo> getRequiredResources();
+  }
+
+  public abstract static class MediaCodec.Callback {
+    method @FlaggedApi("android.media.codec.codec_availability") public void onRequiredResourcesChanged(@NonNull android.media.MediaCodec);
+  }
+
+  @FlaggedApi("android.media.codec.codec_availability") public static final class MediaCodec.GlobalResourceInfo {
+    ctor public MediaCodec.GlobalResourceInfo();
+    method public long getAvailable();
+    method public long getCapacity();
+    method @NonNull public String getName();
+  }
+
+  @FlaggedApi("android.media.codec.codec_availability") public static final class MediaCodec.InstanceResourceInfo {
+    ctor public MediaCodec.InstanceResourceInfo();
+    method @NonNull public String getName();
+    method public long getPerFrameCount();
+    method public long getStaticCount();
+  }
+
   public static final class MediaCodecInfo.VideoCapabilities.PerformancePoint {
     ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(int, int, int, int, @NonNull android.util.Size);
     ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(@NonNull android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint, @NonNull android.util.Size);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b447897..ab75069 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1017,6 +1017,12 @@
     public static final int PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL = 1 << 6;
 
     /**
+     * @hide
+     * Process is guaranteed cpu time (IE. it will not be frozen).
+     */
+    public static final int PROCESS_CAPABILITY_CPU_TIME = 1 << 7;
+
+    /**
      * @hide all capabilities, the ORing of all flags in {@link ProcessCapability}.
      *
      * Don't expose it as TestApi -- we may add new capabilities any time, which could
@@ -1028,7 +1034,8 @@
             | PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK
             | PROCESS_CAPABILITY_BFSL
             | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK
-            | PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL;
+            | PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL
+            | PROCESS_CAPABILITY_CPU_TIME;
 
     /**
      * All implicit capabilities. This capability set is currently only used for processes under
@@ -1053,6 +1060,7 @@
         pw.print((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-');
         pw.print((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-');
         pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL) != 0 ? 'A' : '-');
+        pw.print((caps & PROCESS_CAPABILITY_CPU_TIME) != 0 ? 'T' : '-');
     }
 
     /** @hide */
@@ -1065,6 +1073,7 @@
         sb.append((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-');
         sb.append((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-');
         sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL) != 0 ? 'A' : '-');
+        sb.append((caps & PROCESS_CAPABILITY_CPU_TIME) != 0 ? 'T' : '-');
     }
 
     /**
@@ -5291,7 +5300,6 @@
         if (!exported) {
             /*
             RuntimeException here = new RuntimeException("here");
-            here.fillInStackTrace();
             Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
                     here);
             */
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 60b8f80..cb7b115 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1961,12 +1961,8 @@
 
         @Override
         public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) {
-            try {
-                PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
-                BroadcastStickyCache.dump(pfd);
-            } finally {
-                IoUtils.closeQuietly(pfd);
-            }
+            PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
+            IoUtils.closeQuietly(pfd);
         }
 
         private File getDatabasesDir(Context context) {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index fd70f4f..8b37dbd 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2527,12 +2527,12 @@
 
     /** @hide Access to read oxygen saturation. */
     @SystemApi
-    @FlaggedApi(Flags.FLAG_PLATFORM_OXYGEN_SATURATION_ENABLED)
+    @FlaggedApi(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
     public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation";
 
     /** @hide Access to read skin temperature. */
     @SystemApi
-    @FlaggedApi(Flags.FLAG_PLATFORM_SKIN_TEMPERATURE_ENABLED)
+    @FlaggedApi(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
     public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature";
 
     /** @hide Access to ranging */
@@ -2616,8 +2616,8 @@
             OP_POST_NOTIFICATION,
             // Health
             Flags.replaceBodySensorPermissionEnabled() ? OP_READ_HEART_RATE : OP_NONE,
-            Flags.platformSkinTemperatureEnabled() ? OP_READ_SKIN_TEMPERATURE : OP_NONE,
-            Flags.platformOxygenSaturationEnabled() ? OP_READ_OXYGEN_SATURATION : OP_NONE,
+            Flags.replaceBodySensorPermissionEnabled() ? OP_READ_SKIN_TEMPERATURE : OP_NONE,
+            Flags.replaceBodySensorPermissionEnabled() ? OP_READ_OXYGEN_SATURATION : OP_NONE,
     };
 
     /**
@@ -3132,7 +3132,7 @@
             .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
         new AppOpInfo.Builder(OP_READ_SKIN_TEMPERATURE, OPSTR_READ_SKIN_TEMPERATURE,
             "READ_SKIN_TEMPERATURE").setPermission(
-                Flags.platformSkinTemperatureEnabled()
+                Flags.replaceBodySensorPermissionEnabled()
                     ? HealthPermissions.READ_SKIN_TEMPERATURE : null)
             .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
         new AppOpInfo.Builder(OP_RANGING, OPSTR_RANGING, "RANGING")
@@ -3141,7 +3141,7 @@
             .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
         new AppOpInfo.Builder(OP_READ_OXYGEN_SATURATION, OPSTR_READ_OXYGEN_SATURATION,
             "READ_OXYGEN_SATURATION").setPermission(
-                Flags.platformOxygenSaturationEnabled()
+                Flags.replaceBodySensorPermissionEnabled()
                     ? HealthPermissions.READ_OXYGEN_SATURATION : null)
             .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
     };
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index fb5a12b..e2479169 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1835,7 +1835,6 @@
 
                 if (false) {
                     RuntimeException e = new RuntimeException("here");
-                    e.fillInStackTrace();
                     Log.w(TAG, "Getting drawable 0x" + Integer.toHexString(resId)
                                     + " from package " + packageName
                                     + ": app scale=" + r.getCompatibilityInfo().applicationScale
diff --git a/core/java/android/app/BroadcastStickyCache.java b/core/java/android/app/BroadcastStickyCache.java
deleted file mode 100644
index ea81731..0000000
--- a/core/java/android/app/BroadcastStickyCache.java
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.app;
-
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.hardware.usb.UsbManager;
-import android.media.AudioManager;
-import android.net.ConnectivityManager;
-import android.net.TetheringManager;
-import android.net.nsd.NsdManager;
-import android.net.wifi.WifiManager;
-import android.net.wifi.p2p.WifiP2pManager;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemProperties;
-import android.os.UpdateLock;
-import android.telephony.TelephonyManager;
-import android.util.ArrayMap;
-import android.util.IndentingPrintWriter;
-import android.view.WindowManagerPolicyConstants;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FastPrintWriter;
-
-import java.io.FileOutputStream;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/** @hide */
-public class BroadcastStickyCache {
-
-    private static final String[] CACHED_BROADCAST_ACTIONS = {
-            AudioManager.ACTION_HDMI_AUDIO_PLUG,
-            AudioManager.ACTION_HEADSET_PLUG,
-            AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED,
-            AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED,
-            AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
-            AudioManager.RINGER_MODE_CHANGED_ACTION,
-            ConnectivityManager.CONNECTIVITY_ACTION,
-            Intent.ACTION_BATTERY_CHANGED,
-            Intent.ACTION_DEVICE_STORAGE_FULL,
-            Intent.ACTION_DEVICE_STORAGE_LOW,
-            Intent.ACTION_SIM_STATE_CHANGED,
-            NsdManager.ACTION_NSD_STATE_CHANGED,
-            TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED,
-            TetheringManager.ACTION_TETHER_STATE_CHANGED,
-            UpdateLock.UPDATE_LOCK_CHANGED,
-            UsbManager.ACTION_USB_STATE,
-            WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED,
-            WifiManager.NETWORK_STATE_CHANGED_ACTION,
-            WifiManager.SUPPLICANT_STATE_CHANGED_ACTION,
-            WifiManager.WIFI_STATE_CHANGED_ACTION,
-            WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
-            WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED,
-            "android.net.conn.INET_CONDITION_ACTION" // ConnectivityManager.INET_CONDITION_ACTION
-    };
-
-    @GuardedBy("sCachedStickyBroadcasts")
-    private static final ArrayList<CachedStickyBroadcast> sCachedStickyBroadcasts =
-            new ArrayList<>();
-
-    @GuardedBy("sCachedPropertyHandles")
-    private static final ArrayMap<String, SystemProperties.Handle> sCachedPropertyHandles =
-            new ArrayMap<>();
-
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public static boolean useCache(@Nullable IntentFilter filter) {
-        if (!shouldCache(filter)) {
-            return false;
-        }
-        synchronized (sCachedStickyBroadcasts) {
-            final CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter);
-            if (cachedStickyBroadcast == null) {
-                return false;
-            }
-            final long version = cachedStickyBroadcast.propertyHandle.getLong(-1 /* def */);
-            return version > 0 && cachedStickyBroadcast.version == version;
-        }
-    }
-
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public static void add(@Nullable IntentFilter filter, @Nullable Intent intent) {
-        if (!shouldCache(filter)) {
-            return;
-        }
-        synchronized (sCachedStickyBroadcasts) {
-            CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter);
-            if (cachedStickyBroadcast == null) {
-                final String key = getKey(filter.getAction(0));
-                final SystemProperties.Handle handle = SystemProperties.find(key);
-                final long version = handle == null ? -1 : handle.getLong(-1 /* def */);
-                if (version == -1) {
-                    return;
-                }
-                cachedStickyBroadcast = new CachedStickyBroadcast(filter, handle);
-                sCachedStickyBroadcasts.add(cachedStickyBroadcast);
-                cachedStickyBroadcast.intent = intent;
-                cachedStickyBroadcast.version = version;
-            } else {
-                cachedStickyBroadcast.intent = intent;
-                cachedStickyBroadcast.version = cachedStickyBroadcast.propertyHandle
-                        .getLong(-1 /* def */);
-            }
-        }
-    }
-
-    private static boolean shouldCache(@Nullable IntentFilter filter) {
-        if (!Flags.useStickyBcastCache()) {
-            return false;
-        }
-        if (filter == null || filter.safeCountActions() != 1) {
-            return false;
-        }
-        if (!ArrayUtils.contains(CACHED_BROADCAST_ACTIONS, filter.getAction(0))) {
-            return false;
-        }
-        return true;
-    }
-
-    @VisibleForTesting
-    @NonNull
-    public static String getKey(@NonNull String action) {
-        return "cache_key.system_server.sticky_bcast." + action;
-    }
-
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    @Nullable
-    public static Intent getIntentUnchecked(@NonNull IntentFilter filter) {
-        synchronized (sCachedStickyBroadcasts) {
-            final CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter);
-            return cachedStickyBroadcast.intent;
-        }
-    }
-
-    @GuardedBy("sCachedStickyBroadcasts")
-    @Nullable
-    private static CachedStickyBroadcast getValueUncheckedLocked(@NonNull IntentFilter filter) {
-        for (int i = sCachedStickyBroadcasts.size() - 1; i >= 0; --i) {
-            final CachedStickyBroadcast cachedStickyBroadcast = sCachedStickyBroadcasts.get(i);
-            if (IntentFilter.filterEquals(filter, cachedStickyBroadcast.filter)) {
-                return cachedStickyBroadcast;
-            }
-        }
-        return null;
-    }
-
-    public static void incrementVersion(@NonNull String action) {
-        if (!shouldIncrementVersion(action)) {
-            return;
-        }
-        final String key = getKey(action);
-        synchronized (sCachedPropertyHandles) {
-            SystemProperties.Handle handle = sCachedPropertyHandles.get(key);
-            final long version;
-            if (handle == null) {
-                handle = SystemProperties.find(key);
-                if (handle != null) {
-                    sCachedPropertyHandles.put(key, handle);
-                }
-            }
-            version = handle == null ? 0 : handle.getLong(0 /* def */);
-            SystemProperties.set(key, String.valueOf(version + 1));
-            if (handle == null) {
-                sCachedPropertyHandles.put(key, SystemProperties.find(key));
-            }
-        }
-    }
-
-    public static void incrementVersionIfExists(@NonNull String action) {
-        if (!shouldIncrementVersion(action)) {
-            return;
-        }
-        final String key = getKey(action);
-        synchronized (sCachedPropertyHandles) {
-            final SystemProperties.Handle handle = sCachedPropertyHandles.get(key);
-            if (handle == null) {
-                return;
-            }
-            final long version = handle.getLong(0 /* def */);
-            SystemProperties.set(key, String.valueOf(version + 1));
-        }
-    }
-
-    private static boolean shouldIncrementVersion(@NonNull String action) {
-        if (!Flags.useStickyBcastCache()) {
-            return false;
-        }
-        if (!ArrayUtils.contains(CACHED_BROADCAST_ACTIONS, action)) {
-            return false;
-        }
-        return true;
-    }
-
-    @VisibleForTesting
-    public static void clearForTest() {
-        synchronized (sCachedStickyBroadcasts) {
-            sCachedStickyBroadcasts.clear();
-        }
-        synchronized (sCachedPropertyHandles) {
-            sCachedPropertyHandles.clear();
-        }
-    }
-
-    public static void dump(@NonNull ParcelFileDescriptor pfd) {
-        if (!Flags.useStickyBcastCache()) {
-            return;
-        }
-        final PrintWriter pw = new FastPrintWriter(new FileOutputStream(pfd.getFileDescriptor()));
-        synchronized (sCachedStickyBroadcasts) {
-            dumpLocked(pw);
-        }
-        pw.flush();
-    }
-
-    @GuardedBy("sCachedStickyBroadcasts")
-    private static void dumpLocked(@NonNull PrintWriter pw) {
-        final IndentingPrintWriter ipw = new IndentingPrintWriter(
-                pw, "  " /* singleIndent */, "  " /* prefix */);
-        ipw.println("Cached sticky broadcasts:");
-        ipw.increaseIndent();
-        final int count = sCachedStickyBroadcasts.size();
-        if (count == 0) {
-            ipw.println("<empty>");
-        } else {
-            for (int i = 0; i < count; ++i) {
-                final CachedStickyBroadcast cachedStickyBroadcast = sCachedStickyBroadcasts.get(i);
-                ipw.print("Entry #"); ipw.print(i); ipw.println(":");
-                ipw.increaseIndent();
-                ipw.print("filter="); ipw.println(cachedStickyBroadcast.filter.toLongString());
-                ipw.print("intent="); ipw.println(cachedStickyBroadcast.intent);
-                ipw.print("version="); ipw.println(cachedStickyBroadcast.version);
-                ipw.print("handle="); ipw.println(cachedStickyBroadcast.propertyHandle);
-                ipw.decreaseIndent();
-            }
-        }
-        ipw.decreaseIndent();
-    }
-
-    private static final class CachedStickyBroadcast {
-        @NonNull public final IntentFilter filter;
-        @Nullable public Intent intent;
-        @IntRange(from = 0) public long version;
-        @NonNull public final SystemProperties.Handle propertyHandle;
-
-        CachedStickyBroadcast(@NonNull IntentFilter filter,
-                @NonNull SystemProperties.Handle propertyHandle) {
-            this.filter = filter;
-            this.propertyHandle = propertyHandle;
-        }
-    }
-}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 3ae60d71..cd56957 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1922,19 +1922,10 @@
             }
         }
         try {
-            final Intent intent;
-            if (receiver == null && BroadcastStickyCache.useCache(filter)) {
-                intent = BroadcastStickyCache.getIntentUnchecked(filter);
-            } else {
-                intent = ActivityManager.getService().registerReceiverWithFeature(
-                        mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
-                        AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission,
-                        userId,
-                        flags);
-                if (receiver == null) {
-                    BroadcastStickyCache.add(filter, intent);
-                }
-            }
+            final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
+                    mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
+                    AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId,
+                    flags);
             if (intent != null) {
                 intent.setExtrasClassLoader(getClassLoader());
                 // TODO: determine at registration time if caller is
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 0654ac2..9bb16ae 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -124,7 +124,7 @@
     boolean onlyHasDefaultChannel(String pkg, int uid);
     boolean areChannelsBypassingDnd();
     ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int uid);
-    List<String> getPackagesBypassingDnd(int userId, boolean includeConversationChannels);
+    ParceledListSlice getPackagesBypassingDnd(int userId);
     boolean isPackagePaused(String pkg);
     void deleteNotificationHistoryItem(String pkg, int uid, long postedTime);
     boolean isPermissionFixed(String pkg, int userId);
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index b8233bc..3d85ea6 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1660,7 +1660,6 @@
                         }
                         RuntimeException ex = new IllegalArgumentException(
                                 "Originally unregistered here:");
-                        ex.fillInStackTrace();
                         rd.setUnregisterLocation(ex);
                         holder.put(r, rd);
                     }
@@ -1860,7 +1859,6 @@
             mInstrumentation = instrumentation;
             mRegistered = registered;
             mLocation = new IntentReceiverLeaked(null);
-            mLocation.fillInStackTrace();
         }
 
         void validate(Context context, Handler activityThread) {
@@ -2000,7 +1998,6 @@
                         }
                         RuntimeException ex = new IllegalArgumentException(
                                 "Originally unbound here:");
-                        ex.fillInStackTrace();
                         sd.setUnbindLocation(ex);
                         holder.put(c, sd);
                     }
@@ -2076,7 +2073,6 @@
             mActivityThread = activityThread;
             mActivityExecutor = null;
             mLocation = new ServiceConnectionLeaked(null);
-            mLocation.fillInStackTrace();
             mFlags = flags;
         }
 
@@ -2088,7 +2084,6 @@
             mActivityThread = null;
             mActivityExecutor = activityExecutor;
             mLocation = new ServiceConnectionLeaked(null);
-            mLocation.fillInStackTrace();
             mFlags = flags;
         }
 
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index e2de716..a70d493 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -83,7 +83,7 @@
          * transactions while in this call, since it can happen after an
          * activity's state is saved.  See {@link FragmentManager#beginTransaction()
          * FragmentManager.openTransaction()} for further discussion on this.
-         * 
+         *
          * <p>This function is guaranteed to be called prior to the release of
          * the last data that was supplied for this Loader.  At this point
          * you should remove all use of the old data (since it will be released
@@ -127,7 +127,7 @@
          */
         public void onLoaderReset(Loader<D> loader);
     }
-    
+
     /**
      * Ensures a loader is initialized and active.  If the loader doesn't
      * already exist, one is created and (if the activity/fragment is currently
@@ -228,7 +228,7 @@
     boolean mStarted;
     boolean mRetaining;
     boolean mRetainingStarted;
-    
+
     boolean mCreatingLoader;
     private FragmentHostCallback mHost;
 
@@ -249,13 +249,13 @@
         boolean mListenerRegistered;
 
         LoaderInfo mPendingLoader;
-        
+
         public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) {
             mId = id;
             mArgs = args;
             mCallbacks = callbacks;
         }
-        
+
         void start() {
             if (mRetaining && mRetainingStarted) {
                 // Our owner is started, but we were being retained from a
@@ -271,7 +271,7 @@
             }
 
             mStarted = true;
-            
+
             if (DEBUG) Log.v(TAG, "  Starting: " + this);
             if (mLoader == null && mCallbacks != null) {
                mLoader = mCallbacks.onCreateLoader(mId, mArgs);
@@ -291,7 +291,7 @@
                 mLoader.startLoading();
             }
         }
-        
+
         void retain() {
             if (DEBUG) Log.v(TAG, "  Retaining: " + this);
             mRetaining = true;
@@ -299,7 +299,7 @@
             mStarted = false;
             mCallbacks = null;
         }
-        
+
         void finishRetain() {
             if (mRetaining) {
                 if (DEBUG) Log.v(TAG, "  Finished Retaining: " + this);
@@ -324,7 +324,7 @@
                 callOnLoadFinished(mLoader, mData);
             }
         }
-        
+
         void reportStart() {
             if (mStarted) {
                 if (mReportNextStart) {
@@ -430,7 +430,7 @@
         @Override
         public void onLoadComplete(Loader<Object> loader, Object data) {
             if (DEBUG) Log.v(TAG, "onLoadComplete: " + this);
-            
+
             if (mDestroyed) {
                 if (DEBUG) Log.v(TAG, "  Ignoring load complete -- destroyed");
                 return;
@@ -442,7 +442,7 @@
                 if (DEBUG) Log.v(TAG, "  Ignoring load complete -- not active");
                 return;
             }
-            
+
             LoaderInfo pending = mPendingLoader;
             if (pending != null) {
                 // There is a new request pending and we were just
@@ -455,7 +455,7 @@
                 installLoader(pending);
                 return;
             }
-            
+
             // Notify of the new data so the app can switch out the old data before
             // we try to destroy it.
             if (mData != data || !mHaveData) {
@@ -503,7 +503,7 @@
                 mDeliveredData = true;
             }
         }
-        
+
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder(64);
@@ -543,13 +543,13 @@
             }
         }
     }
-    
+
     LoaderManagerImpl(String who, FragmentHostCallback host, boolean started) {
         mWho = who;
         mHost = host;
         mStarted = started;
     }
-    
+
     void updateHostController(FragmentHostCallback host) {
         mHost = host;
     }
@@ -557,7 +557,7 @@
     public FragmentHostCallback getFragmentHostCallback() {
         return mHost;
     }
-    
+
     private LoaderInfo createLoader(int id, Bundle args,
             LoaderManager.LoaderCallbacks<Object> callback) {
         LoaderInfo info = new LoaderInfo(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
@@ -565,7 +565,7 @@
         info.mLoader = (Loader<Object>)loader;
         return info;
     }
-    
+
     private LoaderInfo createAndInstallLoader(int id, Bundle args,
             LoaderManager.LoaderCallbacks<Object> callback) {
         try {
@@ -577,7 +577,7 @@
             mCreatingLoader = false;
         }
     }
-    
+
     void installLoader(LoaderInfo info) {
         mLoaders.put(info.mId, info);
         if (mStarted) {
@@ -587,23 +587,23 @@
             info.start();
         }
     }
-    
+
     /**
      * Call to initialize a particular ID with a Loader.  If this ID already
      * has a Loader associated with it, it is left unchanged and any previous
      * callbacks replaced with the newly provided ones.  If there is not currently
      * a Loader for the ID, a new one is created and started.
-     * 
+     *
      * <p>This function should generally be used when a component is initializing,
      * to ensure that a Loader it relies on is created.  This allows it to re-use
      * an existing Loader's data if there already is one, so that for example
      * when an {@link Activity} is re-created after a configuration change it
      * does not need to re-create its loaders.
-     * 
+     *
      * <p>Note that in the case where an existing Loader is re-used, the
      * <var>args</var> given here <em>will be ignored</em> because you will
      * continue using the previous Loader.
-     * 
+     *
      * @param id A unique (to this LoaderManager instance) identifier under
      * which to manage the new Loader.
      * @param args Optional arguments that will be propagated to
@@ -617,9 +617,9 @@
         if (mCreatingLoader) {
             throw new IllegalStateException("Called while creating a loader");
         }
-        
+
         LoaderInfo info = mLoaders.get(id);
-        
+
         if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);
 
         if (info == null) {
@@ -630,30 +630,30 @@
             if (DEBUG) Log.v(TAG, "  Re-using existing loader " + info);
             info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
         }
-        
+
         if (info.mHaveData && mStarted) {
             // If the loader has already generated its data, report it now.
             info.callOnLoadFinished(info.mLoader, info.mData);
         }
-        
+
         return (Loader<D>)info.mLoader;
     }
-    
+
     /**
      * Call to re-create the Loader associated with a particular ID.  If there
      * is currently a Loader associated with this ID, it will be
      * canceled/stopped/destroyed as appropriate.  A new Loader with the given
      * arguments will be created and its data delivered to you once available.
-     * 
+     *
      * <p>This function does some throttling of Loaders.  If too many Loaders
      * have been created for the given ID but not yet generated their data,
      * new calls to this function will create and return a new Loader but not
      * actually start it until some previous loaders have completed.
-     * 
+     *
      * <p>After calling this function, any previous Loaders associated with
      * this ID will be considered invalid, and you will receive no further
      * data updates from them.
-     * 
+     *
      * @param id A unique (to this LoaderManager instance) identifier under
      * which to manage the new Loader.
      * @param args Optional arguments that will be propagated to
@@ -667,7 +667,7 @@
         if (mCreatingLoader) {
             throw new IllegalStateException("Called while creating a loader");
         }
-        
+
         LoaderInfo info = mLoaders.get(id);
         if (DEBUG) Log.v(TAG, "restartLoader in " + this + ": args=" + args);
         if (info != null) {
@@ -706,7 +706,7 @@
                             info.mPendingLoader = null;
                         }
                         if (DEBUG) Log.v(TAG, "  Enqueuing as new pending loader");
-                        info.mPendingLoader = createLoader(id, args, 
+                        info.mPendingLoader = createLoader(id, args,
                                 (LoaderManager.LoaderCallbacks<Object>)callback);
                         return (Loader<D>)info.mPendingLoader.mLoader;
                     }
@@ -719,11 +719,11 @@
                 mInactiveLoaders.put(id, info);
             }
         }
-        
+
         info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
         return (Loader<D>)info.mLoader;
     }
-    
+
     /**
      * Rip down, tear apart, shred to pieces a current Loader ID.  After returning
      * from this function, any Loader objects associated with this ID are
@@ -735,7 +735,7 @@
         if (mCreatingLoader) {
             throw new IllegalStateException("Called while creating a loader");
         }
-        
+
         if (DEBUG) Log.v(TAG, "destroyLoader in " + this + " of " + id);
         int idx = mLoaders.indexOfKey(id);
         if (idx >= 0) {
@@ -763,7 +763,7 @@
         if (mCreatingLoader) {
             throw new IllegalStateException("Called while creating a loader");
         }
-        
+
         LoaderInfo loaderInfo = mLoaders.get(id);
         if (loaderInfo != null) {
             if (loaderInfo.mPendingLoader != null) {
@@ -773,16 +773,15 @@
         }
         return null;
     }
- 
+
     void doStart() {
         if (DEBUG) Log.v(TAG, "Starting in " + this);
         if (mStarted) {
             RuntimeException e = new RuntimeException("here");
-            e.fillInStackTrace();
             Log.w(TAG, "Called doStart when already started: " + this, e);
             return;
         }
-        
+
         mStarted = true;
 
         // Call out to sub classes so they can start their loaders
@@ -791,12 +790,11 @@
             mLoaders.valueAt(i).start();
         }
     }
-    
+
     void doStop() {
         if (DEBUG) Log.v(TAG, "Stopping in " + this);
         if (!mStarted) {
             RuntimeException e = new RuntimeException("here");
-            e.fillInStackTrace();
             Log.w(TAG, "Called doStop when not started: " + this, e);
             return;
         }
@@ -806,12 +804,11 @@
         }
         mStarted = false;
     }
-    
+
     void doRetain() {
         if (DEBUG) Log.v(TAG, "Retaining in " + this);
         if (!mStarted) {
             RuntimeException e = new RuntimeException("here");
-            e.fillInStackTrace();
             Log.w(TAG, "Called doRetain when not started: " + this, e);
             return;
         }
@@ -822,7 +819,7 @@
             mLoaders.valueAt(i).retain();
         }
     }
-    
+
     void finishRetain() {
         if (mRetaining) {
             if (DEBUG) Log.v(TAG, "Finished Retaining in " + this);
@@ -833,7 +830,7 @@
             }
         }
     }
-    
+
     void doReportNextStart() {
         for (int i = mLoaders.size()-1; i >= 0; i--) {
             mLoaders.valueAt(i).mReportNextStart = true;
@@ -854,7 +851,7 @@
             }
             mLoaders.clear();
         }
-        
+
         if (DEBUG) Log.v(TAG, "Destroying Inactive in " + this);
         for (int i = mInactiveLoaders.size()-1; i >= 0; i--) {
             mInactiveLoaders.valueAt(i).destroy();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 0381ee0..3d9c55c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5003,7 +5003,7 @@
 
         /**
          * Sets a very short string summarizing the most critical information contained in the
-         * notification. Suggested max length is 5 characters, and there is no guarantee how much or
+         * notification. Suggested max length is 7 characters, and there is no guarantee how much or
          * how little of this text will be shown.
          */
         @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index a458b4e..087e246 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -174,22 +174,54 @@
     }
 
     /**
-     * Apply the registered library paths to the passed impl object
-     * @return the hash code for the current version of the registered paths
+     * Apply the registered library paths to the passed AssetManager. If may create a new
+     * AssetManager if any changes are needed and it isn't allowed to reuse the old one.
+     *
+     * @return new AssetManager and the hash code for the current version of the registered paths
      */
-    public int updateResourceImplWithRegisteredLibs(@NonNull ResourcesImpl impl) {
+    public @NonNull Pair<AssetManager, Integer> updateResourceImplAssetsWithRegisteredLibs(
+            @NonNull AssetManager assets, boolean reuseAssets) {
         if (!Flags.registerResourcePaths()) {
-            return 0;
+            return new Pair<>(assets, 0);
         }
 
-        final var collector = new PathCollector(null);
-        final int size = mSharedLibAssetsMap.size();
-        for (int i = 0; i < size; i++) {
-            final var libraryKey = mSharedLibAssetsMap.valueAt(i).getResourcesKey();
-            collector.appendKey(libraryKey);
+        final int size;
+        final PathCollector collector;
+
+        synchronized (mLock) {
+            size = mSharedLibAssetsMap.size();
+            if (assets == AssetManager.getSystem()) {
+                return new Pair<>(assets, size);
+            }
+            collector = new PathCollector(resourcesKeyFromAssets(assets));
+            for (int i = 0; i < size; i++) {
+                final var libraryKey = mSharedLibAssetsMap.valueAt(i).getResourcesKey();
+                collector.appendKey(libraryKey);
+            }
         }
-        impl.getAssets().addPresetApkKeys(extractApkKeys(collector.collectedKey()));
-        return size;
+        if (collector.isSameAsOriginal()) {
+            return new Pair<>(assets, size);
+        }
+        if (reuseAssets) {
+            assets.addPresetApkKeys(extractApkKeys(collector.collectedKey()));
+            return new Pair<>(assets, size);
+        }
+        final var newAssetsBuilder = new AssetManager.Builder();
+        for (final var asset : assets.getApkAssets()) {
+            if (!asset.isForLoader()) {
+                newAssetsBuilder.addApkAssets(asset);
+            }
+        }
+        for (final var key : extractApkKeys(collector.collectedKey())) {
+            try {
+                final var asset = loadApkAssets(key);
+                newAssetsBuilder.addApkAssets(asset);
+            } catch (IOException e) {
+                Log.e(TAG, "Couldn't load assets for key " + key, e);
+            }
+        }
+        assets.getLoaders().forEach(newAssetsBuilder::addLoader);
+        return new Pair<>(newAssetsBuilder.build(), size);
     }
 
     public static class ApkKey {
@@ -624,6 +656,23 @@
         return apkKeys;
     }
 
+    private ResourcesKey resourcesKeyFromAssets(@NonNull AssetManager assets) {
+        final var libs = new ArrayList<String>();
+        final var overlays = new ArrayList<String>();
+        for (final ApkAssets asset : assets.getApkAssets()) {
+            if (asset.isSystem() || asset.isForLoader()) {
+                continue;
+            }
+            if (asset.isOverlay()) {
+                overlays.add(asset.getAssetPath());
+            } else if (asset.isSharedLib()) {
+                libs.add(asset.getAssetPath());
+            }
+        }
+        return new ResourcesKey(null, null, overlays.toArray(new String[0]),
+                libs.toArray(new String[0]), 0, null, null);
+    }
+
     /**
      * Creates an AssetManager from the paths within the ResourcesKey.
      *
@@ -752,7 +801,7 @@
 
         final Configuration config = generateConfig(key);
         final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj);
-        final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj);
+        final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj, true);
 
         if (DEBUG) {
             Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
@@ -1137,7 +1186,6 @@
         synchronized (mLock) {
             if (DEBUG) {
                 Throwable here = new Throwable();
-                here.fillInStackTrace();
                 Slog.w(TAG, "!! Create resources for key=" + key, here);
             }
 
@@ -1158,7 +1206,6 @@
         synchronized (mLock) {
             if (DEBUG) {
                 Throwable here = new Throwable();
-                here.fillInStackTrace();
                 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
             }
 
@@ -1302,7 +1349,6 @@
 
                 if (DEBUG) {
                     Throwable here = new Throwable();
-                    here.fillInStackTrace();
                     Slog.d(TAG, "updating resources override for activity=" + activityToken
                             + " from oldConfig="
                             + Configuration.resourceQualifierString(oldConfig)
@@ -1835,31 +1881,32 @@
         for (int i = 0; i < resourcesCount; i++) {
             final WeakReference<Resources> ref = mAllResourceReferences.get(i);
             final Resources r = ref != null ? ref.get() : null;
-            if (r != null) {
-                final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
-                if (key != null) {
-                    final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
-                    if (impl == null) {
-                        throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
-                    }
-                    r.setImpl(impl);
-                } else {
-                    // ResourcesKey is null which means the ResourcesImpl could belong to a
-                    // Resources created by application through Resources constructor and was not
-                    // managed by ResourcesManager, so the ResourcesImpl needs to be recreated to
-                    // have shared library asset paths appended if there are any.
-                    if (r.getImpl() != null) {
-                        final ResourcesImpl oldImpl = r.getImpl();
-                        final AssetManager oldAssets = oldImpl.getAssets();
-                        // ResourcesImpl constructor will help to append shared library asset paths.
-                        if (oldAssets != AssetManager.getSystem() && oldAssets.isUpToDate()) {
-                            final ResourcesImpl newImpl = new ResourcesImpl(oldAssets,
-                                    oldImpl.getMetrics(), oldImpl.getConfiguration(),
-                                    oldImpl.getDisplayAdjustments());
+            if (r == null) {
+                continue;
+            }
+            final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
+            if (key != null) {
+                final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
+                if (impl == null) {
+                    throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
+                }
+                r.setImpl(impl);
+            } else {
+                // ResourcesKey is null which means the ResourcesImpl could belong to a
+                // Resources created by application through Resources constructor and was not
+                // managed by ResourcesManager, so the ResourcesImpl needs to be recreated to
+                // have shared library asset paths appended if there are any.
+                final ResourcesImpl oldImpl = r.getImpl();
+                if (oldImpl != null) {
+                    final AssetManager oldAssets = oldImpl.getAssets();
+                    // ResourcesImpl constructor will help to append shared library asset paths.
+                    if (oldAssets != AssetManager.getSystem()) {
+                        if (oldAssets.isUpToDate()) {
+                            final ResourcesImpl newImpl = new ResourcesImpl(oldImpl);
                             r.setImpl(newImpl);
                         } else {
-                            Slog.w(TAG, "Skip appending shared library asset paths for the "
-                                    + "Resource as its assets are not up to date.");
+                            Slog.w(TAG, "Skip appending shared library asset paths for "
+                                    + "the Resources as its assets are not up to date.");
                         }
                     }
                 }
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 637187e..5ed1f4e 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -177,10 +177,6 @@
         {
             "file_patterns": ["(/|^)AppOpsManager.java"],
             "name": "CtsAppOpsTestCases"
-        },
-        {
-            "file_patterns": ["(/|^)BroadcastStickyCache.java"],
-            "name": "BroadcastUnitTests"
         }
     ]
 }
diff --git a/core/java/android/app/ZenBypassingApp.java b/core/java/android/app/ZenBypassingApp.java
new file mode 100644
index 0000000..89bcfa2
--- /dev/null
+++ b/core/java/android/app/ZenBypassingApp.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public final class ZenBypassingApp implements Parcelable {
+
+    @NonNull private String mPkg;
+    private boolean mAllChannelsBypass;
+
+
+    public ZenBypassingApp(@NonNull String pkg, boolean allChannelsBypass) {
+        mPkg = pkg;
+        mAllChannelsBypass = allChannelsBypass;
+    }
+
+    public ZenBypassingApp(Parcel source) {
+        mPkg = source.readString();
+        mAllChannelsBypass = source.readBoolean();
+    }
+
+    @NonNull
+    public String getPkg() {
+        return mPkg;
+    }
+
+    public boolean doAllChannelsBypass() {
+        return mAllChannelsBypass;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mPkg);
+        dest.writeBoolean(mAllChannelsBypass);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<ZenBypassingApp> CREATOR
+            = new Parcelable.Creator<ZenBypassingApp>() {
+        @Override
+        public ZenBypassingApp createFromParcel(Parcel source) {
+            return new ZenBypassingApp(source);
+        }
+        @Override
+        public ZenBypassingApp[] newArray(int size) {
+            return new ZenBypassingApp[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ZenBypassingApp)) return false;
+        ZenBypassingApp that = (ZenBypassingApp) o;
+        return mAllChannelsBypass == that.mAllChannelsBypass && Objects.equals(mPkg,
+                that.mPkg);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPkg, mAllChannelsBypass);
+    }
+
+    @Override
+    public String toString() {
+        return "ZenBypassingApp{" +
+                "mPkg='" + mPkg + '\'' +
+                ", mAllChannelsBypass=" + mAllChannelsBypass +
+                '}';
+    }
+}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 102540c..c789c41 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -55,8 +55,10 @@
 import static android.Manifest.permission.SET_TIME_ZONE;
 import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
 import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED;
+import static android.app.admin.flags.Flags.FLAG_REMOVE_MANAGED_PROFILE_ENABLED;
 import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
 import static android.app.admin.flags.Flags.onboardingConsentlessBugreports;
+import static android.app.admin.flags.Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED;
 import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
 import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
@@ -8918,12 +8920,9 @@
     /**
      * Called by a device owner, a profile owner for the primary user or a profile
      * owner of an organization-owned managed profile to turn auto time on and off.
-     * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME}
-     * to prevent the user from changing this setting.
      * <p>
-     * If user restriction {@link UserManager#DISALLOW_CONFIG_DATE_TIME} is used,
-     * no user will be able set the date and time. Instead, the network date
-     * and time will be used.
+     * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} to prevent the
+     * user from changing this setting, that way no user will be able set the date and time zone.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
      *              caller is not a device admin.
@@ -8936,7 +8935,13 @@
         throwIfParentInstance("setAutoTimeEnabled");
         if (mService != null) {
             try {
-                mService.setAutoTimeEnabled(admin, mContext.getPackageName(), enabled);
+                if (Flags.setAutoTimeEnabledCoexistence()) {
+                    mService.setAutoTimePolicy(mContext.getPackageName(),
+                            enabled ? DevicePolicyManager.AUTO_TIME_ENABLED
+                                    : DevicePolicyManager.AUTO_TIME_DISABLED);
+                } else {
+                    mService.setAutoTimeEnabled(admin, mContext.getPackageName(), enabled);
+                }
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -8966,14 +8971,102 @@
     }
 
     /**
+     * Specifies that the auto time state is not controlled by device policy.
+     *
+     * @see #setAutoTimePolicy(ComponentName, int)
+     */
+    @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE)
+    public static final int AUTO_TIME_NOT_CONTROLLED_BY_POLICY = 0;
+
+    /**
+     * Specifies the "disabled" auto time state.
+     *
+     * @see #setAutoTimePolicy(ComponentName, int)
+     */
+    @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE)
+    public static final int AUTO_TIME_DISABLED = 1;
+
+    /**
+     * Specifies the "enabled" auto time state.
+     *
+     * @see #setAutoTimePolicy(ComponentName, int)
+     */
+    @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE)
+    public static final int AUTO_TIME_ENABLED = 2;
+
+    /**
+     * Flags supplied to {@link #setAutoTimePolicy}(ComponentName, int)}.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "AUTO_TIME_" }, value = {
+            AUTO_TIME_NOT_CONTROLLED_BY_POLICY,
+            AUTO_TIME_DISABLED,
+            AUTO_TIME_ENABLED
+    })
+    public @interface AutoTimePolicy {}
+
+    /**
+     * Called by a device owner, a profile owner for the primary user or a profile owner of an
+     * organization-owned managed profile to turn auto time on and off i.e. Whether time should be
+     * obtained automatically from the network or not.
+     * <p>
+     * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} to prevent the
+     * user from changing this setting, that way no user will be able set the date and time zone.
+     *
+     * @param policy The desired state among {@link #AUTO_TIME_ENABLED} to enable,
+     *              {@link #AUTO_TIME_DISABLED} to disable and
+     *              {@link #AUTO_TIME_NOT_CONTROLLED_BY_POLICY} to unset the policy.
+     * @throws SecurityException if caller is not a device owner, a profile owner for the
+     * primary user, or a profile owner of an organization-owned managed profile, or if the caller
+     * does not hold the required permission.
+     */
+    @SupportsCoexistence
+    @RequiresPermission(value = SET_TIME, conditional = true)
+    @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE)
+    public void setAutoTimePolicy(@AutoTimePolicy int policy) {
+        throwIfParentInstance("setAutoTimePolicy");
+        if (mService != null) {
+            try {
+                mService.setAutoTimePolicy(mContext.getPackageName(), policy);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Returns current auto time policy's state.
+     *
+     * @return One of {@link #AUTO_TIME_ENABLED} if enabled, {@link #AUTO_TIME_DISABLED} if disabled
+     *              and {@link #AUTO_TIME_NOT_CONTROLLED_BY_POLICY} if it's not controlled by
+     *              policy.
+     * @throws SecurityException if caller is not a device owner, a profile owner for the
+     * primary user, or a profile owner of an organization-owned managed profile, or if the caller
+     * does not hold the required permission.
+     */
+    @SupportsCoexistence
+    @RequiresPermission(anyOf = {SET_TIME, QUERY_ADMIN_POLICY}, conditional = true)
+    @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE)
+    public @AutoTimePolicy int getAutoTimePolicy() {
+        throwIfParentInstance("getAutoTimePolicy");
+        if (mService != null) {
+            try {
+                return mService.getAutoTimePolicy(mContext.getPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY;
+    }
+
+    /**
      * Called by a device owner, a profile owner for the primary user or a profile
      * owner of an organization-owned managed profile to turn auto time zone on and off.
-     * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME}
-     * to prevent the user from changing this setting.
      * <p>
-     * If user restriction {@link UserManager#DISALLOW_CONFIG_DATE_TIME} is used,
-     * no user will be able set the date and time zone. Instead, the network date
-     * and time zone will be used.
+     * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} to prevent the
+     * user from changing this setting, that way no user will be able set the date and time zone.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with or Null if the
      *              caller is not a device admin.
@@ -8981,13 +9074,17 @@
      * @throws SecurityException if caller is not a device owner, a profile owner for the
      * primary user, or a profile owner of an organization-owned managed profile.
      */
-    @SupportsCoexistence
     @RequiresPermission(value = SET_TIME_ZONE, conditional = true)
     public void setAutoTimeZoneEnabled(@Nullable ComponentName admin, boolean enabled) {
         throwIfParentInstance("setAutoTimeZone");
         if (mService != null) {
             try {
-                mService.setAutoTimeZoneEnabled(admin, mContext.getPackageName(), enabled);
+                if (Flags.setAutoTimeZoneEnabledCoexistence()) {
+                    mService.setAutoTimeZonePolicy(mContext.getPackageName(),
+                            enabled ? AUTO_TIME_ZONE_ENABLED : AUTO_TIME_ZONE_DISABLED );
+                } else {
+                    mService.setAutoTimeZoneEnabled(admin, mContext.getPackageName(), enabled);
+                }
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -9017,6 +9114,96 @@
     }
 
     /**
+     * Specifies that the auto time zone state is not controlled by device policy.
+     *
+     * @see #setAutoTimeZonePolicy(int)
+     */
+    @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ZONE_ENABLED_COEXISTENCE)
+    public static final int AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY = 0;
+
+    /**
+     * Specifies the "disabled" auto time zone state.
+     *
+     * @see #setAutoTimeZonePolicy(int)
+     */
+    @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ZONE_ENABLED_COEXISTENCE)
+    public static final int AUTO_TIME_ZONE_DISABLED = 1;
+
+    /**
+     * Specifies the "enabled" auto time zone state.
+     *
+     * @see #setAutoTimeZonePolicy(int)
+     */
+    @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ZONE_ENABLED_COEXISTENCE)
+    public static final int AUTO_TIME_ZONE_ENABLED = 2;
+
+    /**
+     * Flags supplied to {@link #setAutoTimeZonePolicy}(int)}.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "AUTO_TIME_ZONE_" }, value = {
+            AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY,
+            AUTO_TIME_ZONE_DISABLED,
+            AUTO_TIME_ZONE_ENABLED
+    })
+    public @interface AutoTimeZonePolicy {}
+
+    /**
+     * Called by a device owner, a profile owner for the primary user or a profile owner of an
+     * organization-owned managed profile to turn auto time zone on and off.
+     * <p>
+     * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} to prevent the
+     * user from changing this setting, that way no user will be able set the date and time zone.
+     *
+     * @param policy The desired state among {@link #AUTO_TIME_ZONE_ENABLED} to enable it,
+     * {@link #AUTO_TIME_ZONE_DISABLED} to disable it or
+     * {@link #AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY} to unset the policy.
+     * @throws SecurityException if caller is not a device owner, a profile owner for the primary
+     * user, or a profile owner of an organization-owned managed profile, or if the caller does not
+     * hold the required permission.
+     */
+    @SupportsCoexistence
+    @RequiresPermission(value = SET_TIME_ZONE, conditional = true)
+    @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ZONE_ENABLED_COEXISTENCE)
+    public void setAutoTimeZonePolicy(@AutoTimeZonePolicy int policy) {
+        throwIfParentInstance("setAutoTimeZonePolicy");
+        if (mService != null) {
+            try {
+                mService.setAutoTimeZonePolicy(mContext.getPackageName(), policy);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Returns auto time zone policy's current state.
+     *
+     * @return One of {@link #AUTO_TIME_ZONE_ENABLED} if enabled, {@link #AUTO_TIME_ZONE_DISABLED}
+     *         if disabled and {@link #AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY} if the state is not
+     *         controlled by policy.
+     * @throws SecurityException if caller is not a device owner, a profile owner for the
+     * primary user, or a profile owner of an organization-owned managed profile, or if the caller
+     * does not hold the required permission.
+     */
+    @SupportsCoexistence
+    @RequiresPermission(anyOf = {SET_TIME_ZONE, QUERY_ADMIN_POLICY}, conditional = true)
+    @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ZONE_ENABLED_COEXISTENCE)
+    public @AutoTimeZonePolicy int getAutoTimeZonePolicy() {
+        throwIfParentInstance("getAutoTimeZonePolicy");
+        if (mService != null) {
+            try {
+                return mService.getAutoTimeZonePolicy(mContext.getPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return DevicePolicyManager.AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY;
+    }
+
+    /**
      * TODO (b/137101239): remove this method in follow-up CL
      * since it's only used for split system user.
      * Called by a device owner to set whether all users created on the device should be ephemeral.
@@ -12550,28 +12737,43 @@
      * @param enabled Whether or not the lockscreen needs to be shown.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
      * @see #isSecondaryLockscreenEnabled
+     * @deprecated Use {@link #setSecondaryLockscreenEnabled(boolean,PersistableBundle)} instead.
      * @hide
-     **/
+     */
+    @Deprecated
     @SystemApi
+    @FlaggedApi(FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
     public void setSecondaryLockscreenEnabled(@NonNull ComponentName admin, boolean enabled) {
-        setSecondaryLockscreenEnabled(admin, enabled, null);
+        throwIfParentInstance("setSecondaryLockscreenEnabled");
+        if (mService != null) {
+            try {
+                mService.setSecondaryLockscreenEnabled(admin, enabled, null);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     /**
      * Called by the system supervision app to set whether a secondary lockscreen needs to be shown.
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
-     *              caller is not a device admin.
+     * <p>The secondary lockscreen will by displayed after the primary keyguard security screen
+     * requirements are met.
+     *
+     * <p>This API, and associated APIs, can only be called by the default supervision app.
+     *
      * @param enabled Whether or not the lockscreen needs to be shown.
      * @param options A {@link PersistableBundle} to supply options to the lock screen.
      * @hide
      */
-    public void setSecondaryLockscreenEnabled(@Nullable ComponentName admin, boolean enabled,
+    @SystemApi
+    @FlaggedApi(FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
+    public void setSecondaryLockscreenEnabled(boolean enabled,
             @Nullable PersistableBundle options) {
         throwIfParentInstance("setSecondaryLockscreenEnabled");
         if (mService != null) {
             try {
-                mService.setSecondaryLockscreenEnabled(admin, enabled, options);
+                mService.setSecondaryLockscreenEnabled(null, enabled, options);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -16962,6 +17164,30 @@
     }
 
     /**
+     * Removes a manged profile from the device only when called from a managed profile's context
+     *
+     * @param user UserHandle of the profile to be removed
+     * @return {@code true} when removal of managed profile was successful, {@code false} when
+     * removal was unsuccessful or throws IllegalArgumentException when provided user was not a
+     * managed profile
+     * @hide
+     */
+    @SystemApi
+    @UserHandleAware
+    @FlaggedApi(FLAG_REMOVE_MANAGED_PROFILE_ENABLED)
+    @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    public boolean removeManagedProfile() {
+        if (mService == null) {
+            throw new IllegalStateException("Could not find DevicePolicyManagerService");
+        }
+        try {
+            return mService.removeManagedProfile(myUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Called when a managed profile has been provisioned.
      *
      * @throws SecurityException if the caller does not hold
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index a4e2b8f..a406802 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -375,9 +375,15 @@
     void setAutoTimeEnabled(in ComponentName who, String callerPackageName, boolean enabled);
     boolean getAutoTimeEnabled(in ComponentName who, String callerPackageName);
 
+    void setAutoTimePolicy(String callerPackageName, int policy);
+    int getAutoTimePolicy(String callerPackageName);
+
     void setAutoTimeZoneEnabled(in ComponentName who, String callerPackageName, boolean enabled);
     boolean getAutoTimeZoneEnabled(in ComponentName who, String callerPackageName);
 
+    void setAutoTimeZonePolicy(String callerPackageName, int policy);
+    int getAutoTimeZonePolicy(String callerPackageName);
+
     void setForceEphemeralUsers(in ComponentName who, boolean forceEpehemeralUsers);
     boolean getForceEphemeralUsers(in ComponentName who);
 
@@ -567,6 +573,8 @@
 
     void finalizeWorkProfileProvisioning(in UserHandle managedProfileUser, in Account migratedAccount);
 
+    boolean removeManagedProfile(int userId);
+
     void setDeviceOwnerType(in ComponentName admin, in int deviceOwnerType);
     int getDeviceOwnerType(in ComponentName admin);
 
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index be24bfa..404471e 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -343,13 +343,20 @@
 }
 
 flag {
-  name: "user_provisioning_same_state"
-  namespace: "enterprise"
-  description: "Handle exceptions while setting same provisioning state."
-  bug: "326441417"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
+    name: "user_provisioning_same_state"
+    namespace: "enterprise"
+    description: "Handle exceptions while setting same provisioning state."
+    bug: "326441417"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
+    name: "remove_managed_profile_enabled"
+    namespace: "enterprise"
+    description: "API that removes a given managed profile."
+    bug: "372652841"
 }
 
 flag {
@@ -374,3 +381,11 @@
   description: "Split up existing create and provision managed profile API."
   bug: "375382324"
 }
+
+flag {
+  name: "secondary_lockscreen_api_enabled"
+  is_exported: true
+  namespace: "enterprise"
+  description: "Add new API for secondary lockscreen"
+  bug: "336297680"
+}
diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java
index 3783a5f..7525d04 100644
--- a/core/java/android/app/jank/JankDataProcessor.java
+++ b/core/java/android/app/jank/JankDataProcessor.java
@@ -70,8 +70,8 @@
             for (int j = 0; j < mPendingStates.size(); j++) {
                 StateData pendingState = mPendingStates.get(j);
                 // This state was active during the frame
-                if (frame.frameVsyncId >= pendingState.mVsyncIdStart
-                        && frame.frameVsyncId <= pendingState.mVsyncIdEnd) {
+                if (frame.getVsyncId() >= pendingState.mVsyncIdStart
+                        && frame.getVsyncId() <= pendingState.mVsyncIdEnd) {
                     recordFrameCount(frame, pendingState, activityName, appUid);
 
                     pendingState.mProcessed = true;
@@ -131,14 +131,14 @@
             mPendingJankStats.put(stateData.mStateDataKey, jankStats);
         }
         // This state has already been accounted for
-        if (jankStats.processedVsyncId == frameData.frameVsyncId) return;
+        if (jankStats.processedVsyncId == frameData.getVsyncId()) return;
 
         jankStats.mTotalFrames += 1;
-        if (frameData.jankType == JankData.JANK_APPLICATION) {
+        if ((frameData.getJankType() & JankData.JANK_APPLICATION) != 0) {
             jankStats.mJankyFrames += 1;
         }
-        jankStats.recordFrameOverrun(frameData.actualAppFrameTimeNs);
-        jankStats.processedVsyncId = frameData.frameVsyncId;
+        jankStats.recordFrameOverrun(frameData.getActualAppFrameTimeNanos());
+        jankStats.processedVsyncId = frameData.getVsyncId();
 
     }
 
diff --git a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig
index 8b6441a..74a96c8 100644
--- a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig
+++ b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig
@@ -8,3 +8,10 @@
     description: "Make methods on OnDeviceIntelligenceManager available for local inference."
     bug: "304755128"
 }
+flag {
+    name: "enable_on_device_intelligence_module"
+    is_exported: true
+    namespace: "ondeviceintelligence"
+    description: "Enable migration to mainline module and related changes."
+    bug: "376427781"
+}
\ No newline at end of file
diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java
index 8ffda72..4a142bb 100644
--- a/core/java/android/app/wallpaper/WallpaperDescription.java
+++ b/core/java/android/app/wallpaper/WallpaperDescription.java
@@ -113,7 +113,7 @@
     /** @return the description for this wallpaper */
     @NonNull
     public List<CharSequence> getDescription() {
-        return new ArrayList<>();
+        return mDescription;
     }
 
     /** @return the {@link Uri} for the action associated with the wallpaper, or {@code null} if not
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index 964a8be..a816294 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -356,7 +356,6 @@
             }
             RuntimeException e = new RuntimeException(
                     "BroadcastReceiver trying to return result during a non-ordered broadcast");
-            e.fillInStackTrace();
             Log.e("BroadcastReceiver", e.getMessage(), e);
         }
     }
@@ -768,7 +767,6 @@
         }
         RuntimeException e = new RuntimeException(
                 "BroadcastReceiver trying to return result during a non-ordered broadcast");
-        e.fillInStackTrace();
         Log.e("BroadcastReceiver", e.getMessage(), e);
     }
 }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b776b59..8853304 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -87,7 +87,6 @@
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.XmlUtils;
 import com.android.modules.expresslog.Counter;
 
@@ -12304,7 +12303,6 @@
     }
 
     /** @hide */
-    @VisibleForTesting
     public Set<NestedIntentKey> getExtraIntentKeys() {
         return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mNestedIntentKeys;
     }
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index b4b5a7f..9ba5a35 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -359,3 +359,10 @@
     bug: "377474232"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "support_minor_versions_in_minsdkversion"
+    namespace: "package_manager_service"
+    description: "Block app installations that specify an incompatible minor SDK version"
+    bug: "377474232"
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 3d89ce1..813208d 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -330,6 +330,17 @@
   is_fixed_read_only: true
 }
 
+flag {
+    name: "cache_user_info_read_only"
+    namespace: "multiuser"
+    description: "Cache UserInfo to avoid unnecessary binder calls"
+    bug: "161915546"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+  }
+  is_fixed_read_only: true
+}
+
 # This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile.
 flag {
     name: "enable_private_space_features"
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 68b5d78..908999b 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -124,11 +124,13 @@
 
     @Nullable
     @GuardedBy("this")
-    private final StringBlock mStringBlock;  // null or closed if mNativePtr = 0.
+    private StringBlock mStringBlock;  // null or closed if mNativePtr = 0.
 
     @PropertyFlags
     private final int mFlags;
 
+    private final boolean mIsOverlay;
+
     @Nullable
     private final AssetsProvider mAssets;
 
@@ -302,40 +304,43 @@
 
     private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
             @Nullable AssetsProvider assets) throws IOException {
+        this(format, flags, assets);
         Objects.requireNonNull(path, "path");
-        mFlags = flags;
         mNativePtr = nativeLoad(format, path, flags, assets);
         mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
-        mAssets = assets;
     }
 
     private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
             @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)
             throws IOException {
+        this(format, flags, assets);
         Objects.requireNonNull(fd, "fd");
         Objects.requireNonNull(friendlyName, "friendlyName");
-        mFlags = flags;
         mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets);
         mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
-        mAssets = assets;
     }
 
     private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
             @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
             @Nullable AssetsProvider assets) throws IOException {
+        this(format, flags, assets);
         Objects.requireNonNull(fd, "fd");
         Objects.requireNonNull(friendlyName, "friendlyName");
-        mFlags = flags;
         mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets);
         mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
-        mAssets = assets;
     }
 
     private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) {
-        mFlags = flags;
+        this(FORMAT_APK, flags, assets);
         mNativePtr = nativeLoadEmpty(flags, assets);
         mStringBlock = null;
+    }
+
+    private ApkAssets(@FormatType int format, @PropertyFlags int flags,
+            @Nullable AssetsProvider assets) {
+        mFlags = flags;
         mAssets = assets;
+        mIsOverlay = format == FORMAT_IDMAP;
     }
 
     @UnsupportedAppUsage
@@ -425,6 +430,18 @@
         }
     }
 
+    public boolean isSystem() {
+        return (mFlags & PROPERTY_SYSTEM) != 0;
+    }
+
+    public boolean isSharedLib() {
+        return (mFlags & PROPERTY_DYNAMIC) != 0;
+    }
+
+    public boolean isOverlay() {
+        return mIsOverlay;
+    }
+
     @Override
     public String toString() {
         return "ApkAssets{path=" + getDebugName() + "}";
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 6fd4d01..4551bd5 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -973,9 +973,9 @@
      * Open an asset using ACCESS_STREAMING mode.  This provides access to
      * files that have been bundled with an application as assets -- that is,
      * files placed in to the "assets" directory.
-     * 
+     *
      * @param fileName The name of the asset to open.  This name can be hierarchical.
-     * 
+     *
      * @see #open(String, int)
      * @see #list
      */
@@ -988,10 +988,10 @@
      * read its contents.  This provides access to files that have been bundled
      * with an application as assets -- that is, files placed in to the
      * "assets" directory.
-     * 
+     *
      * @param fileName The name of the asset to open.  This name can be hierarchical.
      * @param accessMode Desired access mode for retrieving the data.
-     * 
+     *
      * @see #ACCESS_UNKNOWN
      * @see #ACCESS_STREAMING
      * @see #ACCESS_RANDOM
@@ -1037,14 +1037,14 @@
 
     /**
      * Return a String array of all the assets at the given path.
-     * 
+     *
      * @param path A relative path within the assets, i.e., "docs/home.html".
-     * 
+     *
      * @return String[] Array of strings, one for each asset.  These file
      *         names are relative to 'path'.  You can open the file by
      *         concatenating 'path' and a name in the returned string (via
      *         File) and passing that to open().
-     * 
+     *
      * @see #open
      */
     public @Nullable String[] list(@NonNull String path) throws IOException {
@@ -1167,20 +1167,20 @@
             return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
         }
     }
-    
+
     /**
      * Retrieve a parser for a compiled XML file.
-     * 
+     *
      * @param fileName The name of the file to retrieve.
      */
     public @NonNull XmlResourceParser openXmlResourceParser(@NonNull String fileName)
             throws IOException {
         return openXmlResourceParser(0, fileName);
     }
-    
+
     /**
      * Retrieve a parser for a compiled XML file.
-     * 
+     *
      * @param cookie Identifier of the package to be opened.
      * @param fileName The name of the file to retrieve.
      */
@@ -1200,7 +1200,7 @@
 
     /**
      * Retrieve a non-asset as a compiled XML file.  Not for use by applications.
-     * 
+     *
      * @param fileName The name of the file to retrieve.
      * @hide
      */
@@ -1211,7 +1211,7 @@
     /**
      * Retrieve a non-asset as a compiled XML file.  Not for use by
      * applications.
-     * 
+     *
      * @param cookie Identifier of the package to be opened.
      * @param fileName Name of the asset to retrieve.
      * @hide
@@ -1675,7 +1675,6 @@
                 mRefStacks = new HashMap<>();
             }
             RuntimeException ex = new RuntimeException();
-            ex.fillInStackTrace();
             mRefStacks.put(id, ex);
         }
         mNumRefs++;
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index e6b9342..bcaceb2 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -203,9 +203,25 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
             @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
-        mAssets = assets;
-        mAppliedSharedLibsHash =
-                ResourcesManager.getInstance().updateResourceImplWithRegisteredLibs(this);
+        // Don't reuse assets by default as we have no control over whether they're already
+        // inside some other ResourcesImpl.
+        this(assets, metrics, config, displayAdjustments, false);
+    }
+
+    public ResourcesImpl(@NonNull ResourcesImpl orig) {
+        // We know for sure that the other assets are in use, so can't reuse the object here.
+        this(orig.getAssets(), orig.getMetrics(), orig.getConfiguration(),
+                orig.getDisplayAdjustments(), false);
+    }
+
+    public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
+            @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments,
+            boolean reuseAssets) {
+        final var assetsAndHash =
+                ResourcesManager.getInstance().updateResourceImplAssetsWithRegisteredLibs(assets,
+                        reuseAssets);
+        mAssets = assetsAndHash.first;
+        mAppliedSharedLibsHash = assetsAndHash.second;
         mMetrics.setToDefaults();
         mDisplayAdjustments = displayAdjustments;
         mConfiguration.setToDefaults();
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index d243575..6c35d10 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -3,6 +3,16 @@
 
 flag {
     namespace: "credential_manager"
+    name: "ttl_fix_enabled"
+    description: "Enable fix for transaction too large issue"
+    bug: "371052524"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
+    namespace: "credential_manager"
     name: "settings_activity_enabled"
     is_exported: true
     description: "Enable the Credential Manager Settings Activity APIs"
diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java
index e349b81..f00c3a5 100644
--- a/core/java/android/hardware/display/DisplayTopology.java
+++ b/core/java/android/hardware/display/DisplayTopology.java
@@ -23,6 +23,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
+import android.graphics.PointF;
 import android.graphics.RectF;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -150,6 +151,138 @@
         }
     }
 
+    /**
+     * Rearranges the topology toward the positions given for each display. The width and height of
+     * each display, as well as the primary display, are not changed by this call.
+     * <p>
+     * Upon returning, the topology will be valid and normalized with each display as close to the
+     * requested positions as possible.
+     *
+     * @param newPos the desired positions (upper-left corner) of each display. The keys in the map
+     *               are the display IDs.
+     * @throws IllegalArgumentException if the keys in {@code positions} are not the exact display
+     *                                  IDs in this topology, no more, no less
+     */
+    public void rearrange(Map<Integer, PointF> newPos) {
+        var availableParents = new ArrayList<TreeNode>();
+
+        availableParents.addLast(mRoot);
+
+        var needsParent = allNodesIdMap();
+
+        // In the case of missing items, if this check doesn't detect it, a NPE will be thrown
+        // later.
+        if (needsParent.size() != newPos.size()) {
+            throw new IllegalArgumentException("newPos has wrong number of entries: " + newPos);
+        }
+
+        mRoot.mChildren.clear();
+        for (TreeNode n : needsParent.values()) {
+            n.mChildren.clear();
+        }
+
+        needsParent.remove(mRoot.mDisplayId);
+        // Start with a root island and add children to it one-by-one until the island consists of
+        // all the displays. The root island begins with only the root node, which has no
+        // parent. Then we greedily choose an optimal pairing of two nodes, consisting of a node
+        // from the island and a node not yet in the island. This is repeating until all nodes are
+        // in the island.
+        //
+        // The optimal pair is the pair which has the smallest deviation. The deviation consists of
+        // an x-axis component and a y-axis component, called xDeviation and yDeviation.
+        //
+        // The deviations are like distances but a little different. They are calculated in two
+        // steps. The first step calculates both axes in a similar way. The next step compares the
+        // two values and chooses which axis to attach along. Depending on which axis is chosen,
+        // the deviation for one axis is updated. See below for details.
+        while (!needsParent.isEmpty()) {
+            double bestDist = Double.POSITIVE_INFINITY;
+            TreeNode bestChild = null, bestParent = null;
+
+            for (var child : needsParent.values()) {
+                PointF childPos = newPos.get(child.mDisplayId);
+                float childRight = childPos.x + child.getWidth();
+                float childBottom = childPos.y + child.getHeight();
+                for (var parent : availableParents) {
+                    PointF parentPos = newPos.get(parent.mDisplayId);
+                    float parentRight = parentPos.x + parent.getWidth();
+                    float parentBottom = parentPos.y + parent.getHeight();
+
+                    // This is the smaller of the two ranges minus the amount of overlap shared
+                    // between them. The "amount of overlap" is negative if there is no overlap, but
+                    // this does not make a parenting ineligible, because we allow for attaching at
+                    // the corner and for floating point error. The overlap is more negative the
+                    // farther apart the closest corner pair is.
+                    //
+                    // For each axis, this calculates (SmallerRange - Overlap). If one range lies
+                    // completely in the other (or they are equal), the axis' deviation will be
+                    // zero.
+                    //
+                    // The "SmallerRange," which refers to smaller of the widths of the two rects,
+                    // or smaller of the heights of the two rects, is added to the deviation so that
+                    // a maximum overlap results in a deviation of zero.
+                    float xSmallerRange = Math.min(child.getWidth(), parent.getWidth());
+                    float ySmallerRange = Math.min(child.getHeight(), parent.getHeight());
+                    float xOverlap
+                            = Math.min(parentRight, childRight)
+                            - Math.max(parentPos.x, childPos.x);
+                    float yOverlap
+                            = Math.min(parentBottom, childBottom)
+                            - Math.max(parentPos.y, childPos.y);
+                    float xDeviation = xSmallerRange - xOverlap;
+                    float yDeviation = ySmallerRange - yOverlap;
+
+                    float offset;
+                    int pos;
+                    if (xDeviation <= yDeviation) {
+                        if (childPos.y < parentPos.y) {
+                            yDeviation = childBottom - parentPos.y;
+                            pos = POSITION_TOP;
+                        } else {
+                            yDeviation = parentBottom - childPos.y;
+                            pos = POSITION_BOTTOM;
+                        }
+                        offset = childPos.x - parentPos.x;
+                    } else {
+                        if (childPos.x < parentPos.x) {
+                            xDeviation = childRight - parentPos.x;
+                            pos = POSITION_LEFT;
+                        } else {
+                            xDeviation = parentRight - childPos.x;
+                            pos = POSITION_RIGHT;
+                        }
+                        offset = childPos.y - parentPos.y;
+                    }
+
+                    double dist = Math.hypot(xDeviation, yDeviation);
+                    if (dist >= bestDist) {
+                        continue;
+                    }
+
+                    bestDist = dist;
+                    bestChild = child;
+                    bestParent = parent;
+                    // Eagerly update the child's parenting info, even though we may not use it, in
+                    // which case it will be overwritten later.
+                    bestChild.mPosition = pos;
+                    bestChild.mOffset = offset;
+                }
+            }
+
+            assert bestParent != null & bestChild != null;
+
+            bestParent.addChild(bestChild);
+            if (null == needsParent.remove(bestChild.mDisplayId)) {
+                throw new IllegalStateException("child not in pending set! " + bestChild);
+            }
+            availableParents.add(bestChild);
+        }
+
+        // The conversion may have introduced an intersection of two display rects. If they are
+        // bigger than our error tolerance, this function will remove them.
+        normalize();
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -450,6 +583,20 @@
         return a == b || (Float.isNaN(a) && Float.isNaN(b)) || Math.abs(a - b) < EPSILON;
     }
 
+    private Map<Integer, TreeNode> allNodesIdMap() {
+        var pend = new ArrayDeque<TreeNode>();
+        var found = new HashMap<Integer, TreeNode>();
+
+        pend.push(mRoot);
+        do {
+            TreeNode node = pend.pop();
+            found.put(node.mDisplayId, node);
+            pend.addAll(node.mChildren);
+        } while (!pend.isEmpty());
+
+        return found;
+    }
+
     public static final class TreeNode implements Parcelable {
         public static final int POSITION_LEFT = 0;
         public static final int POSITION_TOP = 1;
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 96f6ad1..71b60cf 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -31,6 +31,7 @@
 import static com.android.hardware.input.Flags.touchpadThreeFingerTapShortcut;
 import static com.android.hardware.input.Flags.touchpadVisualizer;
 import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
+import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
 import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS;
 import static com.android.input.flags.Flags.enableInputFilterRustImpl;
 import static com.android.input.flags.Flags.keyboardRepeatKeys;
@@ -1147,4 +1148,13 @@
     public static boolean isCustomizableInputGesturesFeatureFlagEnabled() {
         return enableCustomizableInputGestures() && useKeyGestureEventHandler();
     }
+
+    /**
+     * Whether multi-key gestures are supported using {@code KeyGestureEventHandler}
+     *
+     * @hide
+     */
+    public static boolean doesKeyGestureEventHandlerSupportMultiKeyGestures() {
+        return useKeyGestureEventHandler() && useKeyGestureEventHandlerMultiPressGestures();
+    }
 }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 8c3f0ef..70a1909 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -3436,7 +3436,7 @@
         initialize();
         mInlineSuggestionSessionController.notifyOnStartInput(
                 editorInfo == null ? null : editorInfo.packageName,
-                editorInfo == null ? null : editorInfo.autofillId);
+                editorInfo == null ? null : editorInfo.getAutofillId());
         if (DEBUG) Log.v(TAG, "CALL: onStartInput");
         onStartInput(editorInfo, restarting);
         if (mDecorViewVisible) {
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index f728552..bf7116d 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1371,7 +1371,6 @@
         writeInt(N);
         if (DEBUG_ARRAY_MAP) {
             RuntimeException here =  new RuntimeException("here");
-            here.fillInStackTrace();
             Log.d(TAG, "Writing " + N + " ArrayMap entries", here);
         }
         int startPos;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 4bc8fe0..9ab9228 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3928,9 +3928,9 @@
 
         final int callingUid = Binder.getCallingUid();
         final int processUid = Process.myUid();
-        if (Build.isDebuggable() && callingUid != processUid) {
-            Log.w(TAG, "Uid " + processUid + " is fetching a copy of UserProperties on"
-                            + " behalf of callingUid " + callingUid + ". Possibly"
+        if (processUid == Process.SYSTEM_UID && callingUid != processUid) {
+            Log.w(TAG, "The System (uid " + processUid + ") is fetching a copy of"
+                            + " UserProperties on behalf of callingUid " + callingUid + ". Possibly"
                             + " it should carefully first clearCallingIdentity or perhaps use"
                             + " UserManagerInternal.getUserProperties() instead?",
                     new Throwable());
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 92c5c20..6264fbb 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -284,7 +284,7 @@
     is_fixed_read_only: true
     is_exported: true
     namespace: "android_health_services"
-    description: "This fixed read-only flag is used to enable replacing permission BODY_SENSORS (and BODY_SENSORS_BACKGROUND) with granular health permission READ_HEART_RATE (and READ_HEALTH_DATA_IN_BACKGROUND)"
+    description: "Enables replacement of BODY_SENSORS/BODY_SENSORS_BACKGROUND permissions with granular health permissions READ_HEART_RATE, READ_SKIN_TEMPERATURE, READ_OXYGEN_SATURATION, and READ_HEALTH_DATA_IN_BACKGROUND"
     bug: "364638912"
 }
 
@@ -300,24 +300,6 @@
 }
 
 flag {
-    name: "platform_skin_temperature_enabled"
-    is_fixed_read_only: true
-    is_exported: true
-    namespace: "android_health_services"
-    description: "This fixed read-only flag is used to enable platform support for Skin Temperature."
-    bug: "369872443"
-}
-
-flag {
-    name: "platform_oxygen_saturation_enabled"
-    is_fixed_read_only: true
-    is_exported: true
-    namespace: "android_health_services"
-    description: "This fixed read-only flag is used to enable platform support for Oxygen Saturation (SpO2)."
-    bug: "369873227"
-}
-
-flag {
     name: "allow_host_permission_dialogs_on_virtual_devices"
     is_exported: true
     namespace: "permissions"
@@ -400,3 +382,21 @@
     description: "Uses the SysUi process to host the SelectionToolbarRenderService."
     bug: "363318732"
 }
+
+flag{
+    name: "note_op_batching_enabled"
+    is_fixed_read_only: true
+    is_exported: true
+    namespace: "permissions"
+    description: "Batch noteOperations on the client to reduce binder call volume"
+    bug: "366013082"
+}
+
+flag {
+    name: "supervision_role_permission_update_enabled"
+    is_fixed_read_only: true
+    is_exported: true
+    namespace: "supervision"
+    description: "This flag is used to enable all the remaining permissions required to the supervision role"
+    bug: "367333883"
+}
\ No newline at end of file
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ef35171..d2a20b6 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1217,6 +1217,69 @@
             "android.settings.REGIONAL_PREFERENCES_SETTINGS";
 
     /**
+     * Activity Action: Show screen for allowing the region configuration.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_REGION_SETTINGS =
+            "android.settings.REGION_SETTINGS";
+
+    /**
+     * Activity Action: Show first day of week configuration settings.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_FIRST_DAY_OF_WEEK_SETTINGS =
+            "android.settings.FIRST_DAY_OF_WEEK_SETTINGS";
+
+    /**
+     * Activity Action: Show temperature unit configuration settings.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_TEMPERATURE_UNIT_SETTINGS =
+            "android.settings.TEMPERATURE_UNIT_SETTINGS";
+
+    /**
+     * Activity Action: Show numbering system configuration settings.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_NUMBERING_SYSTEM_SETTINGS =
+            "android.settings.NUMBERING_SYSTEM_SETTINGS";
+
+    /**
+     * Activity Action: Show measurement system configuration settings.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MEASUREMENT_SYSTEM_SETTINGS =
+            "android.settings.MEASUREMENT_SYSTEM_SETTINGS";
+
+    /**
      * Activity Action: Show settings to allow configuration of lockscreen.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig
index 4c63673..fff5363 100644
--- a/core/java/android/provider/flags.aconfig
+++ b/core/java/android/provider/flags.aconfig
@@ -63,3 +63,11 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "system_regional_preferences_api_enabled"
+    is_exported: true
+    namespace: "globalintl"
+    description: "Feature flag for regional preferences APIs"
+    bug: "370379000"
+}
diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
index 6f3e3d8..9fe0dda 100644
--- a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
+++ b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
@@ -16,20 +16,30 @@
 
 package android.security.advancedprotection;
 
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.StringDef;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.content.Intent;
 import android.os.Binder;
 import android.os.RemoteException;
 import android.security.Flags;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
+import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 
@@ -45,6 +55,139 @@
 public final class AdvancedProtectionManager {
     private static final String TAG = "AdvancedProtectionMgr";
 
+    /**
+     * Advanced Protection's identifier for setting policies or restrictions in DevicePolicyManager.
+     *
+     * @hide */
+    public static final String ADVANCED_PROTECTION_SYSTEM_ENTITY =
+            "android.security.advancedprotection";
+
+    /**
+     * Feature identifier for disallowing 2G.
+     *
+     * @hide */
+    @SystemApi
+    public static final String FEATURE_ID_DISALLOW_CELLULAR_2G =
+            "android.security.advancedprotection.feature_disallow_2g";
+
+    /**
+     * Feature identifier for disallowing install of unknown sources.
+     *
+     * @hide */
+    @SystemApi
+    public static final String FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES =
+            "android.security.advancedprotection.feature_disallow_install_unknown_sources";
+
+    /**
+     * Feature identifier for disallowing USB.
+     *
+     * @hide */
+    @SystemApi
+    public static final String FEATURE_ID_DISALLOW_USB =
+            "android.security.advancedprotection.feature_disallow_usb";
+
+    /**
+     * Feature identifier for disallowing WEP.
+     *
+     * @hide */
+    @SystemApi
+    public static final String FEATURE_ID_DISALLOW_WEP =
+            "android.security.advancedprotection.feature_disallow_wep";
+
+    /**
+     * Feature identifier for enabling MTE.
+     *
+     * @hide */
+    @SystemApi
+    public static final String FEATURE_ID_ENABLE_MTE =
+            "android.security.advancedprotection.feature_enable_mte";
+
+    /** @hide */
+    @StringDef(prefix = { "FEATURE_ID_" }, value = {
+            FEATURE_ID_DISALLOW_CELLULAR_2G,
+            FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES,
+            FEATURE_ID_DISALLOW_USB,
+            FEATURE_ID_DISALLOW_WEP,
+            FEATURE_ID_ENABLE_MTE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FeatureId {}
+
+    private static final Set<String> ALL_FEATURE_IDS = Set.of(
+            FEATURE_ID_DISALLOW_CELLULAR_2G,
+            FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES,
+            FEATURE_ID_DISALLOW_USB,
+            FEATURE_ID_DISALLOW_WEP,
+            FEATURE_ID_ENABLE_MTE);
+
+    /**
+     * Activity Action: Show a dialog with disabled by advanced protection message.
+     * <p> If a user action or a setting toggle is disabled by advanced protection, this dialog can
+     * be triggered to let the user know about this.
+     * <p>
+     * Input:
+     * <p>{@link #EXTRA_SUPPORT_DIALOG_FEATURE}: The feature identifier.
+     * <p>{@link #EXTRA_SUPPORT_DIALOG_TYPE}: The type of the action.
+     * <p>
+     * Output: Nothing.
+     *
+     * @hide */
+    @SystemApi
+    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @FlaggedApi(android.security.Flags.FLAG_AAPM_API)
+    public static final String ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG =
+            "android.security.advancedprotection.action.SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG";
+
+    /**
+     * A string extra used with {@link #createSupportIntent} to identify the feature that needs to
+     * show a support dialog explaining it was disabled by advanced protection.
+     *
+     * @hide */
+    @FeatureId
+    @SystemApi
+    public static final String EXTRA_SUPPORT_DIALOG_FEATURE =
+            "android.security.advancedprotection.extra.SUPPORT_DIALOG_FEATURE";
+
+    /**
+     * A string extra used with {@link #createSupportIntent} to identify the type of the action that
+     * needs to be explained in the support dialog.
+     *
+     * @hide */
+    @SupportDialogType
+    @SystemApi
+    public static final String EXTRA_SUPPORT_DIALOG_TYPE =
+            "android.security.advancedprotection.extra.SUPPORT_DIALOG_TYPE";
+
+    /**
+     * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating a user performed an action that was
+     * blocked by advanced protection.
+     *
+     * @hide */
+    @SystemApi
+    public static final String SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION =
+            "android.security.advancedprotection.type_blocked_interaction";
+
+    /**
+     * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating a user pressed on a setting toggle
+     * that was disabled by advanced protection.
+     *
+     * @hide */
+    @SystemApi
+    public static final String SUPPORT_DIALOG_TYPE_DISABLED_SETTING =
+            "android.security.advancedprotection.type_disabled_setting";
+
+    /** @hide */
+    @StringDef(prefix = { "SUPPORT_DIALOG_TYPE_" }, value = {
+            SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION,
+            SUPPORT_DIALOG_TYPE_DISABLED_SETTING,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SupportDialogType {}
+
+    private static final Set<String> ALL_SUPPORT_DIALOG_TYPES = Set.of(
+            SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION,
+            SUPPORT_DIALOG_TYPE_DISABLED_SETTING);
+
     private final ConcurrentHashMap<Callback, IAdvancedProtectionCallback>
             mCallbackMap = new ConcurrentHashMap<>();
 
@@ -164,6 +307,43 @@
     }
 
     /**
+     * Called by a feature to display a support dialog when a feature was disabled by advanced
+     * protection. This returns an intent that can be used with
+     * {@link Context#startActivity(Intent)} to display the dialog.
+     *
+     * <p>Note that this method doesn't check if the feature is actually disabled, i.e. this method
+     * will always return an intent.
+     *
+     * @param featureId The feature identifier.
+     * @param type The type of the feature describing the action that needs to be explained
+     *                 in the dialog or null for default explanation.
+     * @return Intent An intent to be used to start the dialog-activity that explains a feature was
+     *                disabled by advanced protection.
+     * @hide
+     */
+    @SystemApi
+    public @NonNull Intent createSupportIntent(@NonNull @FeatureId String featureId,
+            @Nullable @SupportDialogType String type) {
+        Objects.requireNonNull(featureId);
+        if (!ALL_FEATURE_IDS.contains(featureId)) {
+            throw new IllegalArgumentException(featureId + " is not a valid feature ID. See"
+                    + " FEATURE_ID_* APIs.");
+        }
+        if (type != null && !ALL_SUPPORT_DIALOG_TYPES.contains(type)) {
+            throw new IllegalArgumentException(type + " is not a valid type. See"
+                    + " SUPPORT_DIALOG_TYPE_* APIs.");
+        }
+
+        Intent intent = new Intent(ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG);
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(EXTRA_SUPPORT_DIALOG_FEATURE, featureId);
+        if (type != null) {
+            intent.putExtra(EXTRA_SUPPORT_DIALOG_TYPE, type);
+        }
+        return intent;
+    }
+
+    /**
      * A callback class for monitoring changes to Advanced Protection state
      *
      * <p>To register a callback, implement this interface, and register it with
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index ca20801..be4629a 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -16,6 +16,9 @@
 
 package android.service.autofill;
 
+import static android.service.autofill.Flags.FLAG_FILL_DIALOG_IMPROVEMENTS;
+
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -100,7 +103,12 @@
     /**
      * Indicates the request supports fill dialog presentation for the fields, the
      * system will send the request when the activity just started.
+     *
+     * @deprecated All requests would support fill dialog by default.
+     * Presence of this flag isn't needed.
      */
+    @FlaggedApi(FLAG_FILL_DIALOG_IMPROVEMENTS)
+    @Deprecated
     public static final @RequestFlags int FLAG_SUPPORTS_FILL_DIALOG = 0x40;
 
     /**
@@ -588,10 +596,10 @@
     };
 
     @DataClass.Generated(
-            time = 1701010178309L,
+            time = 1730991738865L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
-            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PCC_DETECTION\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SCREEN_HAS_CREDMAN_FIELD\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_REQUESTS_CREDMAN_SERVICE\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mHints\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.annotation.FlaggedApi @java.lang.Deprecated @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PCC_DETECTION\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SCREEN_HAS_CREDMAN_FIELD\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_REQUESTS_CREDMAN_SERVICE\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mHints\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/quickaccesswallet/flags.aconfig b/core/java/android/service/quickaccesswallet/flags.aconfig
new file mode 100644
index 0000000..07311d5
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.service.quickaccesswallet"
+container: "system"
+
+flag {
+    name: "launch_wallet_option_on_power_double_tap"
+    namespace: "wallet_integrations"
+    description: "Option to launch the Wallet app on double-tap of the power button"
+    bug: "378469025"
+}
\ No newline at end of file
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index ae12132..a42eece 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -614,7 +614,7 @@
                 if (style[j] instanceof TypefaceSpan) {
                     String s = ((TypefaceSpan) style[j]).getFamily();
 
-                    if (s.equals("monospace")) {
+                    if ("monospace".equals(s)) {
                         out.append("</tt>");
                     }
                 }
diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java
index 174e0c8..7ee0ff1 100644
--- a/core/java/android/util/ArrayMap.java
+++ b/core/java/android/util/ArrayMap.java
@@ -649,7 +649,6 @@
         }
         if (index > 0 && mHashes[index-1] > hash) {
             RuntimeException e = new RuntimeException("here");
-            e.fillInStackTrace();
             Log.w(TAG, "New hash " + hash
                     + " is before end of array hash " + mHashes[index-1]
                     + " at index " + index + (DEBUG ? " key " + key : ""), e);
diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java
index bfbca07..1344bb9 100644
--- a/core/java/android/util/ArraySet.java
+++ b/core/java/android/util/ArraySet.java
@@ -526,7 +526,6 @@
             // Cannot optimize since it would break the sorted order - fallback to add()
             if (DEBUG) {
                 RuntimeException e = new RuntimeException("here");
-                e.fillInStackTrace();
                 Log.w(TAG, "New hash " + hash
                         + " is before end of array hash " + mHashes[index - 1]
                         + " at index " + index, e);
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index 5406cf5..264db4a 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -15,9 +15,11 @@
  */
 package android.view;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.UiThread;
 import android.content.Context;
 import android.graphics.Rect;
@@ -29,6 +31,8 @@
 
 import com.android.window.flags.Flags;
 
+import java.util.concurrent.Executor;
+
 /**
  * Provides an interface to the root-Surface of a View Hierarchy or Window. This
  * is used in combination with the {@link android.view.SurfaceControl} API to enable
@@ -202,4 +206,21 @@
         throw new UnsupportedOperationException("The getInputTransferToken needs to be "
                 + "implemented before making this call.");
     }
+
+    /**
+     * Registers a {@link OnJankDataListener} to receive jank classification data about rendered
+     * frames.
+     *
+     * @param executor The executor on which the listener will be invoked.
+     * @param listener The listener to add.
+     * @return The {@link OnJankDataListenerRegistration} for the listener.
+     */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_JANK_API)
+    @SuppressLint("PairedRegistration")
+    default SurfaceControl.OnJankDataListenerRegistration registerOnJankDataListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SurfaceControl.OnJankDataListener listener) {
+        return SurfaceControl.OnJankDataListenerRegistration.NONE;
+    }
 }
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 7e24749..5a71282 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -189,6 +189,11 @@
     @UnsupportedAppUsage
     private long mLastFrameTimeNanos;
 
+    // Keeps track of the last scheduled frame time without additional offsets
+    // added from buffer stuffing recovery. Used to compare timing of vsyncs to
+    // determine idle state.
+    private long mLastNoOffsetFrameTimeNanos;
+
     /** DO NOT USE since this will not updated when screen refresh changes. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R,
             publicAlternatives = "Use {@link android.view.Display#getRefreshRate} instead")
@@ -203,6 +208,50 @@
     private final FrameData mFrameData = new FrameData();
     private volatile boolean mInDoFrameCallback = false;
 
+    private static class BufferStuffingData {
+        enum RecoveryAction {
+            // No recovery
+            NONE,
+            // Recovery has started, adds a negative offset
+            OFFSET,
+            // Recovery has started, delays a frame to return buffer count
+            // back toward threshold.
+            DELAY_FRAME
+        }
+        // The maximum number of times frames will be delayed per buffer stuffing event.
+        // Since buffer stuffing can persist for several consecutive frames following the
+        // initial missed frame, we want to adjust the timeline with enough frame delays and
+        // offsets to return the queued buffer count back to threshold.
+        public static final int MAX_FRAME_DELAYS = 3;
+
+        // Whether buffer stuffing recovery has begun. Recovery can only end
+        // when events are idle.
+        public boolean isRecovering = false;
+
+        // The number of frames delayed so far during recovery. Used to compare with
+        // MAX_FRAME_DELAYS to safeguard against excessive frame delays during recovery.
+        // Also used as unique cookie for tracing.
+        public int numberFrameDelays = 0;
+
+        // The number of additional frame delays scheduled during recovery to wait for the next
+        // vsync. These are scheduled when frame times appear to go backward or frames are
+        // being skipped due to FPSDivisor.
+        public int numberWaitsForNextVsync = 0;
+
+        /**
+         * After buffer stuffing recovery has ended with a detected idle state, the
+         * recovery data trackers can be reset in preparation for any future
+         * stuffing events.
+         */
+        public void reset() {
+            isRecovering = false;
+            numberFrameDelays = 0;
+            numberWaitsForNextVsync = 0;
+        }
+    }
+
+    private final BufferStuffingData mBufferStuffingData = new BufferStuffingData();
+
     /**
      * Contains information about the current frame for jank-tracking,
      * mainly timings of key events along with a bit of metadata about
@@ -850,13 +899,99 @@
         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
 
+    // Conducts logic for beginning or ending buffer stuffing recovery.
+    // Returns an enum for the recovery action that should be taken in doFrame().
+    BufferStuffingData.RecoveryAction checkBufferStuffingRecovery(long frameTimeNanos,
+            DisplayEventReceiver.VsyncEventData vsyncEventData) {
+        // Canned animations can recover from buffer stuffing whenever more
+        // than 2 buffers are queued.
+        if (vsyncEventData.numberQueuedBuffers > 2) {
+            mBufferStuffingData.isRecovering = true;
+            // Intentional frame delay that can happen at most MAX_FRAME_DELAYS times per
+            // buffer stuffing event until the buffer count returns to threshold. The
+            // delayed frames are compensated for by the negative offsets added to the
+            // animation timestamps.
+            if (mBufferStuffingData.numberFrameDelays < mBufferStuffingData.MAX_FRAME_DELAYS) {
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                    Trace.asyncTraceForTrackBegin(
+                            Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", "Thread "
+                            + android.os.Process.myTid() + ", recover frame #"
+                            + mBufferStuffingData.numberFrameDelays,
+                            mBufferStuffingData.numberFrameDelays);
+                }
+                mBufferStuffingData.numberFrameDelays++;
+                scheduleVsyncLocked();
+                return BufferStuffingData.RecoveryAction.DELAY_FRAME;
+            }
+        }
+
+        if (mBufferStuffingData.isRecovering) {
+            // Includes an additional expected frame delay from the natural scheduling
+            // of the next vsync event.
+            int totalFrameDelays = mBufferStuffingData.numberFrameDelays
+                    + mBufferStuffingData.numberWaitsForNextVsync + 1;
+            long vsyncsSinceLastCallback =
+                    (frameTimeNanos - mLastNoOffsetFrameTimeNanos) / mLastFrameIntervalNanos;
+
+            // Detected idle state due to a longer inactive period since the last vsync callback
+            // than the total expected number of vsync frame delays. End buffer stuffing recovery.
+            // There are no frames to animate and offsets no longer need to be added
+            // since the idle state gives the animation a chance to catch up.
+            if (vsyncsSinceLastCallback > totalFrameDelays) {
+                if (DEBUG_JANK) {
+                    Log.d(TAG, "End buffer stuffing recovery");
+                }
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                    for (int i = 0; i < mBufferStuffingData.numberFrameDelays; i++) {
+                        Trace.asyncTraceForTrackEnd(
+                                Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", i);
+                    }
+                }
+                mBufferStuffingData.reset();
+
+            } else {
+                if (DEBUG_JANK) {
+                    Log.d(TAG, "Adjust animation timeline with a negative offset");
+                }
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                    Trace.instantForTrack(
+                            Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery",
+                            "Negative offset added to animation");
+                }
+                return BufferStuffingData.RecoveryAction.OFFSET;
+            }
+        }
+        return BufferStuffingData.RecoveryAction.NONE;
+    }
+
     void doFrame(long frameTimeNanos, int frame,
             DisplayEventReceiver.VsyncEventData vsyncEventData) {
         final long startNanos;
         final long frameIntervalNanos = vsyncEventData.frameInterval;
         boolean resynced = false;
+        long offsetFrameTimeNanos = frameTimeNanos;
+
+        // Evaluate if buffer stuffing recovery needs to start or end, and
+        // what actions need to be taken for recovery.
+        switch (checkBufferStuffingRecovery(frameTimeNanos, vsyncEventData)) {
+            case NONE:
+                // Without buffer stuffing recovery, offsetFrameTimeNanos is
+                // synonymous with frameTimeNanos.
+                break;
+            case OFFSET:
+                // Add animation offset. Used to update frame timeline with
+                // offset before jitter is calculated.
+                offsetFrameTimeNanos = frameTimeNanos - frameIntervalNanos;
+                break;
+            case DELAY_FRAME:
+                // Intentional frame delay to help restore queued buffer count to threshold.
+                return;
+            default:
+                break;
+        }
+
         try {
-            FrameTimeline timeline = mFrameData.update(frameTimeNanos, vsyncEventData);
+            FrameTimeline timeline = mFrameData.update(offsetFrameTimeNanos, vsyncEventData);
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                 Trace.traceBegin(
                         Trace.TRACE_TAG_VIEW, "Choreographer#doFrame " + timeline.mVsyncId);
@@ -867,15 +1002,18 @@
                     traceMessage("Frame not scheduled");
                     return; // no work to do
                 }
+                mLastNoOffsetFrameTimeNanos = frameTimeNanos;
 
                 if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
                     mDebugPrintNextFrameTimeDelta = false;
                     Log.d(TAG, "Frame time delta: "
-                            + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
+                            + ((offsetFrameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
                 }
 
-                long intendedFrameTimeNanos = frameTimeNanos;
+                long intendedFrameTimeNanos = offsetFrameTimeNanos;
                 startNanos = System.nanoTime();
+                // Calculating jitter involves using the original frame time without
+                // adjustments from buffer stuffing
                 final long jitterNanos = startNanos - frameTimeNanos;
                 if (jitterNanos >= frameIntervalNanos) {
                     frameTimeNanos = startNanos;
@@ -899,6 +1037,13 @@
                                     + " ms in the past.");
                         }
                     }
+                    if (mBufferStuffingData.isRecovering) {
+                        frameTimeNanos -= frameIntervalNanos;
+                        if (DEBUG_JANK) {
+                            Log.d(TAG, "Adjusted animation timeline with a negative offset after"
+                                    + " jitter calculation");
+                        }
+                    }
                     timeline = mFrameData.update(
                             frameTimeNanos, mDisplayEventReceiver, jitterNanos);
                     resynced = true;
@@ -910,6 +1055,9 @@
                                 + "previously skipped frame.  Waiting for next vsync.");
                     }
                     traceMessage("Frame time goes backward");
+                    if (mBufferStuffingData.isRecovering) {
+                        mBufferStuffingData.numberWaitsForNextVsync++;
+                    }
                     scheduleVsyncLocked();
                     return;
                 }
@@ -918,6 +1066,9 @@
                     long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                     if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                         traceMessage("Frame skipped due to FPSDivisor");
+                        if (mBufferStuffingData.isRecovering) {
+                            mBufferStuffingData.numberWaitsForNextVsync++;
+                        }
                         scheduleVsyncLocked();
                         return;
                     }
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 0241e94..a1a9fc6 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1786,7 +1786,12 @@
      *         {@code getWindowManager()} or {@code getSystemService(Context.WINDOW_SERVICE)}), the
      *         returned metrics provide the size of the current app window. As a result, in
      *         multi-window mode, the returned size can be smaller than the size of the device
-     *         screen.
+     *         screen. System decoration handling may vary depending on API level:
+     *         <ul>
+     *             <li>API level 35 and above, the window size will be returned.
+     *             <li>API level 34 and below, the window size minus system decoration areas and
+     *                 display cutout is returned.
+     *         </ul>
      *     <li>If metrics are requested from a non-activity context (for example, the application
      *         context, where the WindowManager is accessed by
      *         {@code getApplicationContext().getSystemService(Context.WINDOW_SERVICE)}), the
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index fc7a65d..bb233d2 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -207,6 +207,8 @@
         // reasonable timestamps.
         public int frameTimelinesLength = 1;
 
+        public int numberQueuedBuffers = 0;
+
         VsyncEventData() {
             frameTimelines = new FrameTimeline[FRAME_TIMELINES_CAPACITY];
             for (int i = 0; i < frameTimelines.length; i++) {
@@ -217,11 +219,13 @@
         // Called from native code.
         @SuppressWarnings("unused")
         VsyncEventData(FrameTimeline[] frameTimelines, int preferredFrameTimelineIndex,
-                int frameTimelinesLength, long frameInterval) {
+                int frameTimelinesLength, long frameInterval,
+                int numberQueuedBuffers) {
             this.frameTimelines = frameTimelines;
             this.preferredFrameTimelineIndex = preferredFrameTimelineIndex;
             this.frameTimelinesLength = frameTimelinesLength;
             this.frameInterval = frameInterval;
+            this.numberQueuedBuffers = numberQueuedBuffers;
         }
 
         void copyFrom(VsyncEventData other) {
@@ -231,6 +235,7 @@
             for (int i = 0; i < frameTimelines.length; i++) {
                 frameTimelines[i].copyFrom(other.frameTimelines[i]);
             }
+            numberQueuedBuffers = other.numberQueuedBuffers;
         }
 
         public FrameTimeline preferredFrameTimeline() {
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index 9e25a3e..58b2a67 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -18,10 +18,13 @@
 
 import static android.graphics.FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 
+import com.android.window.flags.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -177,6 +180,16 @@
     public static final int DEADLINE = 13;
 
     /**
+     * Metric identifier for the frame's VSync identifier.
+     * <p>
+     * The id that corresponds to the chosen frame timeline, used to correlate a frame produced
+     * by HWUI with the timeline data from the compositor.
+     * </p>
+     */
+    @FlaggedApi(Flags.FLAG_JANK_API)
+    public static final int FRAME_TIMELINE_VSYNC_ID = 14;
+
+    /**
      * Identifiers for metrics available for each frame.
      *
      * {@see #getMetric(int)}
@@ -337,7 +350,8 @@
      * @return the value of the metric or -1 if it is not available.
      */
     public long getMetric(@Metric int id) {
-        if (id < UNKNOWN_DELAY_DURATION || id > DEADLINE) {
+        if (id < UNKNOWN_DELAY_DURATION
+                || id > (Flags.jankApi() ? FRAME_TIMELINE_VSYNC_ID : DEADLINE)) {
             return -1;
         }
 
@@ -351,6 +365,8 @@
             return mTimingData[Index.INTENDED_VSYNC];
         } else if (id == VSYNC_TIMESTAMP) {
             return mTimingData[Index.VSYNC];
+        } else if (id == FRAME_TIMELINE_VSYNC_ID) {
+            return mTimingData[Index.FRAME_TIMELINE_VSYNC_ID];
         }
 
         int durationsIdx = 2 * id;
@@ -358,4 +374,3 @@
                 - mTimingData[DURATIONS[durationsIdx]];
     }
 }
-
diff --git a/core/java/android/view/HapticScrollFeedbackProvider.java b/core/java/android/view/HapticScrollFeedbackProvider.java
index 0001176..c3fb855 100644
--- a/core/java/android/view/HapticScrollFeedbackProvider.java
+++ b/core/java/android/view/HapticScrollFeedbackProvider.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.flags.Flags.dynamicViewRotaryHapticsConfiguration;
+
 import android.annotation.NonNull;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -41,13 +43,8 @@
 
     private final View mView;
     private final ViewConfiguration mViewConfig;
-    /**
-     * Flag to disable the logic in this class if the View-based scroll haptics implementation is
-     * enabled. If {@code false}, this class will continue to run despite the View's scroll
-     * haptics implementation being enabled. This value should be set to {@code true} when this
-     * class is directly used by the View class.
-     */
-    private final boolean mDisabledIfViewPlaysScrollHaptics;
+    /** Whether or not this provider is being used directly by the View class. */
+    private final boolean mIsFromView;
 
 
     // Info about the cause of the latest scroll event.
@@ -65,17 +62,23 @@
     private boolean mHapticScrollFeedbackEnabled = false;
 
     public HapticScrollFeedbackProvider(@NonNull View view) {
-        this(view, ViewConfiguration.get(view.getContext()),
-                /* disabledIfViewPlaysScrollHaptics= */ true);
+        this(view, ViewConfiguration.get(view.getContext()), /* isFromView= */ false);
     }
 
     /** @hide */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public HapticScrollFeedbackProvider(
-            View view, ViewConfiguration viewConfig, boolean disabledIfViewPlaysScrollHaptics) {
+            View view, ViewConfiguration viewConfig, boolean isFromView) {
         mView = view;
         mViewConfig = viewConfig;
-        mDisabledIfViewPlaysScrollHaptics = disabledIfViewPlaysScrollHaptics;
+        mIsFromView = isFromView;
+        if (dynamicViewRotaryHapticsConfiguration() && !isFromView) {
+            // Disable the View class's rotary scroll feedback logic if this provider is not being
+            // directly used by the View class. This is to avoid double rotary scroll feedback:
+            // one from the View class, and one from this provider instance (i.e. mute the View
+            // class's rotary feedback and enable this provider).
+            view.disableRotaryScrollFeedback();
+        }
     }
 
     @Override
@@ -151,7 +154,8 @@
             mAxis = axis;
             mDeviceId = deviceId;
 
-            if (mDisabledIfViewPlaysScrollHaptics
+            if (!dynamicViewRotaryHapticsConfiguration()
+                    && !mIsFromView
                     && (source == InputDevice.SOURCE_ROTARY_ENCODER)
                     && mViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) {
                 mHapticScrollFeedbackEnabled = false;
diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java
index 9714896..2d2f79d 100644
--- a/core/java/android/view/ImeFocusController.java
+++ b/core/java/android/view/ImeFocusController.java
@@ -23,6 +23,7 @@
 import android.annotation.UiThread;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.inputmethod.InputMethodDebug;
@@ -150,6 +151,17 @@
         if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
             return InputMethodManager.DISPATCH_NOT_HANDLED;
         }
+        if (Flags.refactorInsetsController() && event instanceof KeyEvent keyEvent
+                && keyEvent.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+            final var insetsController = mViewRootImpl.getInsetsController();
+            if (insetsController.getAnimationType(WindowInsets.Type.ime())
+                    == InsetsController.ANIMATION_TYPE_HIDE
+                    || insetsController.isPredictiveBackImeHideAnimInProgress()) {
+                // if there is an ongoing hide animation, the back event should not be dispatched
+                // to the IME.
+                return InputMethodManager.DISPATCH_NOT_HANDLED;
+            }
+        }
         final InputMethodManager imm =
                 mViewRootImpl.mContext.getSystemService(InputMethodManager.class);
         if (imm == null) {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 26ca813..b0813f3 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1910,7 +1910,8 @@
         mImeSourceConsumer.onWindowFocusLost();
     }
 
-    @VisibleForTesting
+    /** Returns the current {@link AnimationType} of an {@link InsetsType}. */
+    @VisibleForTesting(visibility = PACKAGE)
     public @AnimationType int getAnimationType(@InsetsType int type) {
         for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
             InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner;
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 206c737..68efa79 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -22,6 +22,7 @@
 import static android.graphics.Matrix.MSKEW_Y;
 import static android.graphics.Matrix.MTRANS_X;
 import static android.graphics.Matrix.MTRANS_Y;
+import static android.view.flags.Flags.bufferStuffingRecovery;
 import static android.view.SurfaceControlProto.HASH_CODE;
 import static android.view.SurfaceControlProto.LAYER_ID;
 import static android.view.SurfaceControlProto.NAME;
@@ -410,8 +411,19 @@
 
     /**
      * Jank information to be fed back via {@link OnJankDataListener}.
-     * @hide
+     * <p>
+     * Apps may register a {@link OnJankDataListener} to get periodic batches of jank classification
+     * data from the (<a
+     * href="https://source.android.com/docs/core/graphics/surfaceflinger-windowmanagersystem">
+     * composer</a> regarding rendered frames. A frame is considered janky if it did not reach the
+     * display at the intended time, typically due to missing a rendering deadline. This API
+     * provides information that can be used to identify the root cause of the scheduling misses
+     * and provides overall frame scheduling statistics.
+     * <p>
+     * This API can be used in conjunction with the {@link FrameMetrics} API by associating jank
+     * classification data with {@link FrameMetrics} data via the frame VSync id.
      */
+    @FlaggedApi(Flags.FLAG_JANK_API)
     public static class JankData {
 
         /**
@@ -428,29 +440,105 @@
         @Retention(RetentionPolicy.SOURCE)
         public @interface JankType {}
 
-        // No Jank
+        /**
+         * No jank detected, the frame was on time.
+         */
         public static final int JANK_NONE = 0;
-        // Jank caused by the composer missing a deadline
+
+        /**
+         * Bitmask for jank due to deadlines missed by the composer.
+         */
         public static final int JANK_COMPOSER = 1 << 0;
-        // Jank caused by the application missing the composer's deadline
+
+        /**
+         * Bitmask for jank due to deadlines missed by the application.
+         */
         public static final int JANK_APPLICATION = 1 << 1;
-        // Jank due to other unknown reasons
+
+        /**
+         * Bitmask for jank due to deadlines missed by other system components.
+         */
         public static final int JANK_OTHER = 1 << 2;
 
+        private final long mFrameVsyncId;
+        private final @JankType int mJankType;
+        private final long mFrameIntervalNs;
+        private final long mScheduledAppFrameTimeNs;
+        private final long mActualAppFrameTimeNs;
+
+        /**
+         * @hide
+         */
         public JankData(long frameVsyncId, @JankType int jankType, long frameIntervalNs,
                 long scheduledAppFrameTimeNs, long actualAppFrameTimeNs) {
-            this.frameVsyncId = frameVsyncId;
-            this.jankType = jankType;
-            this.frameIntervalNs = frameIntervalNs;
-            this.scheduledAppFrameTimeNs = scheduledAppFrameTimeNs;
-            this.actualAppFrameTimeNs = actualAppFrameTimeNs;
+            mFrameVsyncId = frameVsyncId;
+            mJankType = jankType;
+            mFrameIntervalNs = frameIntervalNs;
+            mScheduledAppFrameTimeNs = scheduledAppFrameTimeNs;
+            mActualAppFrameTimeNs = actualAppFrameTimeNs;
         }
 
-        public final long frameVsyncId;
-        public final @JankType int jankType;
-        public final long frameIntervalNs;
-        public final long scheduledAppFrameTimeNs;
-        public final long actualAppFrameTimeNs;
+        /**
+         * Returns the id of the frame for this jank classification.
+         *
+         * @see FrameMetrics#FRAME_TIMELINE_VSYNC_ID
+         * @see Choreographer.FrameTimeline#getVsyncId
+         * @see Transaction#setFrameTimeline
+         * @return the frame id
+         */
+        public long getVsyncId() {
+            return mFrameVsyncId;
+        }
+
+        /**
+         * Returns the bitmask indicating the types of jank observed.
+         *
+         * @return the jank type bitmask
+         */
+        public @JankType int getJankType() {
+            return mJankType;
+        }
+
+        /**
+         * Returns the time between frame VSyncs in nanoseconds.
+         *
+         * @return the frame interval in ns
+         * @hide
+         */
+        public long getFrameIntervalNanos() {
+            return mFrameIntervalNs;
+        }
+
+        /**
+         * Returns the duration in nanoseconds the application was scheduled to use to render this
+         * frame.
+         * <p>
+         * Note that this may be higher than the frame interval to allow for CPU/GPU
+         * parallelization of work.
+         *
+         * @return scheduled app time in ns
+         */
+        public long getScheduledAppFrameTimeNanos() {
+            return mScheduledAppFrameTimeNs;
+        }
+
+        /**
+         * Returns the actual time in nanoseconds taken by the application to render this frame.
+         *
+         * @return the actual app time in ns
+         */
+        public long getActualAppFrameTimeNanos() {
+            return mActualAppFrameTimeNs;
+        }
+
+        @Override
+        public String toString() {
+            return "JankData{vsync=" + mFrameVsyncId
+                    + ", jankType=0x" + Integer.toHexString(mJankType)
+                    + ", frameInterval=" + mFrameIntervalNs + "ns"
+                    + ", scheduledAppTime=" + mScheduledAppFrameTimeNs + "ns"
+                    + ", actualAppTime=" + mActualAppFrameTimeNs + "ns}";
+        }
     }
 
     /**
@@ -458,12 +546,13 @@
      * surface.
      *
      * @see JankData
-     * @see #addJankDataListener
-     * @hide
+     * @see #addOnJankDataListener
      */
+    @FlaggedApi(Flags.FLAG_JANK_API)
     public interface OnJankDataListener {
         /**
-         * Called when new jank classifications are available.
+         * Called when new jank classifications are available. The listener is invoked out of band
+         * of the rendered frames with jank classification data for a batch of frames.
          */
         void onJankDataAvailable(@NonNull List<JankData> jankData);
 
@@ -471,9 +560,22 @@
 
     /**
      * Handle to a registered {@link OnJankDatalistener}.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_JANK_API)
     public static class OnJankDataListenerRegistration {
+        /** @hide */
+        public static final OnJankDataListenerRegistration NONE =
+                new OnJankDataListenerRegistration() {
+                    @Override
+                    public void flush() {}
+
+                    @Override
+                    public void removeAfter(long afterVsync) {}
+
+                    @Override
+                    public void release() {}
+                };
+
         private final long mNativeObject;
 
         private static final NativeAllocationRegistry sRegistry =
@@ -484,6 +586,11 @@
         private final Runnable mFreeNativeResources;
         private boolean mRemoved = false;
 
+        private OnJankDataListenerRegistration() {
+            mNativeObject = 0;
+            mFreeNativeResources = () -> {};
+        }
+
         OnJankDataListenerRegistration(SurfaceControl surface, OnJankDataListener listener) {
             mNativeObject = nativeCreateJankDataListenerWrapper(surface.mNativeObject, listener);
             mFreeNativeResources = (mNativeObject == 0) ? () -> {} :
@@ -499,10 +606,17 @@
         }
 
         /**
-         * Request the removal of the registered listener after the VSync with the provided ID. Use
-         * a value <= 0 for afterVsync to remove the listener immediately. The given listener will
-         * not be removed before the given VSync, but may still reveive data for frames past the
-         * provided VSync.
+         * Schedule the removal of the registered listener after the frame with the provided id.
+         * <p>
+         * Because jank classification is only possible after frames have been displayed, the
+         * callbacks are always delayed. To ensure receipt of all jank classification data, an
+         * application can schedule the removal to happen no sooner than after the data for the
+         * frame with the provided id has been provided.
+         * <p>
+         * Use a value &lt;= 0 for afterVsync to remove the listener immediately, ensuring no future
+         * callbacks.
+         *
+         * @param afterVsync the id of the Vsync after which to remove the listener
          */
         public void removeAfter(long afterVsync) {
             mRemoved = true;
@@ -511,6 +625,7 @@
 
         /**
          * Free the native resources associated with the listener registration.
+         * @hide
          */
         public void release() {
             if (!mRemoved) {
@@ -663,6 +778,13 @@
     public static final int CAN_OCCLUDE_PRESENTATION = 0x00001000;
 
     /**
+     * Indicates that the SurfaceControl should recover from buffer stuffing when
+     * possible. This is the case when the SurfaceControl is a ViewRootImpl.
+     * @hide
+     */
+    public static final int RECOVERABLE_FROM_BUFFER_STUFFING = 0x00002000;
+
+    /**
      * Surface creation flag: Creates a surface where color components are interpreted
      * as "non pre-multiplied" by their alpha channel. Of course this flag is
      * meaningless for surfaces without an alpha channel. By default
@@ -4867,6 +4989,23 @@
             nativeSetDesiredPresentTimeNanos(mNativeObject, desiredPresentTimeNanos);
             return this;
         }
+
+        /**
+         * Specifies that the SurfaceControl is a buffer producer that should recover from buffer
+         * stuffing, meaning that the SurfaceControl is a ViewRootImpl.
+         *
+         * @hide
+         */
+        @NonNull
+        public Transaction setRecoverableFromBufferStuffing(@NonNull SurfaceControl sc) {
+            if (bufferStuffingRecovery()) {
+                checkPreconditions(sc);
+                nativeSetFlags(mNativeObject, sc.mNativeObject, RECOVERABLE_FROM_BUFFER_STUFFING,
+                        RECOVERABLE_FROM_BUFFER_STUFFING);
+            }
+            return this;
+        }
+
         /**
          * Writes the transaction to parcel, clearing the transaction as if it had been applied so
          * it can be used to store future transactions. It's the responsibility of the parcel
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0b61c94..049189f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -65,6 +65,7 @@
 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
 import static com.android.window.flags.Flags.FLAG_DELEGATE_UNHANDLED_DRAGS;
+import static com.android.window.flags.Flags.FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW;
 
 import static java.lang.Math.max;
 
@@ -5552,10 +5553,11 @@
 
     /**
      * Flag indicating that this drag will result in the caller activity's task to be hidden for the
-     * duration of the drag, this means that the source activity will not receive drag events for
-     * the current drag gesture. Only the current voice interaction service may use this flag.
-     * @hide
+     * duration of the drag, which means that the source activity will not receive drag events for
+     * the current drag gesture. Only the current
+     * {@link android.service.voice.VoiceInteractionService} may use this flag.
      */
+    @FlaggedApi(FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW)
     public static final int DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START = 1 << 14;
 
     /**
@@ -16752,9 +16754,7 @@
                 mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_DETERMINED;
             }
         }
-        final boolean processForRotaryScrollHaptics =
-                isRotaryEncoderEvent && ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_ENABLED) != 0);
-        if (processForRotaryScrollHaptics) {
+        if (isRotaryEncoderEvent && ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_ENABLED) != 0)) {
             mPrivateFlags4 &= ~PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT;
             mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_WAITING_FOR_SCROLL_EVENT;
         }
@@ -16771,7 +16771,10 @@
         // Process scroll haptics after `onGenericMotionEvent`, since that's where scrolling usually
         // happens. Some views may return false from `onGenericMotionEvent` even if they have done
         // scrolling, so disregard the return value when processing for scroll haptics.
-        if (processForRotaryScrollHaptics) {
+        // Check for `PFLAG4_ROTARY_HAPTICS_ENABLED` again, because the View implementation may
+        // call `disableRotaryScrollFeedback` in `onGenericMotionEvent`, which could change the
+        // value of `PFLAG4_ROTARY_HAPTICS_ENABLED`.
+        if (isRotaryEncoderEvent && ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_ENABLED) != 0)) {
             if ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT) != 0) {
                 doRotaryProgressForScrollHaptics(event);
             } else {
@@ -18714,7 +18717,7 @@
     private HapticScrollFeedbackProvider getScrollFeedbackProvider() {
         if (mScrollFeedbackProvider == null) {
             mScrollFeedbackProvider = new HapticScrollFeedbackProvider(this,
-                    ViewConfiguration.get(mContext), /* disabledIfViewPlaysScrollHaptics= */ false);
+                    ViewConfiguration.get(mContext), /* isFromView= */ true);
         }
         return mScrollFeedbackProvider;
     }
@@ -18744,6 +18747,21 @@
     }
 
     /**
+     * Disables the rotary scroll feedback implementation of the View class.
+     *
+     * <p>Note that this does NOT disable all rotary scroll feedback; it just disables the logic
+     * implemented within the View class. The child implementation of the View may implement its own
+     * rotary scroll feedback logic or use {@link ScrollFeedbackProvider} to generate rotary scroll
+     * feedback.
+     */
+    void disableRotaryScrollFeedback() {
+        // Force set PFLAG4_ROTARY_HAPTICS_DETERMINED to avoid recalculating
+        // PFLAG4_ROTARY_HAPTICS_ENABLED under any circumstance.
+        mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_DETERMINED;
+        mPrivateFlags4 &= ~PFLAG4_ROTARY_HAPTICS_ENABLED;
+    }
+
+    /**
      * This is called in response to an internal scroll in this view (i.e., the
      * view scrolled its own contents). This is typically as a result of
      * {@link #scrollBy(int, int)} or {@link #scrollTo(int, int)} having been
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 75d2da1..e50662a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -140,6 +140,8 @@
 import android.animation.AnimationHandler;
 import android.animation.LayoutTransition;
 import android.annotation.AnyThread;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Size;
@@ -1250,7 +1252,6 @@
         mExtraDisplayListenerLogging = !TextUtils.isEmpty(name) && name.equals(mBasePackageName);
         mThread = Thread.currentThread();
         mLocation = new WindowLeaked(null);
-        mLocation.fillInStackTrace();
         mWidth = -1;
         mHeight = -1;
         mDirty = new Rect();
@@ -2758,6 +2759,9 @@
         // Only call transferFrom if the surface has changed to prevent inc the generation ID and
         // causing EGL resources to be recreated.
         mSurface.transferFrom(blastSurface);
+
+        // Since the SurfaceControl is a VRI, indicate that it can recover from buffer stuffing.
+        mTransaction.setRecoverableFromBufferStuffing(mSurfaceControl).applyAsyncUnsafe();
     }
 
     private void setBoundsLayerCrop(Transaction t) {
@@ -11897,6 +11901,20 @@
     }
 
     /**
+     * {@inheritDoc}
+     */
+    @NonNull
+    @Override
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_JANK_API)
+    public SurfaceControl.OnJankDataListenerRegistration registerOnJankDataListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SurfaceControl.OnJankDataListener listener) {
+        SurfaceControl.OnJankDataListener wrapped = (data) ->
+                executor.execute(() -> listener.onJankDataAvailable(data));
+        return mSurfaceControl.addOnJankDataListener(wrapped);
+    }
+
+    /**
      * Class for managing the accessibility interaction connection
      * based on the global accessibility state.
      */
diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java
index 8bcc9de..12af692 100644
--- a/core/java/android/view/WindowMetrics.java
+++ b/core/java/android/view/WindowMetrics.java
@@ -107,8 +107,8 @@
      * and display cutout areas depending on the calling context and target SDK level. Please refer
      * to {@link Display#getSize(Point)} for details.
      * <p>
-     * The value reported by {@link Display#getSize(Point)} excluding system decoration areas can be
-     * obtained by using:
+     * The following code snippet shows how to get the bounds excluding navigation bars and display
+     * cutout:
      * <pre class="prettyprint">
      * final WindowMetrics metrics = windowManager.getCurrentWindowMetrics();
      * // Gets all excluding insets
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 1a45939..52c5af8 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -25,12 +25,14 @@
 import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
 import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
 import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
+import static android.service.autofill.Flags.FLAG_FILL_DIALOG_IMPROVEMENTS;
 import static android.view.ContentInfo.SOURCE_AUTOFILL;
 import static android.view.autofill.Helper.sDebug;
 import static android.view.autofill.Helper.sVerbose;
 import static android.view.autofill.Helper.toList;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1607,7 +1609,12 @@
      *             the virtual view in the host view.
      *
      * @throws IllegalArgumentException if the {@code infos} was empty
+     *
+     * @deprecated This function will not do anything. Showing fill dialog is now fully controlled
+     * by the framework and the autofill provider.
      */
+    @FlaggedApi(FLAG_FILL_DIALOG_IMPROVEMENTS)
+    @Deprecated
     public void notifyVirtualViewsReady(
             @NonNull View view, @NonNull SparseArray<VirtualViewFillInfo> infos) {
         Objects.requireNonNull(infos);
@@ -4034,8 +4041,13 @@
      *             receiving a focus event. The autofill suggestions shown will include content for
      *             related views as well.
      * @return {@code true} if the autofill dialog is being shown
+     *
+     * @deprecated This function will not do anything. Showing fill dialog is now fully controlled
+     * by the framework and the autofill provider.
      */
     // TODO(b/210926084): Consider whether to include the one-time show logic within this method.
+    @FlaggedApi(FLAG_FILL_DIALOG_IMPROVEMENTS)
+    @Deprecated
     public boolean showAutofillDialog(@NonNull View view) {
         Objects.requireNonNull(view);
         if (shouldShowAutofillDialog(view, view.getAutofillId())) {
@@ -4073,7 +4085,12 @@
      *            suggestions.
      * @param virtualId id identifying the virtual view inside the host view.
      * @return {@code true} if the autofill dialog is being shown
+     *
+     * @deprecated This function will not do anything. Showing fill dialog is now fully controlled
+     * by the framework and the autofill provider.
      */
+    @FlaggedApi(FLAG_FILL_DIALOG_IMPROVEMENTS)
+    @Deprecated
     public boolean showAutofillDialog(@NonNull View view, int virtualId) {
         Objects.requireNonNull(view);
         if (shouldShowAutofillDialog(view, getAutofillId(view, virtualId))) {
diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig
index 658aa29..b180e58 100644
--- a/core/java/android/view/flags/scroll_feedback_flags.aconfig
+++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig
@@ -23,3 +23,10 @@
     bug: "331830899"
     is_fixed_read_only: true
 }
+
+flag {
+    namespace: "wear_frameworks"
+    name: "dynamic_view_rotary_haptics_configuration"
+    description: "Whether ScrollFeedbackProvider dynamically disables View-based rotary haptics."
+    bug: "377998870 "
+}
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 1b86f96..3b6343e 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -132,4 +132,12 @@
     description: "Use refactored round scrollbar."
     bug: "333417898"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "buffer_stuffing_recovery"
+    namespace: "window_surfaces"
+    description: "Recover from buffer stuffing when SurfaceFlinger misses a frame"
+    bug: "294922229"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index fb3e083..a560339 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -24,6 +24,7 @@
 import static android.view.inputmethod.EditorInfoProto.PRIVATE_IME_OPTIONS;
 import static android.view.inputmethod.EditorInfoProto.TARGET_INPUT_METHOD_USER_ID;
 import static android.view.inputmethod.Flags.FLAG_EDITORINFO_HANDWRITING_ENABLED;
+import static android.view.inputmethod.Flags.FLAG_PUBLIC_AUTOFILL_ID_IN_EDITORINFO;
 
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
@@ -470,12 +471,10 @@
     public String packageName;
 
     /**
-     * Autofill Id for the field that's currently on focus.
-     *
-     * <p> Marked as hide since it's only used by framework.</p>
-     * @hide
+     * Autofill Id for the field that's currently on focus. See link {@link AutofillId} for more
+     * details. It is set by {@link View#getAutofillId()}
      */
-    public AutofillId autofillId;
+    private AutofillId autofillId;
 
     /**
      * Identifier for the editor's field.  This is optional, and may be
@@ -1200,6 +1199,28 @@
     }
 
     /**
+     * Returns the {@link AutofillId} of the view that this {@link EditorInfo} is associated with.
+     * The value is filled in with the result of {@link android.view.View#getAutofillId()
+     * View.getAutofillId()} on the view that is being edited.
+     *
+     * Note: For virtual view(e.g. Compose or Webview), default behavior is the autofillId is the id
+     * of the container view, unless the virtual view provider sets the virtual id when the
+     * InputMethodManager calls {@link android.view.View#onCreateInputConnection()} on the container
+     * view.
+     */
+    @FlaggedApi(FLAG_PUBLIC_AUTOFILL_ID_IN_EDITORINFO)
+    @Nullable
+    public AutofillId getAutofillId() {
+        return autofillId;
+    }
+
+    /** Sets the {@link AutofillId} of the view that this {@link EditorInfo} is associated with. */
+    @FlaggedApi(FLAG_PUBLIC_AUTOFILL_ID_IN_EDITORINFO)
+    public void setAutofillId(@Nullable AutofillId autofillId) {
+        this.autofillId = autofillId;
+    }
+
+    /**
      * Export the state of {@link EditorInfo} into a protocol buffer output stream.
      *
      * @param proto Stream to write the state to
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 73f9d9f..6303c76 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2471,6 +2471,11 @@
                 return;
             }
 
+            if (Flags.refactorInsetsController()) {
+                showSoftInput(rootView, statsToken, flags, resultReceiver, reason);
+                return;
+            }
+
             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
 
             // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
@@ -5174,7 +5179,7 @@
         // system can verify the consistency between the uid of this process and package name passed
         // from here. See comment of Context#getOpPackageName() for details.
         editorInfo.packageName = servedView.getContext().getOpPackageName();
-        editorInfo.autofillId = servedView.getAutofillId();
+        editorInfo.setAutofillId(servedView.getAutofillId());
         editorInfo.fieldId = servedView.getId();
         final InputConnection ic = servedView.onCreateInputConnection(editorInfo);
         if (DEBUG) Log.v(TAG, "Starting input: editorInfo=" + editorInfo + " ic=" + ic);
@@ -5183,7 +5188,7 @@
         // This ensures that even disconnected EditorInfos have well-defined attributes,
         // making them consistently and straightforwardly comparable.
         if (ic == null) {
-            editorInfo.autofillId = AutofillId.NO_AUTOFILL_ID;
+            editorInfo.setAutofillId(AutofillId.NO_AUTOFILL_ID);
             editorInfo.fieldId = 0;
         }
         return new Pair<>(ic, editorInfo);
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index edd9d6c..e619ab0 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -165,4 +165,13 @@
     description: "Writing tools API"
     bug: "373788889"
     is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+  name: "public_autofill_id_in_editorinfo"
+  is_exported: true
+  namespace: "input_method"
+  description: "Guarding public API autofillId in editor info"
+  bug: "342672560"
+  is_fixed_read_only: true
+}
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 877fa74..1baf3b8 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -20,6 +20,8 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.graphics.Bitmap;
@@ -552,6 +554,23 @@
      * Parameters used in the {@link #onShowFileChooser} method.
      */
     public static abstract class FileChooserParams {
+        /**
+         * Enable File System Access for webview.
+         *
+         * File System Access JS APIs window.showOpenFileChooser(), showDirectoryChooser(), and
+         * showSaveFilePicker() will invoke #onFileChooser(). Additional MODE_OPEN_FOLDER will be
+         * returned by #getMode(), #getIntent() will return ACTION_OPEN_DOCUMENT,
+         * ACTION_OPEN_DOCUMENT_TREE, and ACTION_CREATE_DOCUMENT rather than the current
+         * ACTION_GET_CONTENT, and new function #getPermissionMode() will be available.
+         *
+         * @hide
+         */
+        @ChangeId
+        @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM)
+        @FlaggedApi(android.webkit.Flags.FLAG_FILE_SYSTEM_ACCESS)
+        @SystemApi
+        public static final long ENABLE_FILE_SYSTEM_ACCESS = 364980165L;
+
         /** @hide */
         @IntDef(prefix = { "MODE_" }, value = {
             MODE_OPEN,
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 392c307..96b9dc7 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -97,3 +97,12 @@
     is_fixed_read_only: true
     bug: "308662081"
 }
+
+flag {
+    name: "jank_api"
+    namespace: "window_surfaces"
+    description: "Adds the jank data listener to AttachedSurfaceControl"
+    is_fixed_read_only: true
+    is_exported: true
+    bug: "293949943"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index d9de38a..4f924a82 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -160,12 +160,21 @@
 }
 
 flag {
-  name: "delegate_unhandled_drags"
-  is_exported: true
-  namespace: "multitasking"
-  description: "Enables delegating unhandled drags to SystemUI"
-  bug: "320797628"
-  is_fixed_read_only: true
+    name: "delegate_unhandled_drags"
+    is_exported: true
+    namespace: "multitasking"
+    description: "Enables delegating unhandled drags to SystemUI"
+    bug: "320797628"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "supports_drag_assistant_to_multiwindow"
+    is_exported: true
+    namespace: "multitasking"
+    description: "Enables support for dragging the assistant into multiwindow"
+    bug: "371206207"
+    is_fixed_read_only: true
 }
 
 flag {
diff --git a/core/java/com/android/internal/app/AppLocaleCollector.java b/core/java/com/android/internal/app/AppLocaleCollector.java
index 56f633f..ca1fc0a 100644
--- a/core/java/com/android/internal/app/AppLocaleCollector.java
+++ b/core/java/com/android/internal/app/AppLocaleCollector.java
@@ -41,7 +41,7 @@
 import java.util.stream.Collectors;
 
 /** The Locale data collector for per-app language. */
-public class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
+public class AppLocaleCollector implements LocaleCollectorBase {
     private static final String TAG = AppLocaleCollector.class.getSimpleName();
     private final Context mContext;
     private final String mAppPackageName;
@@ -167,8 +167,8 @@
     }
 
     @Override
-    public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) {
-        HashSet<String> langTagsToIgnore = new HashSet<>();
+    public Set<String> getIgnoredLocaleList(boolean translatedOnly) {
+        Set<String> langTagsToIgnore = new HashSet<>();
 
         if (mAppCurrentLocale != null) {
             langTagsToIgnore.add(mAppCurrentLocale.getLocale().toLanguageTag());
diff --git a/core/java/com/android/internal/app/LocaleCollectorBase.java b/core/java/com/android/internal/app/LocaleCollectorBase.java
new file mode 100644
index 0000000..f839077
--- /dev/null
+++ b/core/java/com/android/internal/app/LocaleCollectorBase.java
@@ -0,0 +1,36 @@
+/**
+ * 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.internal.app;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * The interface which provides the locale list.
+ */
+public interface LocaleCollectorBase {
+
+    /** Gets the ignored locale list. */
+    Set<String> getIgnoredLocaleList(boolean translatedOnly);
+
+    /** Gets the supported locale list. */
+    Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
+            boolean translatedOnly, boolean isForCountryMode);
+
+    /** Indicates if the class work for specific package. */
+    boolean hasSpecificPackageName();
+}
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index ef4acd1..ffffefa 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -44,7 +44,10 @@
  * <p>It shows suggestions at the top, then the rest of the locales.
  * Allows the user to search for locales using both their native name and their name in the
  * default locale.</p>
+ *
+ * @deprecated use SettingLib's widget instead of customized UIs.
  */
+@Deprecated
 public class LocalePickerWithRegion extends ListFragment implements SearchView.OnQueryTextListener {
     private static final String TAG = LocalePickerWithRegion.class.getSimpleName();
     private static final String PARENT_FRAGMENT_NAME = "localeListEditor";
@@ -78,21 +81,6 @@
         default void onParentLocaleSelected(LocaleStore.LocaleInfo locale) {}
     }
 
-    /**
-     * The interface which provides the locale list.
-     */
-    interface LocaleCollectorBase {
-        /** Gets the ignored locale list. */
-        HashSet<String> getIgnoredLocaleList(boolean translatedOnly);
-
-        /** Gets the supported locale list. */
-        Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
-                boolean translatedOnly, boolean isForCountryMode);
-
-        /** Indicates if the class work for specific package. */
-        boolean hasSpecificPackageName();
-    }
-
     private static LocalePickerWithRegion createNumberingSystemPicker(
             LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
             boolean translatedOnly, OnActionExpandListener onActionExpandListener,
diff --git a/core/java/com/android/internal/app/SystemLocaleCollector.java b/core/java/com/android/internal/app/SystemLocaleCollector.java
index 416f510..c7931cb 100644
--- a/core/java/com/android/internal/app/SystemLocaleCollector.java
+++ b/core/java/com/android/internal/app/SystemLocaleCollector.java
@@ -24,7 +24,7 @@
 import java.util.Set;
 
 /** The Locale data collector for System language. */
-class SystemLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
+public class SystemLocaleCollector implements LocaleCollectorBase {
     private final Context mContext;
     private LocaleList mExplicitLocales;
 
@@ -32,14 +32,14 @@
         this(context, null);
     }
 
-    SystemLocaleCollector(Context context, LocaleList explicitLocales) {
+    public SystemLocaleCollector(Context context, LocaleList explicitLocales) {
         mContext = context;
         mExplicitLocales = explicitLocales;
     }
 
     @Override
-    public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) {
-        HashSet<String> ignoreList = new HashSet<>();
+    public Set<String> getIgnoredLocaleList(boolean translatedOnly) {
+        Set<String> ignoreList = new HashSet<>();
         if (!translatedOnly) {
             final LocaleList userLocales = LocalePicker.getLocales();
             final String[] langTags = userLocales.toLanguageTags().split(",");
diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java
index a21a842..543adac 100644
--- a/core/java/com/android/internal/app/procstats/AssociationState.java
+++ b/core/java/com/android/internal/app/procstats/AssociationState.java
@@ -257,7 +257,6 @@
                         if (VALIDATE_TIMES) {
                             if (mActiveDuration > mAssociationState.mTotalActiveDuration) {
                                 RuntimeException ex = new RuntimeException();
-                                ex.fillInStackTrace();
                                 Slog.w(TAG, "Source act duration " + mActiveDurations
                                         + " exceeds total " + mAssociationState.mTotalActiveDuration
                                         + " in procstate " + mActiveProcState + " in source "
@@ -650,7 +649,6 @@
                         + mySrc.mKey.mProcess + " to assoc " + mName);
                 if ((mySrc.mDuration + otherSrc.mDuration) > mTotalDuration) {
                     RuntimeException ex = new RuntimeException();
-                    ex.fillInStackTrace();
                     Slog.w(TAG, "Source tot duration " + mySrc.mDuration + "+"
                             + otherSrc.mDuration
                             + (newSrc ? " (new)" : " (old)") + " exceeds total "
@@ -665,7 +663,6 @@
                             + mySrc.mKey.mProcess + " to assoc " + mName);
                     if ((mySrc.mActiveDuration + otherSrc.mActiveDuration) > mTotalDuration) {
                         RuntimeException ex = new RuntimeException();
-                        ex.fillInStackTrace();
                         Slog.w(TAG, "Source act duration " + mySrc.mActiveDuration + "+"
                                 + otherSrc.mActiveDuration
                                 + (newSrc ? " (new)" : " (old)") + " exceeds total "
@@ -746,14 +743,12 @@
             if (VALIDATE_TIMES) {
                 if (src.mDuration > mTotalDuration) {
                     RuntimeException ex = new RuntimeException();
-                    ex.fillInStackTrace();
                     Slog.w(TAG, "Reading tot duration " + src.mDuration
                             + " exceeds total " + mTotalDuration + " in source "
                             + src.mKey.mProcess + " to assoc " + mName, ex);
                 }
                 if (src.mActiveDurations == null && src.mActiveDuration > mTotalDuration) {
                     RuntimeException ex = new RuntimeException();
-                    ex.fillInStackTrace();
                     Slog.w(TAG, "Reading act duration " + src.mActiveDuration
                             + " exceeds total " + mTotalDuration + " in source "
                             + src.mKey.mProcess + " to assoc " + mName, ex);
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 0dbdb36..7523a2d 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -538,7 +538,6 @@
     public void incActiveServices(String serviceName) {
         if (DEBUG && "".equals(mName)) {
             RuntimeException here = new RuntimeException("here");
-            here.fillInStackTrace();
             Slog.d(TAG, "incActiveServices: " + this + " service=" + serviceName
                     + " to " + (mNumActiveServices+1), here);
         }
@@ -551,7 +550,6 @@
     public void decActiveServices(String serviceName) {
         if (DEBUG && "".equals(mName)) {
             RuntimeException here = new RuntimeException("here");
-            here.fillInStackTrace();
             Slog.d(TAG, "decActiveServices: " + this + " service=" + serviceName
                     + " to " + (mNumActiveServices-1), here);
         }
@@ -569,7 +567,6 @@
     public void incStartedServices(int memFactor, long now, String serviceName) {
         if (false) {
             RuntimeException here = new RuntimeException("here");
-            here.fillInStackTrace();
             Slog.d(TAG, "incStartedServices: " + this + " service=" + serviceName
                     + " to " + (mNumStartedServices+1), here);
         }
@@ -585,7 +582,6 @@
     public void decStartedServices(int memFactor, long now, String serviceName) {
         if (false) {
             RuntimeException here = new RuntimeException("here");
-            here.fillInStackTrace();
             Slog.d(TAG, "decActiveServices: " + this + " service=" + serviceName
                     + " to " + (mNumStartedServices-1), here);
         }
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 44c0bd0..2834e68 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -139,7 +139,7 @@
         }
 
         static JankInfo createFromSurfaceControlCallback(SurfaceControl.JankData jankStat) {
-            return new JankInfo(jankStat.frameVsyncId).update(jankStat);
+            return new JankInfo(jankStat.getVsyncId()).update(jankStat);
         }
 
         private JankInfo(long frameVsyncId) {
@@ -154,10 +154,10 @@
 
         private JankInfo update(SurfaceControl.JankData jankStat) {
             this.surfaceControlCallbackFired = true;
-            this.jankType = jankStat.jankType;
-            this.refreshRate = DisplayRefreshRate.getRefreshRate(jankStat.frameIntervalNs);
+            this.jankType = jankStat.getJankType();
+            this.refreshRate = DisplayRefreshRate.getRefreshRate(jankStat.getFrameIntervalNanos());
             if (Flags.useSfFrameDuration()) {
-                this.totalDurationNanos = jankStat.actualAppFrameTimeNs;
+                this.totalDurationNanos = jankStat.getActualAppFrameTimeNanos();
             }
             return this;
         }
@@ -458,14 +458,14 @@
                 }
 
                 for (SurfaceControl.JankData jankStat : jankData) {
-                    if (!isInRange(jankStat.frameVsyncId)) {
+                    if (!isInRange(jankStat.getVsyncId())) {
                         continue;
                     }
-                    JankInfo info = findJankInfo(jankStat.frameVsyncId);
+                    JankInfo info = findJankInfo(jankStat.getVsyncId());
                     if (info != null) {
                         info.update(jankStat);
                     } else {
-                        mJankInfos.put((int) jankStat.frameVsyncId,
+                        mJankInfos.put((int) jankStat.getVsyncId(),
                                 JankInfo.createFromSurfaceControlCallback(jankStat));
                     }
                 }
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index 6b6b81f..48d0d6c 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -410,6 +410,11 @@
     private int mLocaleConfigRes;
     private boolean mAllowCrossUidActivitySwitchFromBelow;
 
+    @Nullable
+    private int[] mAlternateLauncherIconResIds;
+    @Nullable
+    private int[] mAlternateLauncherLabelResIds;
+
     private List<AndroidPackageSplit> mSplits;
 
     @NonNull
@@ -874,6 +879,18 @@
         return adoptPermissions;
     }
 
+    @Nullable
+    @Override
+    public int[] getAlternateLauncherIconResIds() {
+        return mAlternateLauncherIconResIds;
+    }
+
+    @Nullable
+    @Override
+    public int[] getAlternateLauncherLabelResIds() {
+        return mAlternateLauncherLabelResIds;
+    }
+
     @NonNull
     @Override
     public List<ParsedApexSystemService> getApexSystemServices() {
@@ -1888,6 +1905,19 @@
     }
 
     @Override
+    public PackageImpl setAlternateLauncherIconResIds(@Nullable int[] alternateLauncherIconResIds) {
+        this.mAlternateLauncherIconResIds = alternateLauncherIconResIds;
+        return this;
+    }
+
+    @Override
+    public PackageImpl setAlternateLauncherLabelResIds(
+            @Nullable int[] alternateLauncherLabelResIds) {
+        this.mAlternateLauncherLabelResIds = alternateLauncherLabelResIds;
+        return this;
+    }
+
+    @Override
     public PackageImpl setTaskReparentingAllowed(boolean value) {
         return setBoolean(Booleans.ALLOW_TASK_REPARENTING, value);
     }
@@ -3273,6 +3303,8 @@
         dest.writeLong(this.mBooleans2);
         dest.writeBoolean(this.mAllowCrossUidActivitySwitchFromBelow);
         dest.writeInt(this.mIntentMatchingFlags);
+        dest.writeIntArray(this.mAlternateLauncherIconResIds);
+        dest.writeIntArray(this.mAlternateLauncherLabelResIds);
     }
 
     private void writeFeatureFlagState(@NonNull Parcel dest) {
@@ -3465,6 +3497,8 @@
         this.mBooleans2 = in.readLong();
         this.mAllowCrossUidActivitySwitchFromBelow = in.readBoolean();
         this.mIntentMatchingFlags = in.readInt();
+        this.mAlternateLauncherIconResIds = in.createIntArray();
+        this.mAlternateLauncherLabelResIds = in.createIntArray();
 
         assignDerivedFields();
         assignDerivedFields2();
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index f4bceb8..67b985a 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -413,6 +413,18 @@
 
     ParsingPackage setOnBackInvokedCallbackEnabled(boolean enableOnBackInvokedCallback);
 
+    /**
+     * Set the drawable resources id array of the alternate icons that are parsing from the
+     * AndroidManifest file
+     */
+    ParsingPackage setAlternateLauncherIconResIds(int[] alternateLauncherIconResIds);
+
+    /**
+     * Set the string resources id array of the alternate labels that are parsing from the
+     * AndroidManifest file
+     */
+    ParsingPackage setAlternateLauncherLabelResIds(int[] alternateLauncherLabelResIds);
+
     @CallSuper
     ParsedPackage hideAsParsed();
 
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 5db7b41..8a6e6be 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -46,6 +46,7 @@
 import android.content.pm.ConfigurationInfo;
 import android.content.pm.FeatureGroupInfo;
 import android.content.pm.FeatureInfo;
+import android.content.pm.Flags;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.Property;
@@ -154,6 +155,13 @@
 
     private static final String TAG = ParsingUtils.TAG;
 
+    // It is the maximum length of the typedArray of {@link android.R.attr#alternateIcons}
+    // and {@link android.R.attr#alternateLabels}.
+    private static final int MAXIMUM_LAUNCHER_ALTERNATE_IDS_LENGTH = 500;
+
+    private static final String TYPE_STRING = "string";
+    private static final String TYPE_DRAWABLE = "drawable";
+
     public static final boolean DEBUG_JAR = false;
     public static final boolean DEBUG_BACKUP = false;
     public static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f;
@@ -2021,6 +2029,24 @@
                 pkg.setManageSpaceActivityName(manageSpaceActivityName);
             }
 
+            if (Flags.changeLauncherBadging()) {
+                ParseResult<int[]> result = drawableResIdArray(input, sa, res,
+                        R.styleable.AndroidManifestApplication_alternateLauncherIcons,
+                        MAXIMUM_LAUNCHER_ALTERNATE_IDS_LENGTH);
+                if (result.isError()) {
+                    return input.error(result);
+                }
+                pkg.setAlternateLauncherIconResIds(result.getResult());
+
+                result = stringResIdArray(input, sa, res,
+                        R.styleable.AndroidManifestApplication_alternateLauncherLabels,
+                        MAXIMUM_LAUNCHER_ALTERNATE_IDS_LENGTH);
+                if (result.isError()) {
+                    return input.error(result);
+                }
+                pkg.setAlternateLauncherLabelResIds(result.getResult());
+            }
+
             if (pkg.isBackupAllowed()) {
                 // backupAgent, killAfterRestore, fullBackupContent, backupInForeground,
                 // and restoreAnyVersion are only relevant if backup is possible for the
@@ -3395,6 +3421,95 @@
         return sa.getResourceId(attribute, 0);
     }
 
+    /**
+     * Parse the drawable resource id array in the typed array {@code resourceId}
+     * if available. If {@code maxSize} is not zero, only parse and preserve at most
+     * {@code maxSize} ids.
+     */
+    private static ParseResult<int[]> drawableResIdArray(ParseInput input, @NonNull TypedArray sa,
+            @NonNull Resources res, int resourceId, int maxSize) {
+        return resIdArray(input, sa, res, resourceId, TYPE_DRAWABLE, maxSize);
+    }
+
+    /**
+     * Parse the string resource id array in the typed array {@code resourceId}
+     * if available. If {@code maxSize} is not zero, only parse and preserve at most
+     * {@code maxSize} ids.
+     */
+    private static ParseResult<int[]> stringResIdArray(ParseInput input, @NonNull TypedArray sa,
+            @NonNull Resources res, int resourceId, int maxSize) {
+        return resIdArray(input, sa, res, resourceId, TYPE_STRING, maxSize);
+    }
+
+    /**
+     * Parse the resource id array in the typed array {@code resourceId}
+     * if available. If {@code maxSize} is larger than zero, only parse and preserve
+     * at most {@code maxSize} ids that type is matched to the {@code expectedTypeName}.
+     * Because the TypedArray allows mixed types in an array, if {@code expectedTypeName}
+     * is null, it means don't check the type.
+     */
+    private static ParseResult<int[]> resIdArray(ParseInput input, @NonNull TypedArray sa,
+            @NonNull Resources res, int resourceId, @Nullable String expectedTypeName,
+            int maxSize) {
+        if (!sa.hasValue(resourceId)) {
+            return input.success(null);
+        }
+
+        final int typeArrayResId = sa.getResourceId(resourceId, /* defValue= */ 0);
+        if (typeArrayResId == 0) {
+            return input.success(null);
+        }
+
+        // Parse the typedArray
+        try (TypedArray typedArray = res.obtainTypedArray(typeArrayResId)) {
+            final String typedArrayName = res.getResourceName(typeArrayResId);
+            final int length = typedArray.length();
+            if (maxSize > 0 && length > maxSize) {
+                return input.error(TextUtils.formatSimple(
+                        "The length of the typedArray (%s) is larger than %d.",
+                        typedArrayName, maxSize));
+            }
+            Set<Integer> resourceIdSet = new ArraySet<>();
+            for (int i = 0; i < length; i++) {
+                final int id = typedArray.getResourceId(i, /* defValue= */ 0);
+                // Add the id when the conditions are all matched:
+                // 1. The resource Id is not 0
+                // 2. The type is the expected type
+                // 3. The id is not duplicated
+                if (id == 0) {
+                    return input.error(TextUtils.formatSimple(
+                            "There is an item that is not a resource id in the typedArray (%s).",
+                            typedArrayName));
+                }
+
+                try {
+                    if (resourceIdSet.contains(id)) {
+                        return input.error(TextUtils.formatSimple(
+                                "There is a duplicated resource (%s) in the typedArray (%s).",
+                                res.getResourceName(id), typedArrayName));
+                    }
+                    final String typeName = res.getResourceTypeName(id);
+                    if (expectedTypeName != null
+                            && !TextUtils.equals(typeName, expectedTypeName)) {
+                        return input.error(TextUtils.formatSimple(
+                                "There is a resource (%s) in the typedArray (%s) that is not a"
+                                        + " %s type.", res.getResourceName(id), typedArrayName,
+                                expectedTypeName));
+                    }
+                } catch (Resources.NotFoundException e) {
+                    return input.error(TextUtils.formatSimple(
+                            "There is a resource in the typedArray (%s) that is not found in"
+                                    + " the app resources.", typedArrayName));
+                }
+                resourceIdSet.add(id);
+            }
+            if (resourceIdSet.isEmpty()) {
+                return input.success(null);
+            }
+            return input.success(resourceIdSet.stream().mapToInt(i -> i).toArray());
+        }
+    }
+
     private static String string(@StyleableRes int attribute, TypedArray sa) {
         return sa.getString(attribute);
     }
diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index 5350059..d05f5e3 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -91,6 +91,28 @@
 public interface AndroidPackage {
 
     /**
+     * An array containing the drawable resources that used for the launcher
+     * activity icons.
+     *
+     * @see R.attr#alternateLauncherIcons
+     * @hide
+     */
+    @Immutable.Ignore
+    @Nullable
+    int[] getAlternateLauncherIconResIds();
+
+    /**
+     * An array containing the string resources that used for the launcher
+     * activity labels.
+     *
+     * @see R.attr#alternateLauncherLabels
+     * @hide
+     */
+    @Immutable.Ignore
+    @Nullable
+    int[] getAlternateLauncherLabelResIds();
+
+    /**
      * @see ApplicationInfo#className
      * @see R.styleable#AndroidManifestApplication_name
      */
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index f007cc5..a09c405 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -67,6 +67,7 @@
         jfieldID preferredFrameTimelineIndex;
         jfieldID frameTimelinesLength;
         jfieldID frameTimelines;
+        jfieldID numberQueuedBuffers;
     } vsyncEventDataClassInfo;
 
 } gDisplayEventReceiverClassInfo;
@@ -165,7 +166,8 @@
     return env->NewObject(gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
                           gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.init,
                           frameTimelineObjs.get(), vsyncEventData.preferredFrameTimelineIndex,
-                          vsyncEventData.frameTimelinesLength, vsyncEventData.frameInterval);
+                          vsyncEventData.frameTimelinesLength, vsyncEventData.frameInterval,
+                          vsyncEventData.numberQueuedBuffers);
 }
 
 void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId,
@@ -188,6 +190,9 @@
         env->SetLongField(vsyncEventDataObj.get(),
                           gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval,
                           vsyncEventData.frameInterval);
+        env->SetIntField(vsyncEventDataObj.get(),
+                         gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.numberQueuedBuffers,
+                         vsyncEventData.numberQueuedBuffers);
 
         ScopedLocalRef<jobjectArray>
                 frameTimelinesObj(env,
@@ -441,7 +446,7 @@
             GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
                              "<init>",
                              "([Landroid/view/"
-                             "DisplayEventReceiver$VsyncEventData$FrameTimeline;IIJ)V");
+                             "DisplayEventReceiver$VsyncEventData$FrameTimeline;IIJI)V");
 
     gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.preferredFrameTimelineIndex =
             GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
@@ -456,6 +461,9 @@
             GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
                             "frameTimelines",
                             "[Landroid/view/DisplayEventReceiver$VsyncEventData$FrameTimeline;");
+    gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.numberQueuedBuffers =
+            GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
+                            "numberQueuedBuffers", "I");
 
     return res;
 }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index d3bf36e..82b463e 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -2163,7 +2163,7 @@
 
 class JankDataListenerWrapper : public JankDataListener {
 public:
-    JankDataListenerWrapper(JNIEnv* env, jobject onJankDataListenerObject) {
+    JankDataListenerWrapper(JNIEnv* env, jobject onJankDataListenerObject) : mRemovedVsyncId(-1) {
         mOnJankDataListenerWeak = env->NewWeakGlobalRef(onJankDataListenerObject);
         env->GetJavaVM(&mVm);
     }
@@ -2174,6 +2174,12 @@
     }
 
     bool onJankDataAvailable(const std::vector<gui::JankData>& jankData) override {
+        // Don't invoke the listener if we've been force removed and got this
+        // out-of-order callback.
+        if (mRemovedVsyncId == 0) {
+            return false;
+        }
+
         JNIEnv* env = getEnv();
 
         jobject target = env->NewLocalRef(mOnJankDataListenerWeak);
@@ -2181,9 +2187,29 @@
             return false;
         }
 
-        jobjectArray jJankDataArray = env->NewObjectArray(jankData.size(),
-                gJankDataClassInfo.clazz, nullptr);
-        for (size_t i = 0; i < jankData.size(); i++) {
+        // Compute the count of data items we'll actually forward to Java.
+        size_t count = 0;
+        if (mRemovedVsyncId <= 0) {
+            count = jankData.size();
+        } else {
+            for (const gui::JankData& frame : jankData) {
+                if (frame.frameVsyncId <= mRemovedVsyncId) {
+                    count++;
+                }
+            }
+        }
+
+        if (count == 0) {
+            return false;
+        }
+
+        jobjectArray jJankDataArray = env->NewObjectArray(count, gJankDataClassInfo.clazz, nullptr);
+        for (size_t i = 0, j = 0; i < jankData.size() && j < count; i++) {
+            // Filter any data for frames past our removal vsync.
+            if (mRemovedVsyncId > 0 && jankData[i].frameVsyncId > mRemovedVsyncId) {
+                continue;
+            }
+
             // The exposed constants in SurfaceControl are simplified, so we need to translate the
             // jank type we get from SF to what is exposed in Java.
             int sfJankType = jankData[i].jankType;
@@ -2210,7 +2236,7 @@
                                    jankData[i].frameVsyncId, javaJankType,
                                    jankData[i].frameIntervalNs, jankData[i].scheduledAppFrameTimeNs,
                                    jankData[i].actualAppFrameTimeNs);
-            env->SetObjectArrayElement(jJankDataArray, i, jJankData);
+            env->SetObjectArrayElement(jJankDataArray, j++, jJankData);
             env->DeleteLocalRef(jJankData);
         }
 
@@ -2225,6 +2251,11 @@
         return true;
     }
 
+    void removeListener(int64_t afterVsyncId) {
+        mRemovedVsyncId = (afterVsyncId <= 0) ? 0 : afterVsyncId;
+        JankDataListener::removeListener(afterVsyncId);
+    }
+
 private:
 
     JNIEnv* getEnv() {
@@ -2235,6 +2266,7 @@
 
     JavaVM* mVm;
     jobject mOnJankDataListenerWeak;
+    int64_t mRemovedVsyncId;
 };
 
 static jlong nativeCreateJankDataListenerWrapper(JNIEnv* env, jclass clazz,
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.cpp b/core/jni/com_android_internal_content_FileSystemUtils.cpp
index 6c72544..76ead2a 100644
--- a/core/jni/com_android_internal_content_FileSystemUtils.cpp
+++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp
@@ -22,7 +22,6 @@
 #include <android-base/hex.h>
 #include <android-base/unique_fd.h>
 #include <bionic/macros.h>
-#include <elf.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
@@ -204,7 +203,8 @@
     return true;
 }
 
-bool punchHolesInElf64(const char *filePath, const uint64_t offset) {
+bool getLoadSegmentPhdrs(const char *filePath, const uint64_t offset,
+                         std::vector<Elf64_Phdr> &programHeaders) {
     // Open Elf file
     Elf64_Ehdr ehdr;
     std::ifstream inputStream(filePath, std::ifstream::in);
@@ -227,11 +227,6 @@
     uint64_t programHeaderOffset = ehdr.e_phoff;
     uint16_t programHeaderNum = ehdr.e_phnum;
 
-    IF_ALOGD() {
-        ALOGD("Punching holes in file: %s programHeaderOffset: %" PRIu64 " programHeaderNum: %hu",
-              filePath, programHeaderOffset, programHeaderNum);
-    }
-
     // if this is a zip file, also consider elf offset inside a file
     uint64_t phOffset;
     if (__builtin_add_overflow(offset, programHeaderOffset, &phOffset)) {
@@ -240,7 +235,6 @@
     }
     inputStream.seekg(phOffset);
 
-    std::vector<Elf64_Phdr> programHeaders;
     for (int headerIndex = 0; headerIndex < programHeaderNum; headerIndex++) {
         Elf64_Phdr header;
         inputStream.read((char *)&header, sizeof(header));
@@ -254,6 +248,15 @@
         programHeaders.push_back(header);
     }
 
+    return true;
+}
+
+bool punchHolesInElf64(const char *filePath, const uint64_t offset) {
+    std::vector<Elf64_Phdr> programHeaders;
+    if (!getLoadSegmentPhdrs(filePath, offset, programHeaders)) {
+        ALOGE("Failed to read program headers from ELF file.");
+        return false;
+    }
     return punchHoles(filePath, offset, programHeaders);
 }
 
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.h b/core/jni/com_android_internal_content_FileSystemUtils.h
index 52445e2..4a95686c 100644
--- a/core/jni/com_android_internal_content_FileSystemUtils.h
+++ b/core/jni/com_android_internal_content_FileSystemUtils.h
@@ -15,8 +15,11 @@
  */
 #pragma once
 
+#include <elf.h>
 #include <sys/types.h>
 
+#include <vector>
+
 namespace android {
 
 /*
@@ -35,4 +38,11 @@
  */
 bool punchHolesInZip(const char* filePath, uint64_t offset, uint16_t extraFieldLen);
 
+/*
+ * This function reads program headers from ELF file. ELF can be specified with file path directly
+ * or it should be at offset inside Apk. Program headers passed to function is populated.
+ */
+bool getLoadSegmentPhdrs(const char* filePath, const uint64_t offset,
+                         std::vector<Elf64_Phdr>& programHeaders);
+
 } // namespace android
\ No newline at end of file
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dc054a4..969ee2e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1849,6 +1849,10 @@
         <item>-1</item>
     </integer-array>
 
+     <!-- Specifies the delay in milliseconds after the last user input before turning off the
+          keyboard backlight.
+    -->
+    <integer name="config_keyboardBacklightTimeoutMs">30000</integer>
     <!-- An array describing the screen's backlight values corresponding to the brightness
          values in the config_screenBrightnessNits array.
 
diff --git a/core/res/res/values/stoppable_fgs_system_apps.xml b/core/res/res/values/stoppable_fgs_system_apps.xml
index 165ff61..06843f4 100644
--- a/core/res/res/values/stoppable_fgs_system_apps.xml
+++ b/core/res/res/values/stoppable_fgs_system_apps.xml
@@ -19,6 +19,7 @@
 <resources>
     <!-- A list of system apps whose FGS can be stopped in the task manager. -->
     <string-array translatable="false" name="stoppable_fgs_system_apps">
+        <item>com.android.virtualization.terminal</item>
     </string-array>
     <!-- stoppable_fgs_system_apps which is supposed to be overridden by vendor -->
     <string-array translatable="false" name="vendor_stoppable_fgs_system_apps">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index badb986..9dd3027 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2098,6 +2098,7 @@
   <java-symbol type="integer" name="config_autoBrightnessDarkeningLightDebounce"/>
   <java-symbol type="integer" name="config_autoBrightnessInitialLightSensorRate"/>
   <java-symbol type="integer" name="config_autoBrightnessLightSensorRate"/>
+  <java-symbol type="integer" name="config_keyboardBacklightTimeoutMs" />
   <java-symbol type="integer" name="config_carDockKeepsScreenOn" />
   <java-symbol type="integer" name="config_criticalBatteryWarningLevel" />
   <java-symbol type="integer" name="config_datause_notification_type" />
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
index 1721e1e..9dd196d 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
@@ -79,7 +79,7 @@
         TEST_EDITOR_INFO.label = "testLabel";
         TEST_EDITOR_INFO.packageName = "android.view.inputmethod";
         TEST_EDITOR_INFO.fieldId = 0;
-        TEST_EDITOR_INFO.autofillId = AutofillId.NO_AUTOFILL_ID;
+        TEST_EDITOR_INFO.setAutofillId(AutofillId.NO_AUTOFILL_ID);
         TEST_EDITOR_INFO.fieldName = "testField";
         TEST_EDITOR_INFO.extras = new Bundle();
         TEST_EDITOR_INFO.extras.putString("testKey", "testValue");
@@ -531,7 +531,7 @@
             info.setStylusHandwritingEnabled(true);
         }
         info.packageName = "android.view.inputmethod";
-        info.autofillId = new AutofillId(123);
+        info.setAutofillId(new AutofillId(123));
         info.fieldId = 456;
         info.fieldName = "testField";
         info.extras = new Bundle();
@@ -597,7 +597,7 @@
     @Test
     public void testKindofEqualsComparesAutofillId() {
         final EditorInfo infoCopy = TEST_EDITOR_INFO.createCopyInternal();
-        infoCopy.autofillId = new AutofillId(42);
+        infoCopy.setAutofillId(new AutofillId(42));
         assertFalse(TEST_EDITOR_INFO.kindofEquals(infoCopy));
     }
 
diff --git a/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java b/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java
index 232abe2..7f069ad 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java
@@ -22,7 +22,7 @@
 import android.test.AndroidTestCase;
 import android.test.PerformanceTestCase;
 
-public class ActivityTestsBase extends AndroidTestCase 
+public class ActivityTestsBase extends AndroidTestCase
         implements PerformanceTestCase, LaunchpadActivity.CallingTest {
     public static final String PERMISSION_GRANTED =
             "com.android.frameworks.coretests.permission.TEST_GRANTED";
@@ -111,7 +111,6 @@
 
     public void finishWithResult(int resultCode, Intent data) {
         RuntimeException where = new RuntimeException("Original error was here");
-        where.fillInStackTrace();
         finishWithResult(resultCode, data, where);
     }
 
@@ -194,15 +193,15 @@
     public int getResultCode() {
         return mResultCode;
     }
-    
+
     public Intent getResultData() {
         return mData;
     }
-    
+
     public RuntimeException getResultStack() {
         return mResultStack;
     }
-    
+
     public void onTimeout() {
         String msg = mExpecting == null
                 ? "Timeout" : ("Timeout while expecting " + mExpecting);
diff --git a/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java b/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java
index fda249f..9b358e0 100644
--- a/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java
+++ b/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java
@@ -461,7 +461,6 @@
         mResultCode = resultCode;
         mData = data;
         mResultStack = new RuntimeException("Original error was here");
-        mResultStack.fillInStackTrace();
     }
 
     private void registerMyReceiver(IntentFilter filter) {
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
index a6de611..8969b2b 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
+++ b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
@@ -16,7 +16,10 @@
 
 package android.hardware.display
 
+import android.graphics.PointF
+import android.graphics.RectF
 import android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_LEFT
 import android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP
 import android.hardware.display.DisplayTopology.TreeNode.POSITION_RIGHT
 import android.view.Display
@@ -469,4 +472,205 @@
         assertThat(actualDisplay4.offset).isEqualTo(-400f)
         assertThat(actualDisplay4.children).isEmpty()
     }
-}
\ No newline at end of file
+
+    @Test
+    fun rearrange_twoDisplays() {
+        val nodes = rearrangeRects(
+            // Arrange in staggered manner, connected vertically.
+            RectF(100f, 100f, 250f, 200f),
+            RectF(150f, 200f, 300f, 300f),
+        )
+
+        assertThat(nodes[0].children).containsExactly(nodes[1])
+        assertThat(nodes[1].children).isEmpty()
+        assertPositioning(nodes, Pair(POSITION_BOTTOM, 50f))
+    }
+
+    @Test
+    fun rearrange_reverseOrderOfSeveralDisplays() {
+        val nodes = rearrangeRects(
+            RectF(0f, 0f, 150f, 100f),
+            RectF(-150f, 0f, 0f, 100f),
+            RectF(-300f, 0f, -150f, 100f),
+            RectF(-450f, 0f, -300f, 100f),
+        )
+
+        assertPositioning(
+            nodes,
+            Pair(POSITION_LEFT, 0f),
+            Pair(POSITION_LEFT, 0f),
+            Pair(POSITION_LEFT, 0f),
+        )
+
+        assertThat(nodes[0].children).containsExactly(nodes[1])
+        assertThat(nodes[1].children).containsExactly(nodes[2])
+        assertThat(nodes[2].children).containsExactly(nodes[3])
+        assertThat(nodes[3].children).isEmpty()
+    }
+
+    @Test
+    fun rearrange_crossWithRootInCenter() {
+        val nodes = rearrangeRects(
+            RectF(0f, 0f, 150f, 100f),
+            RectF(-150f, 0f, 0f, 100f),
+            RectF(0f,-100f, 150f, 0f),
+            RectF(150f, 0f, 300f, 100f),
+            RectF(0f, 100f, 150f, 200f),
+        )
+
+        assertPositioning(
+            nodes,
+            Pair(POSITION_LEFT, 0f),
+            Pair(POSITION_TOP, 0f),
+            Pair(POSITION_RIGHT, 0f),
+            Pair(POSITION_BOTTOM, 0f),
+        )
+
+        assertThat(nodes[0].children)
+            .containsExactly(nodes[1], nodes[2], nodes[3], nodes[4])
+    }
+
+    @Test
+    fun rearrange_elbowArrangementDoesNotUseCornerAdjacency1() {
+        val nodes = rearrangeRects(
+            //     2
+            //     |
+            // 0 - 1
+
+            RectF(0f, 0f, 100f, 100f),
+            RectF(100f, 0f, 200f, 100f),
+            RectF(100f, -100f, 200f, 0f),
+        )
+
+        assertThat(nodes[0].children).containsExactly(nodes[1])
+        assertThat(nodes[1].children).containsExactly(nodes[2])
+        assertThat(nodes[2].children).isEmpty()
+
+        assertPositioning(
+            nodes,
+            Pair(POSITION_RIGHT, 0f),
+            Pair(POSITION_TOP, 0f),
+        )
+    }
+
+    @Test
+    fun rearrange_elbowArrangementDoesNotUseCornerAdjacency2() {
+        val nodes = rearrangeRects(
+            //     0
+            //     |
+            //     1
+            //     |
+            // 3 - 2
+
+            RectF(0f, 0f, 100f, 100f),
+            RectF(0f, 100f, 100f, 200f),
+            RectF(0f, 200f, 100f, 300f),
+            RectF(-100f, 200f, 0f, 300f),
+        )
+
+        assertThat(nodes[0].children).containsExactly(nodes[1])
+        assertThat(nodes[1].children).containsExactly(nodes[2])
+        assertThat(nodes[2].children).containsExactly(nodes[3])
+        assertThat(nodes[3].children).isEmpty()
+
+        assertPositioning(
+            nodes,
+            Pair(POSITION_BOTTOM, 0f),
+            Pair(POSITION_BOTTOM, 0f),
+            Pair(POSITION_LEFT, 0f),
+        )
+    }
+
+    @Test
+    fun rearrange_useLargerEdge() {
+        val nodes = rearrangeRects(
+            //444111
+            //444111
+            //444111
+            //  000222
+            //  000222
+            //  000222
+            //    333
+            //    333
+            //    333
+            RectF(20f, 30f, 50f, 60f),
+            RectF(30f, 0f, 60f, 30f),
+            RectF(50f, 30f, 80f, 60f),
+            RectF(40f, 60f, 70f, 90f),
+            RectF(0f, 0f, 30f, 30f),
+        )
+
+        assertPositioning(
+            nodes,
+            Pair(POSITION_TOP, 10f),
+            Pair(POSITION_RIGHT, 0f),
+            Pair(POSITION_BOTTOM, -10f),
+            Pair(POSITION_LEFT, 0f),
+        )
+
+        assertThat(nodes[0].children).containsExactly(nodes[1], nodes[2])
+        assertThat(nodes[1].children).containsExactly(nodes[4])
+        assertThat(nodes[2].children).containsExactly(nodes[3])
+        (3..4).forEach { assertThat(nodes[it].children).isEmpty() }
+    }
+
+    @Test
+    fun rearrange_closeGaps() {
+        val nodes = rearrangeRects(
+            //000
+            //000 111
+            //000 111
+            //    111
+            //
+            //        222
+            //        222
+            //        222
+            RectF(0f, 0f, 30f, 30f),
+            RectF(40f, 10f, 70f, 40f),
+            RectF(80.5f, 50f, 110f, 80f),  // left+=0.5 to cause a preference for TOP/BOTTOM attach
+        )
+
+        assertPositioning(
+            nodes,
+            // In the case of corner adjacency, we prefer a left/right attachment.
+            Pair(POSITION_RIGHT, 10f),
+            Pair(POSITION_BOTTOM, 40.5f),  // TODO: fix implementation to remove this gap
+        )
+
+        assertThat(nodes[0].children).containsExactly(nodes[1])
+        assertThat(nodes[1].children).containsExactly(nodes[2])
+        assertThat(nodes[2].children).isEmpty()
+    }
+
+    /**
+     * Runs the rearrange algorithm and returns the resulting tree as a list of nodes, with the
+     * root at index 0. The number of nodes is inferred from the number of positions passed.
+     */
+    private fun rearrangeRects(vararg pos : RectF) : List<DisplayTopology.TreeNode> {
+        // Generates a linear sequence of nodes in order in the List from root to leaf,
+        // left-to-right. IDs are ascending from 0 to count - 1.
+
+        val nodes = pos.indices.map {
+            DisplayTopology.TreeNode(it, pos[it].width(), pos[it].height(), POSITION_RIGHT, 0f)
+        }
+
+        nodes.forEachIndexed { id, node ->
+            if (id > 0) {
+                nodes[id - 1].addChild(node)
+            }
+        }
+
+        DisplayTopology(nodes[0], 0).rearrange(pos.indices.associateWith {
+            PointF(pos[it].left, pos[it].top)
+        })
+
+        return nodes
+    }
+
+    private fun assertPositioning(
+            nodes : List<DisplayTopology.TreeNode>, vararg positions : Pair<Int, Float>) {
+        assertThat(nodes.drop(1).map { Pair(it.position, it.offset )})
+            .containsExactly(*positions)
+            .inOrder()
+    }
+}
diff --git a/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
index ac6c19e..66cf9c7 100644
--- a/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
+++ b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED;
+import static android.view.flags.Flags.FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION;
 import static android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS;
 import static android.view.HapticFeedbackConstants.SCROLL_LIMIT;
 import static android.view.HapticFeedbackConstants.SCROLL_TICK;
@@ -74,12 +75,13 @@
 
         mView = new TestView(InstrumentationRegistry.getContext());
         mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
-                /* disabledIfViewPlaysScrollHaptics= */ true);
+                /* isFromView= */ false);
         mSetFlagsRule.disableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
     }
 
     @Test
     public void testRotaryEncoder_noFeedbackWhenViewBasedFeedbackIsEnabled() {
+        mSetFlagsRule.disableFlags(FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION);
         when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
                 .thenReturn(true);
         setHapticScrollTickInterval(5);
@@ -97,7 +99,24 @@
     }
 
     @Test
+    public void testRotaryEncoder_dynamicViewRotaryFeedback_enabledEvenWhenViewFeedbackIsEnabled() {
+        mSetFlagsRule.enableFlags(FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION);
+        when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
+                .thenReturn(true);
+        setHapticScrollTickInterval(5);
+        mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
+                /* isFromView= */ false);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 10);
+
+        assertFeedbackCount(mView, SCROLL_TICK, 1);
+    }
+
+    @Test
     public void testRotaryEncoder_inputDeviceCustomized_noFeedbackWhenViewBasedFeedbackIsEnabled() {
+        mSetFlagsRule.disableFlags(FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION);
         mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
 
         when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
@@ -119,7 +138,7 @@
     @Test
     public void testRotaryEncoder_feedbackWhenDisregardingViewBasedScrollHaptics() {
         mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
-                /* disabledIfViewPlaysScrollHaptics= */ false);
+                /* isFromView= */ true);
         when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
                 .thenReturn(true);
         setHapticScrollTickInterval(5);
@@ -144,7 +163,7 @@
         List<HapticFeedbackRequest> requests = new ArrayList<>();
 
         mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
-                /* disabledIfViewPlaysScrollHaptics= */ false);
+                /* isFromView= */ true);
         when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
                 .thenReturn(true);
         setHapticScrollTickInterval(5);
@@ -917,19 +936,20 @@
 
     @Test
     public void testNonRotaryInputFeedbackNotBlockedByRotaryUnavailability() {
+        mSetFlagsRule.disableFlags(FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION);
         when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
                 .thenReturn(true);
         setHapticScrollFeedbackEnabled(true);
         setHapticScrollTickInterval(5);
         mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
-                /* disabledIfViewPlaysScrollHaptics= */ true);
+                /* isFromView= */ false);
 
         // Expect one feedback here. Touch input should provide feedback since scroll feedback has
         // been enabled via `setHapticScrollFeedbackEnabled(true)`.
         mProvider.onScrollProgress(
                 INPUT_DEVICE_1, InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.AXIS_Y,
                 /* deltaInPixels= */ 10);
-        // Because `isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()` is false and
+        // Because `isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()` is true and
         // `disabledIfViewPlaysScrollHaptics` is true, the scroll progress from rotary encoders will
         // produce no feedback.
         mProvider.onScrollProgress(
diff --git a/core/tests/coretests/src/android/view/RotaryScrollHapticsTest.java b/core/tests/coretests/src/android/view/RotaryScrollHapticsTest.java
index 9a5c1c5..b1a5637 100644
--- a/core/tests/coretests/src/android/view/RotaryScrollHapticsTest.java
+++ b/core/tests/coretests/src/android/view/RotaryScrollHapticsTest.java
@@ -30,8 +30,12 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.Nullable;
 import android.content.Context;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.view.flags.Flags;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -39,6 +43,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -48,6 +53,8 @@
 @RunWith(AndroidJUnit4.class)
 @Presubmit
 public final class RotaryScrollHapticsTest {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final int TEST_ROTARY_DEVICE_ID = 1;
     private static final int TEST_RANDOM_DEVICE_ID = 2;
 
@@ -167,6 +174,26 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION)
+    public void testChildViewImplementationUsesScrollFeedbackProvider_doesNoScrollFeedback() {
+        mView.configureGenericMotion(/* result= */ false, /* scroll= */ true);
+        mView.mUsesCustomScrollFeedbackProvider = true;
+
+        // Send multiple generic motion events, to catch bugs where the behavior is WAI only for the
+        // first dispatch, but buggy for future calls.
+        mView.dispatchGenericMotionEvent(createRotaryEvent(20));
+        mView.dispatchGenericMotionEvent(createRotaryEvent(10));
+        mView.dispatchGenericMotionEvent(createRotaryEvent(30));
+
+        // Verify that the base View class's ScrollFeedbackProvider produces no scroll progress
+        // or limit events, because there's a custom ScrollFeedbackProvider used by the child
+        // View class implementation, which should hint the base View class to disable its own
+        // ScrollFeedbackProvider usage.
+        verifyNoScrollProgress();
+        verifyNoScrollLimit();
+    }
+
+    @Test
     public void testScrollProgress_genericMotionEventCallbackReturningTrue_doesScrollProgress() {
         mView.configureGenericMotion(/* result= */ true, /* scroll= */ true);
 
@@ -208,6 +235,9 @@
     private static final class TestGenericMotionEventControllingView extends View {
         private boolean mGenericMotionResult;
         private boolean mScrollOnGenericMotion;
+        private boolean mUsesCustomScrollFeedbackProvider = false;
+
+        @Nullable private ScrollFeedbackProvider mCustomScrollFeedbackProvider;
 
         TestGenericMotionEventControllingView(Context context) {
             super(context);
@@ -222,6 +252,19 @@
         public boolean onGenericMotionEvent(MotionEvent event) {
             if (mScrollOnGenericMotion) {
                 scrollTo(100, 200); // scroll values random (not relevant for tests).
+                if (mUsesCustomScrollFeedbackProvider) {
+                    // Mimic how a real child class of View would instantiate and use the
+                    // ScrollFeedbackProvider API.
+                    if (mCustomScrollFeedbackProvider == null) {
+                        mCustomScrollFeedbackProvider = ScrollFeedbackProvider.createProvider(this);
+                    }
+                    float axisScrollValue = event.getAxisValue(AXIS_SCROLL);
+                    mCustomScrollFeedbackProvider.onScrollProgress(
+                            event.getDeviceId(),
+                            event.getSource(),
+                            MotionEvent.AXIS_SCROLL,
+                            (int) (axisScrollValue * TEST_SCALED_VERTICAL_SCROLL_FACTOR));
+                }
             }
             return mGenericMotionResult;
         }
diff --git a/data/sounds/Android.bp b/data/sounds/Android.bp
new file mode 100644
index 0000000..65d4872
--- /dev/null
+++ b/data/sounds/Android.bp
@@ -0,0 +1,304 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+phony {
+    name: "frameworks_sounds",
+    required: [
+        "frameworks_alarm_sounds",
+        "frameworks_notifications_sounds",
+        "frameworks_ringtones_sounds",
+        "frameworks_ui_sounds",
+        "frameworks_ui_48k_sounds",
+    ],
+}
+
+prebuilt_media {
+    name: "frameworks_alarm_sounds",
+    srcs: [
+        "Alarm_Beep_01.ogg",
+        "Alarm_Beep_02.ogg",
+        "Alarm_Beep_03.ogg",
+        "Alarm_Buzzer.ogg",
+        "Alarm_Classic.ogg",
+        "Alarm_Rooster_02.ogg",
+        "alarms/ogg/Argon.ogg",
+        "alarms/ogg/Barium.ogg",
+        "alarms/ogg/Carbon.ogg",
+        "alarms/ogg/Helium.ogg",
+        "alarms/ogg/Krypton.ogg",
+        "alarms/ogg/Neon.ogg",
+        "alarms/ogg/Neptunium.ogg",
+        "alarms/ogg/Osmium.ogg",
+        "alarms/ogg/Oxygen.ogg",
+        "alarms/ogg/Platinum.ogg",
+        "alarms/ogg/Promethium.ogg",
+        "alarms/ogg/Scandium.ogg",
+    ],
+    relative_install_path: "audio/alarms",
+    product_specific: true,
+    no_full_install: true,
+}
+
+prebuilt_media {
+    name: "frameworks_notifications_sounds",
+    srcs: [
+        "notifications/ogg/Adara.ogg",
+        "notifications/Aldebaran.ogg",
+        "notifications/Altair.ogg",
+        "notifications/ogg/Alya.ogg",
+        "notifications/Antares.ogg",
+        "notifications/ogg/Antimony.ogg",
+        "notifications/ogg/Arcturus.ogg",
+        "notifications/ogg/Argon.ogg",
+        "notifications/Beat_Box_Android.ogg",
+        "notifications/ogg/Bellatrix.ogg",
+        "notifications/ogg/Beryllium.ogg",
+        "notifications/Betelgeuse.ogg",
+        "newwavelabs/CaffeineSnake.ogg",
+        "notifications/Canopus.ogg",
+        "notifications/ogg/Capella.ogg",
+        "notifications/Castor.ogg",
+        "notifications/ogg/CetiAlpha.ogg",
+        "notifications/ogg/Cobalt.ogg",
+        "notifications/Cricket.ogg",
+        "newwavelabs/DearDeer.ogg",
+        "notifications/Deneb.ogg",
+        "notifications/Doink.ogg",
+        "newwavelabs/DontPanic.ogg",
+        "notifications/Drip.ogg",
+        "notifications/Electra.ogg",
+        "F1_MissedCall.ogg",
+        "F1_New_MMS.ogg",
+        "F1_New_SMS.ogg",
+        "notifications/ogg/Fluorine.ogg",
+        "notifications/Fomalhaut.ogg",
+        "notifications/ogg/Gallium.ogg",
+        "notifications/Heaven.ogg",
+        "notifications/ogg/Helium.ogg",
+        "newwavelabs/Highwire.ogg",
+        "notifications/ogg/Hojus.ogg",
+        "notifications/ogg/Iridium.ogg",
+        "notifications/ogg/Krypton.ogg",
+        "newwavelabs/KzurbSonar.ogg",
+        "notifications/ogg/Lalande.ogg",
+        "notifications/Merope.ogg",
+        "notifications/ogg/Mira.ogg",
+        "newwavelabs/OnTheHunt.ogg",
+        "notifications/ogg/Palladium.ogg",
+        "notifications/Plastic_Pipe.ogg",
+        "notifications/ogg/Polaris.ogg",
+        "notifications/ogg/Pollux.ogg",
+        "notifications/ogg/Procyon.ogg",
+        "notifications/ogg/Proxima.ogg",
+        "notifications/ogg/Radon.ogg",
+        "notifications/ogg/Rubidium.ogg",
+        "notifications/ogg/Selenium.ogg",
+        "notifications/ogg/Shaula.ogg",
+        "notifications/Sirrah.ogg",
+        "notifications/SpaceSeed.ogg",
+        "notifications/ogg/Spica.ogg",
+        "notifications/ogg/Strontium.ogg",
+        "notifications/ogg/Syrma.ogg",
+        "notifications/TaDa.ogg",
+        "notifications/ogg/Talitha.ogg",
+        "notifications/ogg/Tejat.ogg",
+        "notifications/ogg/Thallium.ogg",
+        "notifications/Tinkerbell.ogg",
+        "notifications/ogg/Upsilon.ogg",
+        "notifications/ogg/Vega.ogg",
+        "newwavelabs/Voila.ogg",
+        "notifications/ogg/Xenon.ogg",
+        "notifications/ogg/Zirconium.ogg",
+        "notifications/arcturus.ogg",
+        "notifications/moonbeam.ogg",
+        "notifications/pixiedust.ogg",
+        "notifications/pizzicato.ogg",
+        "notifications/regulus.ogg",
+        "notifications/sirius.ogg",
+        "notifications/tweeters.ogg",
+        "notifications/vega.ogg",
+    ],
+    relative_install_path: "audio/notifications",
+    product_specific: true,
+    no_full_install: true,
+}
+
+prebuilt_media {
+    name: "frameworks_ringtones_sounds",
+    srcs: [
+        "ringtones/ANDROMEDA.ogg",
+        "ringtones/ogg/Andromeda.ogg",
+        "ringtones/ogg/Aquila.ogg",
+        "ringtones/ogg/ArgoNavis.ogg",
+        "ringtones/ogg/Atria.ogg",
+        "ringtones/BOOTES.ogg",
+        "newwavelabs/Backroad.ogg",
+        "newwavelabs/BeatPlucker.ogg",
+        "newwavelabs/BentleyDubs.ogg",
+        "newwavelabs/Big_Easy.ogg",
+        "newwavelabs/BirdLoop.ogg",
+        "newwavelabs/Bollywood.ogg",
+        "newwavelabs/BussaMove.ogg",
+        "ringtones/CANISMAJOR.ogg",
+        "ringtones/CASSIOPEIA.ogg",
+        "newwavelabs/Cairo.ogg",
+        "newwavelabs/Calypso_Steel.ogg",
+        "ringtones/ogg/CanisMajor.ogg",
+        "newwavelabs/CaribbeanIce.ogg",
+        "ringtones/ogg/Carina.ogg",
+        "ringtones/ogg/Centaurus.ogg",
+        "newwavelabs/Champagne_Edition.ogg",
+        "newwavelabs/Club_Cubano.ogg",
+        "newwavelabs/CrayonRock.ogg",
+        "newwavelabs/CrazyDream.ogg",
+        "newwavelabs/CurveBall.ogg",
+        "ringtones/ogg/Cygnus.ogg",
+        "newwavelabs/DancinFool.ogg",
+        "newwavelabs/Ding.ogg",
+        "newwavelabs/DonMessWivIt.ogg",
+        "ringtones/ogg/Draco.ogg",
+        "newwavelabs/DreamTheme.ogg",
+        "newwavelabs/Eastern_Sky.ogg",
+        "newwavelabs/Enter_the_Nexus.ogg",
+        "ringtones/Eridani.ogg",
+        "newwavelabs/EtherShake.ogg",
+        "ringtones/FreeFlight.ogg",
+        "newwavelabs/FriendlyGhost.ogg",
+        "newwavelabs/Funk_Yall.ogg",
+        "newwavelabs/GameOverGuitar.ogg",
+        "newwavelabs/Gimme_Mo_Town.ogg",
+        "ringtones/ogg/Girtab.ogg",
+        "newwavelabs/Glacial_Groove.ogg",
+        "newwavelabs/Growl.ogg",
+        "newwavelabs/HalfwayHome.ogg",
+        "ringtones/ogg/Hydra.ogg",
+        "newwavelabs/InsertCoin.ogg",
+        "ringtones/ogg/Kuma.ogg",
+        "newwavelabs/LoopyLounge.ogg",
+        "newwavelabs/LoveFlute.ogg",
+        "ringtones/Lyra.ogg",
+        "ringtones/ogg/Machina.ogg",
+        "newwavelabs/MidEvilJaunt.ogg",
+        "newwavelabs/MildlyAlarming.ogg",
+        "newwavelabs/Nairobi.ogg",
+        "newwavelabs/Nassau.ogg",
+        "newwavelabs/NewPlayer.ogg",
+        "newwavelabs/No_Limits.ogg",
+        "newwavelabs/Noises1.ogg",
+        "newwavelabs/Noises2.ogg",
+        "newwavelabs/Noises3.ogg",
+        "newwavelabs/OrganDub.ogg",
+        "ringtones/ogg/Orion.ogg",
+        "ringtones/PERSEUS.ogg",
+        "newwavelabs/Paradise_Island.ogg",
+        "ringtones/ogg/Pegasus.ogg",
+        "ringtones/ogg/Perseus.ogg",
+        "newwavelabs/Playa.ogg",
+        "ringtones/ogg/Pyxis.ogg",
+        "ringtones/ogg/Rasalas.ogg",
+        "newwavelabs/Revelation.ogg",
+        "ringtones/ogg/Rigel.ogg",
+        "Ring_Classic_02.ogg",
+        "Ring_Digital_02.ogg",
+        "Ring_Synth_02.ogg",
+        "Ring_Synth_04.ogg",
+        "newwavelabs/Road_Trip.ogg",
+        "newwavelabs/RomancingTheTone.ogg",
+        "newwavelabs/Safari.ogg",
+        "newwavelabs/Savannah.ogg",
+        "ringtones/ogg/Scarabaeus.ogg",
+        "ringtones/ogg/Sceptrum.ogg",
+        "newwavelabs/Seville.ogg",
+        "newwavelabs/Shes_All_That.ogg",
+        "newwavelabs/SilkyWay.ogg",
+        "newwavelabs/SitarVsSitar.ogg",
+        "ringtones/ogg/Solarium.ogg",
+        "newwavelabs/SpringyJalopy.ogg",
+        "newwavelabs/Steppin_Out.ogg",
+        "newwavelabs/Terminated.ogg",
+        "ringtones/Testudo.ogg",
+        "ringtones/ogg/Themos.ogg",
+        "newwavelabs/Third_Eye.ogg",
+        "newwavelabs/Thunderfoot.ogg",
+        "newwavelabs/TwirlAway.ogg",
+        "ringtones/URSAMINOR.ogg",
+        "ringtones/ogg/UrsaMinor.ogg",
+        "newwavelabs/VeryAlarmed.ogg",
+        "ringtones/Vespa.ogg",
+        "newwavelabs/World.ogg",
+        "ringtones/ogg/Zeta.ogg",
+        "ringtones/hydra.ogg",
+    ],
+    relative_install_path: "audio/ringtones",
+    product_specific: true,
+    no_full_install: true,
+}
+
+prebuilt_media {
+    name: "frameworks_ui_48k_sounds",
+    srcs: [
+        "effects/ogg/Effect_Tick_48k.ogg",
+        "effects/ogg/KeypressDelete_120_48k.ogg",
+        "effects/ogg/KeypressReturn_120_48k.ogg",
+        "effects/ogg/KeypressSpacebar_120_48k.ogg",
+        "effects/ogg/KeypressStandard_120_48k.ogg",
+        "effects/ogg/KeypressInvalid_120_48k.ogg",
+        "effects/ogg/Trusted_48k.ogg",
+        "effects/ogg/VideoRecord_48k.ogg",
+        "effects/ogg/VideoStop_48k.ogg",
+        "effects/ogg/camera_click_48k.ogg",
+    ],
+    dsts: [
+        "Effect_Tick.ogg",
+        "KeypressDelete.ogg",
+        "KeypressReturn.ogg",
+        "KeypressSpacebar.ogg",
+        "KeypressStandard.ogg",
+        "KeypressInvalid.ogg",
+        "Trusted.ogg",
+        "VideoRecord.ogg",
+        "VideoStop.ogg",
+        "camera_click.ogg",
+    ],
+    relative_install_path: "audio/ui",
+    product_specific: true,
+    no_full_install: true,
+}
+
+prebuilt_media {
+    name: "frameworks_ui_sounds",
+    srcs: [
+        "effects/ogg/Dock.ogg",
+        "effects/ogg/Lock.ogg",
+        "effects/ogg/LowBattery.ogg",
+        "effects/ogg/Undock.ogg",
+        "effects/ogg/Unlock.ogg",
+        "effects/ogg/WirelessChargingStarted.ogg",
+        "effects/ogg/camera_focus.ogg",
+        "effects/ogg/ChargingStarted.ogg",
+        "effects/ogg/InCallNotification.ogg",
+        "effects/ogg/NFCFailure.ogg",
+        "effects/ogg/NFCInitiated.ogg",
+        "effects/ogg/NFCSuccess.ogg",
+        "effects/ogg/NFCTransferComplete.ogg",
+        "effects/ogg/NFCTransferInitiated.ogg",
+    ],
+    relative_install_path: "audio/ui",
+    product_specific: true,
+    no_full_install: true,
+}
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index b2ac640..636e3cf 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -32,7 +32,6 @@
             android:name=".desktopmode.DesktopWallpaperActivity"
             android:excludeFromRecents="true"
             android:launchMode="singleInstance"
-            android:showForAllUsers="true"
             android:theme="@style/DesktopWallpaperTheme" />
 
         <activity
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 0fd98ed..39dc267 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
@@ -1295,6 +1295,14 @@
             // We still have bubbles, if we dragged an individual bubble to dismiss we were expanded
             // so re-expand to whatever is selected.
             showExpandedViewForBubbleBar();
+            if (bubbleKey.equals(selectedBubbleKey)) {
+                // We dragged the selected bubble to dismiss, log switch event
+                if (mBubbleData.getSelectedBubble() instanceof Bubble) {
+                    // Log only bubbles as overflow can't be dragged
+                    mLogger.log((Bubble) mBubbleData.getSelectedBubble(),
+                            BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED);
+                }
+            }
         }
     }
 
@@ -1337,10 +1345,16 @@
     public void expandStackAndSelectBubbleFromLauncher(String key, int topOnScreen) {
         mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen);
 
+        boolean wasExpanded = (mLayerView != null && mLayerView.isExpanded());
+
         if (BubbleOverflow.KEY.equals(key)) {
             mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
             mLayerView.showExpandedView(mBubbleData.getOverflow());
-            mLogger.log(BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
+            if (wasExpanded) {
+                mLogger.log(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED);
+            } else {
+                mLogger.log(BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
+            }
             return;
         }
 
@@ -1352,7 +1366,11 @@
             // already in the stack
             mBubbleData.setSelectedBubbleFromLauncher(b);
             mLayerView.showExpandedView(b);
-            mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
+            if (wasExpanded) {
+                mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED);
+            } else {
+                mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
+            }
         } else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) {
             // TODO: (b/271468319) handle overflow
         } else {
@@ -2081,6 +2099,9 @@
             // Only need to update the layer view if we're currently expanded for selection changes.
             if (mLayerView != null && mLayerView.isExpanded()) {
                 mLayerView.showExpandedView(selectedBubble);
+                if (selectedBubble instanceof Bubble bubble) {
+                    mLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED);
+                }
             }
         }
     };
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 0200e18..c99d9ba8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -188,6 +188,8 @@
      */
     private boolean mIsFirstReachabilityEducationRunning;
 
+    private boolean mIsInDesktopMode;
+
     @NonNull
     private final CompatUIStatusManager mCompatUIStatusManager;
 
@@ -253,18 +255,19 @@
         if (taskInfo != null && !taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat()) {
             mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
         }
-
-        if (taskInfo != null && taskListener != null) {
-            updateActiveTaskInfo(taskInfo);
-        }
-
-        // We close all the Compat UI educations in case we're in desktop mode.
-        if (taskInfo.configuration == null || taskListener == null
-                || isInDesktopMode(taskInfo.displayId)) {
+        mIsInDesktopMode = isInDesktopMode(taskInfo);
+        // We close all the Compat UI educations in case TaskInfo has no configuration or
+        // TaskListener or in desktop mode.
+        if (taskInfo.configuration == null || taskListener == null || mIsInDesktopMode) {
             // Null token means the current foreground activity is not in compatibility mode.
             removeLayouts(taskInfo.taskId);
             return;
         }
+        if (taskInfo != null && taskListener != null) {
+            updateActiveTaskInfo(taskInfo);
+        }
+
+
         // We're showing the first reachability education so we ignore incoming TaskInfo
         // until the education flow has completed or we double tap. The double-tap
         // basically cancel all the onboarding flow. We don't have to ignore events in case
@@ -443,7 +446,7 @@
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         CompatUIWindowManager layout = mActiveCompatLayouts.get(taskInfo.taskId);
         if (layout != null) {
-            if (layout.needsToBeRecreated(taskInfo, taskListener)) {
+            if (layout.needsToBeRecreated(taskInfo, taskListener) || mIsInDesktopMode) {
                 mActiveCompatLayouts.remove(taskInfo.taskId);
                 layout.release();
             } else {
@@ -456,7 +459,10 @@
                 return;
             }
         }
-
+        if (mIsInDesktopMode) {
+            // Return if in desktop mode.
+            return;
+        }
         // Create a new UI layout.
         final Context context = getOrCreateDisplayContext(taskInfo.displayId);
         if (context == null) {
@@ -494,7 +500,8 @@
     private void createOrUpdateLetterboxEduLayout(@NonNull TaskInfo taskInfo,
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         if (mActiveLetterboxEduLayout != null) {
-            if (mActiveLetterboxEduLayout.needsToBeRecreated(taskInfo, taskListener)) {
+            if (mActiveLetterboxEduLayout.needsToBeRecreated(taskInfo, taskListener)
+                    || mIsInDesktopMode) {
                 mActiveLetterboxEduLayout.release();
                 mActiveLetterboxEduLayout = null;
             } else {
@@ -507,6 +514,10 @@
                 return;
             }
         }
+        if (mIsInDesktopMode) {
+            // Return if in desktop mode.
+            return;
+        }
         // Create a new UI layout.
         final Context context = getOrCreateDisplayContext(taskInfo.displayId);
         if (context == null) {
@@ -541,7 +552,7 @@
         RestartDialogWindowManager layout =
                 mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId);
         if (layout != null) {
-            if (layout.needsToBeRecreated(taskInfo, taskListener)) {
+            if (layout.needsToBeRecreated(taskInfo, taskListener) || mIsInDesktopMode) {
                 mTaskIdToRestartDialogWindowManagerMap.remove(taskInfo.taskId);
                 layout.release();
             } else {
@@ -556,6 +567,10 @@
                 return;
             }
         }
+        if (mIsInDesktopMode) {
+            // Return if in desktop mode.
+            return;
+        }
         // Create a new UI layout.
         final Context context = getOrCreateDisplayContext(taskInfo.displayId);
         if (context == null) {
@@ -594,7 +609,8 @@
     private void createOrUpdateReachabilityEduLayout(@NonNull TaskInfo taskInfo,
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         if (mActiveReachabilityEduLayout != null) {
-            if (mActiveReachabilityEduLayout.needsToBeRecreated(taskInfo, taskListener)) {
+            if (mActiveReachabilityEduLayout.needsToBeRecreated(taskInfo, taskListener)
+                    || mIsInDesktopMode) {
                 mActiveReachabilityEduLayout.release();
                 mActiveReachabilityEduLayout = null;
             } else {
@@ -608,6 +624,10 @@
                 return;
             }
         }
+        if (mIsInDesktopMode) {
+            // Return if in desktop mode.
+            return;
+        }
         // Create a new UI layout.
         final Context context = getOrCreateDisplayContext(taskInfo.displayId);
         if (context == null) {
@@ -647,7 +667,8 @@
     private void createOrUpdateUserAspectRatioSettingsLayout(@NonNull TaskInfo taskInfo,
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         if (mUserAspectRatioSettingsLayout != null) {
-            if (mUserAspectRatioSettingsLayout.needsToBeRecreated(taskInfo, taskListener)) {
+            if (mUserAspectRatioSettingsLayout.needsToBeRecreated(taskInfo, taskListener)
+                    || mIsInDesktopMode) {
                 mUserAspectRatioSettingsLayout.release();
                 mUserAspectRatioSettingsLayout = null;
             } else {
@@ -660,7 +681,10 @@
                 return;
             }
         }
-
+        if (mIsInDesktopMode) {
+            // Return if in desktop mode.
+            return;
+        }
         // Create a new UI layout.
         final Context context = getOrCreateDisplayContext(taskInfo.displayId);
         if (context == null) {
@@ -840,8 +864,8 @@
         boolean mHasShownUserAspectRatioSettingsButtonHint;
     }
 
-    private boolean isInDesktopMode(int displayId) {
-        return Flags.skipCompatUiEducationInDesktopMode()
-                && mInDesktopModePredicate.test(displayId);
+    private boolean isInDesktopMode(@Nullable TaskInfo taskInfo) {
+        return taskInfo != null && Flags.skipCompatUiEducationInDesktopMode()
+                && mInDesktopModePredicate.test(taskInfo.displayId);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 223038f..6928c25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -42,6 +42,7 @@
 import android.os.Handler
 import android.os.IBinder
 import android.os.SystemProperties
+import android.os.UserHandle
 import android.util.Size
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.DragEvent
@@ -1174,7 +1175,11 @@
 
     private fun addWallpaperActivity(wct: WindowContainerTransaction) {
         logV("addWallpaperActivity")
-        val intent = Intent(context, DesktopWallpaperActivity::class.java)
+        val userHandle = UserHandle.of(userId)
+        val userContext =
+            context.createContextAsUser(userHandle, /* flags= */ 0)
+        val intent = Intent(userContext, DesktopWallpaperActivity::class.java)
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId)
         val options =
             ActivityOptions.makeBasic().apply {
                 launchWindowingMode = WINDOWING_MODE_FULLSCREEN
@@ -1182,11 +1187,13 @@
                     ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
             }
         val pendingIntent =
-            PendingIntent.getActivity(
-                context,
-                /* requestCode = */ 0,
+            PendingIntent.getActivityAsUser(
+                userContext,
+                /* requestCode= */ 0,
                 intent,
-                PendingIntent.FLAG_IMMUTABLE
+                PendingIntent.FLAG_IMMUTABLE,
+                /* bundle= */ null,
+                userHandle
             )
         wct.sendPendingIntent(pendingIntent, intent, options.toBundle())
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
index 1c2415c..e835b2f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
@@ -36,7 +36,6 @@
 class DesktopWallpaperActivity : Activity() {
 
     override fun onCreate(savedInstanceState: Bundle?) {
-        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopWallpaperActivity: onCreate")
         super.onCreate(savedInstanceState)
         window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 5ffc64f..79a9ce5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -47,7 +47,6 @@
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipMenuController;
-import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.DefaultMixedHandler;
@@ -312,10 +311,10 @@
     }
 
     /** Whether a particular package is same as current pip package. */
-    public boolean isPackageActiveInPip(String packageName) {
-        final TaskInfo inPipTask = mPipOrganizer.getTaskInfo();
-        return packageName != null && inPipTask != null && mPipOrganizer.isInPip()
-                && packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent));
+    public boolean isPackageActiveInPip(@Nullable String packageName) {
+        return packageName != null
+                && mPipBoundsState.getLastPipComponentName() != null
+                && packageName.equals(mPipBoundsState.getLastPipComponentName().getPackageName());
     }
 
     /** Add PiP-related changes to `outWCT` for the given request. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index bc09183..e901c39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -322,7 +322,7 @@
             mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction);
             mPipBoundsState.setBounds(toBounds);
         }
-        t.setBounds(mPipTransitionState.mPipTaskToken, mPipBoundsState.getBounds());
+        t.setBounds(mPipTransitionState.getPipTaskToken(), mPipBoundsState.getBounds());
     }
 
     private void setDisplayLayout(DisplayLayout layout) {
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 607de0e..5438a01 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
@@ -25,10 +25,13 @@
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipBoundsState;
@@ -48,11 +51,13 @@
     private final ShellExecutor mMainExecutor;
     private final PipTransitionState mPipTransitionState;
     private PipTransitionController mPipTransitionController;
-    private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+    private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
 
     @Nullable private Runnable mUpdateMovementBoundsRunnable;
 
+    private PipAlphaAnimatorSupplier mPipAlphaAnimatorSupplier;
+
     public PipScheduler(Context context,
             PipBoundsState pipBoundsState,
             ShellExecutor mainExecutor,
@@ -64,10 +69,7 @@
 
         mSurfaceControlTransactionFactory =
                 new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
-    }
-
-    ShellExecutor getMainExecutor() {
-        return mMainExecutor;
+        mPipAlphaAnimatorSupplier = PipAlphaAnimator::new;
     }
 
     void setPipTransitionController(PipTransitionController pipTransitionController) {
@@ -76,27 +78,29 @@
 
     @Nullable
     private WindowContainerTransaction getExitPipViaExpandTransaction() {
-        if (mPipTransitionState.mPipTaskToken == null) {
+        WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
+        if (pipTaskToken == null) {
             return null;
         }
         WindowContainerTransaction wct = new WindowContainerTransaction();
         // final expanded bounds to be inherited from the parent
-        wct.setBounds(mPipTransitionState.mPipTaskToken, null);
+        wct.setBounds(pipTaskToken, null);
         // if we are hitting a multi-activity case
         // windowing mode change will reparent to original host task
-        wct.setWindowingMode(mPipTransitionState.mPipTaskToken, WINDOWING_MODE_UNDEFINED);
+        wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED);
         return wct;
     }
 
     @Nullable
     private WindowContainerTransaction getRemovePipTransaction() {
-        if (mPipTransitionState.mPipTaskToken == null) {
+        WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
+        if (pipTaskToken == null) {
             return null;
         }
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.setBounds(mPipTransitionState.mPipTaskToken, null);
-        wct.setWindowingMode(mPipTransitionState.mPipTaskToken, WINDOWING_MODE_UNDEFINED);
-        wct.reorder(mPipTransitionState.mPipTaskToken, false);
+        wct.setBounds(pipTaskToken, null);
+        wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED);
+        wct.reorder(pipTaskToken, false);
         return wct;
     }
 
@@ -117,7 +121,7 @@
     /** 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,
+        PipAlphaAnimator animator = mPipAlphaAnimatorSupplier.get(mContext,
                 mPipTransitionState.getPinnedTaskLeash(), tx, PipAlphaAnimator.FADE_OUT);
         animator.setAnimationEndCallback(this::scheduleRemovePipImmediately);
         animator.start();
@@ -159,13 +163,14 @@
      *                    for running the animator will get this as an extra.
      */
     public void scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd, int duration) {
-        if (mPipTransitionState.mPipTaskToken == null || !mPipTransitionState.isInPip()) {
+        WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
+        if (pipTaskToken == null || !mPipTransitionState.isInPip()) {
             return;
         }
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.setBounds(mPipTransitionState.mPipTaskToken, toBounds);
+        wct.setBounds(pipTaskToken, toBounds);
         if (configAtEnd) {
-            wct.deferConfigToTransitionEnd(mPipTransitionState.mPipTaskToken);
+            wct.deferConfigToTransitionEnd(pipTaskToken);
         }
         mPipTransitionController.startResizeTransition(wct, duration);
     }
@@ -204,7 +209,7 @@
             return;
         }
         SurfaceControl leash = mPipTransitionState.getPinnedTaskLeash();
-        final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
 
         Matrix transformTensor = new Matrix();
         final float[] mMatrixTmp = new float[9];
@@ -218,7 +223,7 @@
         tx.apply();
     }
 
-    void setUpdateMovementBoundsRunnable(Runnable updateMovementBoundsRunnable) {
+    void setUpdateMovementBoundsRunnable(@Nullable Runnable updateMovementBoundsRunnable) {
         mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
     }
 
@@ -235,4 +240,23 @@
         mPipBoundsState.setBounds(newBounds);
         maybeUpdateMovementBounds();
     }
+
+    @VisibleForTesting
+    void setSurfaceControlTransactionFactory(
+            @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
+        mSurfaceControlTransactionFactory = factory;
+    }
+
+    @VisibleForTesting
+    interface PipAlphaAnimatorSupplier {
+        PipAlphaAnimator get(@NonNull Context context,
+                SurfaceControl leash,
+                SurfaceControl.Transaction tx,
+                @PipAlphaAnimator.Fade int direction);
+    }
+
+    @VisibleForTesting
+    void setPipAlphaAnimatorSupplier(@NonNull PipAlphaAnimatorSupplier supplier) {
+        mPipAlphaAnimatorSupplier = supplier;
+    }
 }
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 3caad09..d415c10 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
@@ -420,7 +420,8 @@
         }
 
         // Update the src-rect-hint in params in place, to set up initial animator transform.
-        params.getSourceRectHint().set(adjustedSourceRectHint);
+        params.copyOnlySet(new PictureInPictureParams.Builder()
+                .setSourceRectHint(adjustedSourceRectHint).build());
 
         // Config-at-end transitions need to have their activities transformed before starting
         // the animation; this makes the buffer seem like it's been updated to final size.
@@ -543,7 +544,7 @@
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        WindowContainerToken pipToken = mPipTransitionState.mPipTaskToken;
+        WindowContainerToken pipToken = mPipTransitionState.getPipTaskToken();
 
         TransitionInfo.Change pipChange = getChangeByToken(info, pipToken);
         if (pipChange == null) {
@@ -773,11 +774,11 @@
     }
 
     private boolean isRemovePipTransition(@NonNull TransitionInfo info) {
-        if (mPipTransitionState.mPipTaskToken == null) {
+        if (mPipTransitionState.getPipTaskToken() == null) {
             // PiP removal makes sense if enter-PiP has cached a valid pinned task token.
             return false;
         }
-        TransitionInfo.Change pipChange = info.getChange(mPipTransitionState.mPipTaskToken);
+        TransitionInfo.Change pipChange = info.getChange(mPipTransitionState.getPipTaskToken());
         if (pipChange == null) {
             // Search for the PiP change by token since the windowing mode might be FULLSCREEN now.
             return false;
@@ -859,18 +860,18 @@
                 Preconditions.checkState(extra != null,
                         "No extra bundle for " + mPipTransitionState);
 
-                mPipTransitionState.mPipTaskToken = extra.getParcelable(
-                        PIP_TASK_TOKEN, WindowContainerToken.class);
+                mPipTransitionState.setPipTaskToken(extra.getParcelable(
+                        PIP_TASK_TOKEN, WindowContainerToken.class));
                 mPipTransitionState.setPinnedTaskLeash(extra.getParcelable(
                         PIP_TASK_LEASH, SurfaceControl.class));
-                boolean hasValidTokenAndLeash = mPipTransitionState.mPipTaskToken != null
+                boolean hasValidTokenAndLeash = mPipTransitionState.getPipTaskToken() != null
                         && mPipTransitionState.getPinnedTaskLeash() != null;
 
                 Preconditions.checkState(hasValidTokenAndLeash,
                         "Unexpected bundle for " + mPipTransitionState);
                 break;
             case PipTransitionState.EXITED_PIP:
-                mPipTransitionState.mPipTaskToken = null;
+                mPipTransitionState.setPipTaskToken(null);
                 mPipTransitionState.setPinnedTaskLeash(null);
                 break;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 03e06f9..8e90bfe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -138,7 +138,7 @@
 
     // pinned PiP task's WC token
     @Nullable
-    WindowContainerToken mPipTaskToken;
+    private WindowContainerToken mPipTaskToken;
 
     // pinned PiP task's leash
     @Nullable
@@ -304,6 +304,14 @@
         mSwipePipToHomeAppBounds.setEmpty();
     }
 
+    @Nullable WindowContainerToken getPipTaskToken() {
+        return mPipTaskToken;
+    }
+
+    public void setPipTaskToken(@Nullable WindowContainerToken token) {
+        mPipTaskToken = token;
+    }
+
     @Nullable SurfaceControl getPinnedTaskLeash() {
         return mPinnedTaskLeash;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index f739d65..49cf8ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -63,6 +63,8 @@
             "Bubbles"),
     WM_SHELL_COMPAT_UI(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_COMPAT_UI),
+    WM_SHELL_APP_COMPAT(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM_APP_COMPAT),
     TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
 
     private final boolean mEnabled;
@@ -131,6 +133,7 @@
         private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen";
         private static final String TAG_WM_DESKTOP_MODE = "ShellDesktopMode";
         private static final String TAG_WM_COMPAT_UI = "CompatUi";
+        private static final String TAG_WM_APP_COMPAT = "AppCompat";
 
         private static final boolean ENABLE_DEBUG = true;
         private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 9016c45..417a655 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -1286,6 +1286,16 @@
             if (mFinishCB == null
                     || (Flags.enableShellTopTaskTracking() && mPendingFinishTransition != null)) {
                 Slog.e(TAG, "Duplicate call to finish");
+                if (runnerFinishCb != null) {
+                    try {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                                "[%d] RecentsController.finishInner: calling finish callback",
+                                mInstanceId);
+                        runnerFinishCb.send(0, null);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Failed to report transition finished", e);
+                    }
+                }
                 return;
             }
 
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowWithDragToTopDragZoneTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowWithDragToTopDragZoneTest.kt
new file mode 100644
index 0000000..7e0b81a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowWithDragToTopDragZoneTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.MaximizeAppWindowWithDragToTopDragZone
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [MaximizeAppWindowWithDragToTopDragZone]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class MaximizeAppWindowWithDragToTopDragZoneTest : MaximizeAppWindowWithDragToTopDragZone()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt
new file mode 100644
index 0000000..a2b88f2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.internal.R
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Base scenario test for maximizing a desktop app window by dragging it to the top drag zone.
+ */
+@Ignore("Test Base Class")
+abstract class MaximizeAppWindowWithDragToTopDragZone
+constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        // Skip the test when the drag-to-maximize is disabled on this device.
+        Assume.assumeTrue(Flags.enableDragToMaximize() &&
+            instrumentation.context.resources.getBoolean(R.bool.config_dragToMaximizeInDesktopMode))
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        ChangeDisplayOrientationRule.setRotation(rotation)
+        testApp.enterDesktopWithDrag(wmHelper, device)
+    }
+
+    @Test
+    open fun maximizeAppWithDragToTopDragZone() {
+        testApp.maximizeAppWithDragToTopDragZone(wmHelper, device)
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
new file mode 100644
index 0000000..cab6252
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.when;
+import static org.mockito.kotlin.MatchersKt.eq;
+import static org.mockito.kotlin.VerificationKt.times;
+import static org.mockito.kotlin.VerificationKt.verify;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test against {@link PipScheduler}
+ */
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipSchedulerTest {
+    private static final int TEST_RESIZE_DURATION = 1;
+    private static final Rect TEST_STARTING_BOUNDS = new Rect(0, 0, 10, 10);
+    private static final Rect TEST_BOUNDS = new Rect(0, 0, 20, 20);
+
+    @Mock private Context mMockContext;
+    @Mock private Resources mMockResources;
+    @Mock private PipBoundsState mMockPipBoundsState;
+    @Mock private ShellExecutor mMockMainExecutor;
+    @Mock private PipTransitionState mMockPipTransitionState;
+    @Mock private PipTransitionController mMockPipTransitionController;
+    @Mock private Runnable mMockUpdateMovementBoundsRunnable;
+    @Mock private WindowContainerToken mMockPipTaskToken;
+    @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
+    @Mock private SurfaceControl.Transaction mMockTransaction;
+    @Mock private PipAlphaAnimator mMockAlphaAnimator;
+
+    @Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
+    @Captor private ArgumentCaptor<WindowContainerTransaction> mWctArgumentCaptor;
+
+    private PipScheduler mPipScheduler;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockResources.getInteger(anyInt())).thenReturn(0);
+        when(mMockPipBoundsState.getBounds()).thenReturn(TEST_STARTING_BOUNDS);
+        when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
+        when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(mMockTransaction);
+
+        mPipScheduler = new PipScheduler(mMockContext, mMockPipBoundsState, mMockMainExecutor,
+                mMockPipTransitionState);
+        mPipScheduler.setPipTransitionController(mMockPipTransitionController);
+        mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory);
+        mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, tx, direction) ->
+                mMockAlphaAnimator);
+
+        SurfaceControl testLeash = new SurfaceControl.Builder()
+                .setContainerLayer()
+                .setName("PipSchedulerTest")
+                .setCallsite("PipSchedulerTest")
+                .build();
+        when(mMockPipTransitionState.getPinnedTaskLeash()).thenReturn(testLeash);
+    }
+
+    @Test
+    public void scheduleExitPipViaExpand_nullTaskToken_noop() {
+        setNullPipTaskToken();
+
+        mPipScheduler.scheduleExitPipViaExpand();
+
+        verify(mMockMainExecutor, never()).execute(any());
+    }
+
+    @Test
+    public void scheduleExitPipViaExpand_exitTransitionCalled() {
+        setMockPipTaskToken();
+
+        mPipScheduler.scheduleExitPipViaExpand();
+
+        verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture());
+        assertNotNull(mRunnableArgumentCaptor.getValue());
+        mRunnableArgumentCaptor.getValue().run();
+
+        verify(mMockPipTransitionController, times(1))
+                .startExitTransition(eq(TRANSIT_EXIT_PIP), any(), isNull());
+    }
+
+    @Test
+    public void removePipAfterAnimation() {
+        //TODO: Update once this is changed to run animation as part of transition
+        setMockPipTaskToken();
+
+        mPipScheduler.removePipAfterAnimation();
+        verify(mMockAlphaAnimator, times(1))
+                .setAnimationEndCallback(mRunnableArgumentCaptor.capture());
+        assertNotNull(mRunnableArgumentCaptor.getValue());
+        verify(mMockAlphaAnimator, times(1)).start();
+
+        mRunnableArgumentCaptor.getValue().run();
+
+        verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture());
+        assertNotNull(mRunnableArgumentCaptor.getValue());
+
+        mRunnableArgumentCaptor.getValue().run();
+
+        verify(mMockPipTransitionController, times(1))
+                .startExitTransition(eq(TRANSIT_REMOVE_PIP), any(), isNull());
+    }
+
+    @Test
+    public void scheduleAnimateResizePip_bounds_nullTaskToken_noop() {
+        setNullPipTaskToken();
+
+        mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS);
+
+        verify(mMockPipTransitionController, never()).startResizeTransition(any(), anyInt());
+    }
+
+    @Test
+    public void scheduleAnimateResizePip_boundsConfig_nullTaskToken_noop() {
+        setNullPipTaskToken();
+
+        mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true);
+
+        verify(mMockPipTransitionController, never()).startResizeTransition(any(), anyInt());
+    }
+
+    @Test
+    public void scheduleAnimateResizePip_boundsConfig_setsConfigAtEnd() {
+        setMockPipTaskToken();
+        when(mMockPipTransitionState.isInPip()).thenReturn(true);
+
+        mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true);
+
+        verify(mMockPipTransitionController, times(1))
+                .startResizeTransition(mWctArgumentCaptor.capture(), anyInt());
+        assertNotNull(mWctArgumentCaptor.getValue());
+        assertNotNull(mWctArgumentCaptor.getValue().getChanges());
+        boolean hasConfigAtEndChange = false;
+        for (WindowContainerTransaction.Change change :
+                mWctArgumentCaptor.getValue().getChanges().values()) {
+            if (change.getConfigAtTransitionEnd()) {
+                hasConfigAtEndChange = true;
+                break;
+            }
+        }
+        assertTrue(hasConfigAtEndChange);
+    }
+
+    @Test
+    public void scheduleAnimateResizePip_boundsConfigDuration_nullTaskToken_noop() {
+        setNullPipTaskToken();
+
+        mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true, TEST_RESIZE_DURATION);
+
+        verify(mMockPipTransitionController, never()).startResizeTransition(any(), anyInt());
+    }
+
+    @Test
+    public void scheduleAnimateResizePip_notInPip_noop() {
+        setMockPipTaskToken();
+        when(mMockPipTransitionState.isInPip()).thenReturn(false);
+
+        mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true, TEST_RESIZE_DURATION);
+
+        verify(mMockPipTransitionController, never()).startResizeTransition(any(), anyInt());
+    }
+
+    @Test
+    public void scheduleAnimateResizePip_resizeTransition() {
+        setMockPipTaskToken();
+        when(mMockPipTransitionState.isInPip()).thenReturn(true);
+
+        mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true, TEST_RESIZE_DURATION);
+
+        verify(mMockPipTransitionController, times(1))
+                .startResizeTransition(any(), eq(TEST_RESIZE_DURATION));
+    }
+
+    @Test
+    public void scheduleUserResizePip_emptyBounds_noop() {
+        setMockPipTaskToken();
+
+        mPipScheduler.scheduleUserResizePip(new Rect());
+
+        verify(mMockTransaction, never()).apply();
+    }
+
+    @Test
+    public void scheduleUserResizePip_rotation_emptyBounds_noop() {
+        setMockPipTaskToken();
+
+        mPipScheduler.scheduleUserResizePip(new Rect(), 90);
+
+        verify(mMockTransaction, never()).apply();
+    }
+
+    @Test
+    public void scheduleUserResizePip_applyTransaction() {
+        setMockPipTaskToken();
+
+        mPipScheduler.scheduleUserResizePip(TEST_BOUNDS, 90);
+
+        verify(mMockTransaction, times(1)).apply();
+    }
+
+    @Test
+    public void finishResize_movementBoundsRunnableCalled() {
+        mPipScheduler.setUpdateMovementBoundsRunnable(mMockUpdateMovementBoundsRunnable);
+        mPipScheduler.scheduleFinishResizePip(TEST_BOUNDS);
+
+        verify(mMockUpdateMovementBoundsRunnable, times(1)).run();
+    }
+
+    private void setNullPipTaskToken() {
+        when(mMockPipTransitionState.getPipTaskToken()).thenReturn(null);
+    }
+
+    private void setMockPipTaskToken() {
+        when(mMockPipTransitionState.getPipTaskToken()).thenReturn(mMockPipTaskToken);
+    }
+}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 2ae89d3..82e9503 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -16,6 +16,7 @@
 
 package android.media;
 
+import static android.media.codec.Flags.FLAG_CODEC_AVAILABILITY;
 import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE;
 import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
 import static android.media.codec.Flags.FLAG_SUBSESSION_METRICS;
@@ -29,6 +30,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
@@ -1843,6 +1845,12 @@
      */
     private static final int CB_METRICS_FLUSHED = 8;
 
+    /**
+     * Callback ID to notify the change in resource requirement
+     * for the codec component.
+     */
+    private static final int CB_REQUIRED_RESOURCES_CHANGE = 9;
+
     private class EventHandler extends Handler {
         private MediaCodec mCodec;
 
@@ -2017,13 +2025,19 @@
 
                 case CB_METRICS_FLUSHED:
                 {
-
                     if (GetFlag(() -> android.media.codec.Flags.subsessionMetrics())) {
                         mCallback.onMetricsFlushed(mCodec, (PersistableBundle)msg.obj);
                     }
                     break;
                 }
 
+                case CB_REQUIRED_RESOURCES_CHANGE: {
+                    if (android.media.codec.Flags.codecAvailability()) {
+                        mCallback.onRequiredResourcesChanged(mCodec);
+                    }
+                    break;
+                }
+
                 default:
                 {
                     break;
@@ -2302,6 +2316,70 @@
     }
 
     /**
+     * @hide
+     * Abstraction for the Global Codec resources.
+     * This encapsulates all the available codec resources on the device.
+     *
+     * To be able to enforce and test the implementation of codec availability hal APIs,
+     * globally available codec resources are exposed only as TestApi.
+     * This will be tracked and verified through cts.
+     */
+    @FlaggedApi(FLAG_CODEC_AVAILABILITY)
+    @TestApi
+    public static final class GlobalResourceInfo {
+        /**
+         * Identifier for the Resource type.
+         */
+        String mName;
+        /**
+         * Total count/capacity of resources of this type.
+         */
+        long mCapacity;
+        /**
+         * Available count of this resource type.
+         */
+        long mAvailable;
+
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
+        public long getCapacity() {
+            return mCapacity;
+        }
+
+        public long getAvailable() {
+            return mAvailable;
+        }
+    };
+
+    /**
+     * @hide
+     * Get a list of globally available codec resources.
+     *
+     * To be able to enforce and test the implementation of codec availability hal APIs,
+     * it is exposed only as TestApi.
+     * This will be tracked and verified through cts.
+     *
+     * This returns a {@link java.util.List} list of codec resources.
+     * For every {@link GlobalResourceInfo} in the list, it encapsulates the
+     * information about each resources available globaly on device.
+     *
+     * @return A list of available device codec resources; an empty list if no
+     *         device codec resources are available.
+     * @throws UnsupportedOperationException if not implemented.
+     */
+    @FlaggedApi(FLAG_CODEC_AVAILABILITY)
+    @TestApi
+    public static @NonNull List<GlobalResourceInfo> getGloballyAvailableResources() {
+        return native_getGloballyAvailableResources();
+    }
+
+    @NonNull
+    private static native List<GlobalResourceInfo> native_getGloballyAvailableResources();
+
+    /**
      * Configures a component.
      *
      * @param format The format of the input data (decoder) or the desired
@@ -2443,6 +2521,73 @@
     }
 
     /**
+     * @hide
+     * Abstraction for the resources associated with a codec instance.
+     * This encapsulates the required codec resources for a configured codec instance.
+     *
+     * To be able to enforce and test the implementation of codec availability hal APIs,
+     * required codec resources are exposed only as TestApi.
+     * This will be tracked and verified through cts.
+     */
+    @FlaggedApi(FLAG_CODEC_AVAILABILITY)
+    @TestApi
+    public static final class InstanceResourceInfo {
+        /**
+         * Identifier for the Resource type.
+         */
+        String mName;
+        /**
+         * Required resource count of this type.
+         */
+        long mStaticCount;
+        /**
+         * Per frame resource requirement of this resource type.
+         */
+        long mPerFrameCount;
+
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
+        public long getStaticCount() {
+            return mStaticCount;
+        }
+
+        public long getPerFrameCount() {
+            return mPerFrameCount;
+        }
+    };
+
+    /**
+     * @hide
+     * Get a list of required codec resources for this configuration.
+     *
+     * To be able to enforce and test the implementation of codec availability hal APIs,
+     * it is exposed only as TestApi.
+     * This will be tracked and verified through cts.
+     *
+     * This returns a {@link java.util.List} list of codec resources.
+     * For every {@link GlobalResourceInfo} in the list, it encapsulates the
+     * information about each resources required for the current configuration.
+     *
+     * NOTE: This may only be called after {@link #configure}.
+     *
+     * @return A list of required device codec resources; an empty list if no
+     *         device codec resources are required.
+     * @throws IllegalStateException if the codec wasn't configured yet.
+     * @throws UnsupportedOperationException if not implemented.
+     */
+    @FlaggedApi(FLAG_CODEC_AVAILABILITY)
+    @TestApi
+    public @NonNull List<InstanceResourceInfo> getRequiredResources() {
+        return native_getRequiredResources();
+    }
+
+    @NonNull
+    private native List<InstanceResourceInfo> native_getRequiredResources();
+
+    /**
      *  Dynamically sets the output surface of a codec.
      *  <p>
      *  This can only be used if the codec was configured with an output surface.  The
@@ -5740,6 +5885,25 @@
                 @NonNull MediaCodec codec, @NonNull PersistableBundle metrics) {
             // default implementation ignores this callback.
         }
+
+        /**
+         * @hide
+         * Called when there is a change in the required resources for the codec.
+         * <p>
+         * Upon receiving this notification, the updated resource requirement
+         * can be queried through {@link #getRequiredResources}.
+         *
+         * @param codec The MediaCodec object.
+         */
+        @FlaggedApi(FLAG_CODEC_AVAILABILITY)
+        @TestApi
+        public void onRequiredResourcesChanged(@NonNull MediaCodec codec) {
+            /*
+             * A default implementation for backward compatibility.
+             * Since this is a TestApi, we are not enforcing the callback to be
+             * overridden.
+             */
+        }
     }
 
     private void postEventFromNative(
diff --git a/media/java/android/media/quality/AmbientBacklightEvent.java b/media/java/android/media/quality/AmbientBacklightEvent.java
index 3bc6b86..5c11def 100644
--- a/media/java/android/media/quality/AmbientBacklightEvent.java
+++ b/media/java/android/media/quality/AmbientBacklightEvent.java
@@ -16,9 +16,11 @@
 
 package android.media.quality;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.media.tv.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -27,8 +29,10 @@
 import java.util.Objects;
 
 /**
+ * Ambient backlight event
  * @hide
  */
+@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
 public final class AmbientBacklightEvent implements Parcelable {
 
     /** @hide */
@@ -64,6 +68,9 @@
     @Nullable
     private final AmbientBacklightMetadata mMetadata;
 
+    /**
+     * Constructor of AmbientBacklightEvent.
+     */
     public AmbientBacklightEvent(int eventType,
             @Nullable AmbientBacklightMetadata metadata) {
         mEventType = eventType;
@@ -75,10 +82,19 @@
         mMetadata = in.readParcelable(AmbientBacklightMetadata.class.getClassLoader());
     }
 
+    /**
+     * Gets event type.
+     */
     public int getEventType() {
         return mEventType;
     }
 
+    /**
+     * Gets ambient backlight metadata.
+     *
+     * @return the metadata of the event. It's non-null only for
+     * {@link #AMBIENT_BACKLIGHT_EVENT_METADATA}.
+     */
     @Nullable
     public AmbientBacklightMetadata getMetadata() {
         return mMetadata;
@@ -95,7 +111,8 @@
         return 0;
     }
 
-    public static final @NonNull Parcelable.Creator<AmbientBacklightEvent> CREATOR =
+    @NonNull
+    public static final Parcelable.Creator<AmbientBacklightEvent> CREATOR =
             new Parcelable.Creator<AmbientBacklightEvent>() {
                 public AmbientBacklightEvent createFromParcel(Parcel in) {
                     return new AmbientBacklightEvent(in);
diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.java b/media/java/android/media/quality/AmbientBacklightMetadata.java
index fc77934..9c11f9a 100644
--- a/media/java/android/media/quality/AmbientBacklightMetadata.java
+++ b/media/java/android/media/quality/AmbientBacklightMetadata.java
@@ -16,6 +16,10 @@
 
 package android.media.quality;
 
+import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
+import android.graphics.PixelFormat;
+import android.media.tv.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -24,9 +28,11 @@
 import java.util.Arrays;
 
 /**
+ * Metadata of ambient backlight.
  * @hide
  */
-public class AmbientBacklightMetadata implements Parcelable {
+@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
+public final class AmbientBacklightMetadata implements Parcelable {
     @NonNull
     private final String mPackageName;
     private final int mCompressAlgorithm;
@@ -37,6 +43,9 @@
     @NonNull
     private final int[] mZonesColors;
 
+    /**
+     * Constructor of AmbientBacklightMetadata.
+     */
     public AmbientBacklightMetadata(@NonNull String packageName, int compressAlgorithm,
             int source, int colorFormat, int horizontalZonesNumber, int verticalZonesNumber,
             @NonNull int[] zonesColors) {
@@ -59,31 +68,58 @@
         mZonesColors = in.createIntArray();
     }
 
+    /**
+     * Gets package name.
+     * @hide
+     */
     @NonNull
     public String getPackageName() {
         return mPackageName;
     }
 
+    /**
+     * Gets compress algorithm.
+     */
+    @AmbientBacklightSettings.CompressAlgorithm
     public int getCompressAlgorithm() {
         return mCompressAlgorithm;
     }
 
+    /**
+     * Gets source of ambient backlight detection.
+     */
+    @AmbientBacklightSettings.Source
     public int getSource() {
         return mSource;
     }
 
+    /**
+     * Gets color format.
+     */
+    @PixelFormat.Format
     public int getColorFormat() {
         return mColorFormat;
     }
 
+    /**
+     * Gets the number of lights in each horizontal zone.
+     */
+    @IntRange(from = 0)
     public int getHorizontalZonesNumber() {
         return mHorizontalZonesNumber;
     }
 
+    /**
+     * Gets the number of lights in each vertical zone.
+     */
+    @IntRange(from = 0)
     public int getVerticalZonesNumber() {
         return mVerticalZonesNumber;
     }
 
+    /**
+     * @hide
+     */
     @NonNull
     public int[] getZonesColors() {
         return mZonesColors;
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.java b/media/java/android/media/quality/AmbientBacklightSettings.java
index bb782bf..4ed7bc7 100644
--- a/media/java/android/media/quality/AmbientBacklightSettings.java
+++ b/media/java/android/media/quality/AmbientBacklightSettings.java
@@ -16,7 +16,11 @@
 
 package android.media.quality;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.graphics.PixelFormat;
+import android.media.tv.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -29,7 +33,8 @@
  * Settings for ambient backlight.
  * @hide
  */
-public class AmbientBacklightSettings implements Parcelable {
+@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
+public final class AmbientBacklightSettings implements Parcelable {
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({SOURCE_NONE, SOURCE_AUDIO, SOURCE_VIDEO, SOURCE_AUDIO_VIDEO})
@@ -62,6 +67,7 @@
 
     /**
      * The color format is RGB888.
+     * @hide
      */
     public static final int COLOR_FORMAT_RGB888 = 1;
 
@@ -76,7 +82,7 @@
     public static final int ALGORITHM_NONE = 0;
 
     /**
-     * The compress algorithm is RLE.
+     * The compress algorithm is run length encoding (RLE).
      */
     public static final int ALGORITHM_RLE = 1;
 
@@ -115,6 +121,9 @@
      */
     private final int mThreshold;
 
+    /**
+     * Constructs AmbientBacklightSettings.
+     */
     public AmbientBacklightSettings(int source, int maxFps, int colorFormat,
             int horizontalZonesNumber, int verticalZonesNumber, boolean isLetterboxOmitted,
             int threshold) {
@@ -137,32 +146,57 @@
         mThreshold = in.readInt();
     }
 
+    /**
+     * Gets source of ambient backlight detection.
+     */
     @Source
     public int getSource() {
         return mSource;
     }
 
+    /**
+     * Gets max frames per second.
+     */
+    @IntRange(from = 1)
     public int getMaxFps() {
         return mMaxFps;
     }
 
-    @ColorFormat
+    /**
+     * Gets color format.
+     */
+    @PixelFormat.Format
     public int getColorFormat() {
         return mColorFormat;
     }
 
+    /**
+     * Gets the number of lights in each horizontal zone.
+     */
+    @IntRange(from = 0)
     public int getHorizontalZonesNumber() {
         return mHorizontalZonesNumber;
     }
 
+    /**
+     * Gets the number of lights in each vertical zone.
+     */
+    @IntRange(from = 0)
     public int getVerticalZonesNumber() {
         return mVerticalZonesNumber;
     }
 
+    /**
+     * Returns {@code true} if letter box is omitted; {@code false} otherwise.
+     * @hide
+     */
     public boolean isLetterboxOmitted() {
         return mIsLetterboxOmitted;
     }
 
+    /**
+     * @hide
+     */
     public int getThreshold() {
         return mThreshold;
     }
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
index 472d798..f07ef87 100644
--- a/media/java/android/media/quality/MediaQualityContract.java
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -33,6 +33,7 @@
      */
     public interface BaseParameters {
         String PARAMETER_ID = "_id";
+        String PARAMETER_TYPE = "_type";
         String PARAMETER_NAME = "_name";
         String PARAMETER_PACKAGE = "_package";
         String PARAMETER_INPUT_ID = "_input_id";
@@ -43,7 +44,7 @@
      * Parameters picture quality.
      * @hide
      */
-    public static final class PictureQuality {
+    public static final class PictureQuality implements BaseParameters {
         /**
          * The brightness.
          *
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 26d83ac..4d4526c 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -567,7 +567,6 @@
 
     /**
      * Registers a {@link AmbientBacklightCallback}.
-     * @hide
      */
     public void registerAmbientBacklightCallback(
             @NonNull @CallbackExecutor Executor executor,
@@ -581,7 +580,6 @@
 
     /**
      * Unregisters the existing {@link AmbientBacklightCallback}.
-     * @hide
      */
     public void unregisterAmbientBacklightCallback(
             @NonNull final AmbientBacklightCallback callback) {
@@ -602,7 +600,6 @@
      * Set the ambient backlight settings.
      *
      * @param settings The settings to use for the backlight detector.
-     * @hide
      */
     public void setAmbientBacklightSettings(
             @NonNull AmbientBacklightSettings settings) {
@@ -618,7 +615,6 @@
      * Enables or disables the ambient backlight detection.
      *
      * @param enabled {@code true} to enable, {@code false} to disable.
-     * @hide
      */
     public void setAmbientBacklightEnabled(boolean enabled) {
         try {
@@ -843,14 +839,12 @@
 
     /**
      * Callback used to monitor status of ambient backlight.
-     * @hide
      */
     public abstract static class AmbientBacklightCallback {
         /**
          * Called when new ambient backlight event is emitted.
-         * @hide
          */
-        public void onAmbientBacklightEvent(AmbientBacklightEvent event) {
+        public void onAmbientBacklightEvent(@NonNull AmbientBacklightEvent event) {
         }
     }
 }
diff --git a/media/java/android/media/quality/PictureProfileHandle.aidl b/media/java/android/media/quality/PictureProfileHandle.aidl
new file mode 100644
index 0000000..5d14631
--- /dev/null
+++ b/media/java/android/media/quality/PictureProfileHandle.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.quality;
+
+parcelable PictureProfileHandle;
diff --git a/media/java/android/media/quality/PictureProfileHandle.java b/media/java/android/media/quality/PictureProfileHandle.java
new file mode 100644
index 0000000..2b1cda4
--- /dev/null
+++ b/media/java/android/media/quality/PictureProfileHandle.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.quality;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+// TODO(b/337330263): Expose as public API after API review
+/**
+  * A type-safe handle to a picture profile, which represents a collection of parameters used to
+  * configure picture processing hardware to enhance the quality of graphic buffers.
+  * @hide
+  */
+@FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+public final class PictureProfileHandle implements Parcelable {
+    private final long mId;
+
+    @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+    public PictureProfileHandle(long id) {
+        mId = id;
+    }
+
+    @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+    public long getId() {
+        return mId;
+    }
+
+    @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeLong(mId);
+    }
+
+    @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+    @NonNull
+    public static final Creator<PictureProfileHandle> CREATOR =
+            new Creator<PictureProfileHandle>() {
+                @Override
+                public PictureProfileHandle createFromParcel(Parcel in) {
+                    return new PictureProfileHandle(in);
+                }
+
+                @Override
+                public PictureProfileHandle[] newArray(int size) {
+                    return new PictureProfileHandle[size];
+                }
+            };
+
+    private PictureProfileHandle(@NonNull Parcel in) {
+        mId = in.readLong();
+    }
+}
diff --git a/media/java/android/media/tv/TvInputServiceExtensionManager.java b/media/java/android/media/tv/TvInputServiceExtensionManager.java
index c514f6e..9442726 100644
--- a/media/java/android/media/tv/TvInputServiceExtensionManager.java
+++ b/media/java/android/media/tv/TvInputServiceExtensionManager.java
@@ -20,7 +20,9 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.StringDef;
+import android.annotation.SystemApi;
 import android.media.tv.flags.Flags;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -43,6 +45,7 @@
  *
  * @hide
  */
+@SystemApi
 @FlaggedApi(Flags.FLAG_TIF_EXTENSION_STANDARDIZATION)
 public final class TvInputServiceExtensionManager {
     private static final String TAG = "TvInputServiceExtensionManager";
@@ -186,8 +189,7 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface StandardizedExtensionName {}
     /**
-     * Interface responsible for creating scan session and obtain parameters.
-     * @hide
+     * Interface responsible for creating scan session and obtaining related parameters.
      */
     public static final String ISCAN_INTERFACE = SCAN_PACKAGE + "IScanInterface";
     /**
@@ -286,12 +288,10 @@
     public static final String ISCAN_SAT_SEARCH = SCAN_PACKAGE + "IScanSatSearch";
     /**
      * Interface for Over-the-Air Download.
-     * @hide
      */
     public static final String IOAD_UPDATE_INTERFACE = OAD_PACKAGE + "IOadUpdateInterface";
     /**
      * Interface for handling conditional access module app related information.
-     * @hide
      */
     public static final String ICAM_APP_INFO_SERVICE = CAM_PACKAGE + "ICamAppInfoService";
     /**
@@ -406,8 +406,7 @@
     public static final String IDOWNLOADABLE_RATING_TABLE_MONITOR = RATING_PACKAGE
             + "IDownloadableRatingTableMonitor";
     /**
-     * Interface for handling RRT rating related information.
-     * @hide
+     * Interface for handling Region Rating Table rating system related information.
      */
     public static final String IRATING_INTERFACE = RATING_PACKAGE + "IRatingInterface";
     /**
@@ -442,12 +441,10 @@
     public static final String IPROGRAM_INFO_LISTENER = RATING_PACKAGE + "IProgramInfoListener";
     /**
      * Interface for getting broadcast time related information.
-     * @hide
      */
     public static final String IBROADCAST_TIME = TIME_PACKAGE + "BroadcastTime";
     /**
      * Interface for handling data service signal information on teletext.
-     * @hide
      */
     public static final String IDATA_SERVICE_SIGNAL_INFO = TELETEXT_PACKAGE
             + "IDataServiceSignalInfo";
@@ -476,17 +473,14 @@
             + "IScanBackgroundServiceUpdateListener";
     /**
      * Interface for generating client token.
-     * @hide
      */
     public static final String ICLIENT_TOKEN = CLIENT_TOKEN_PACKAGE + "IClientToken";
     /**
      * Interfaces for handling screen mode information.
-     * @hide
      */
     public static final String ISCREEN_MODE_SETTINGS = SCREEN_MODE_PACKAGE + "IScreenModeSettings";
     /**
      * Interfaces for handling HDMI signal information update.
-     * @hide
      */
     public static final String IHDMI_SIGNAL_INTERFACE = SIGNAL_PACKAGE + "IHdmiSignalInterface";
     /**
@@ -529,7 +523,6 @@
     public static final String ISERVICE_LIST_EDIT = SERVICE_DATABASE_PACKAGE + "IServiceListEdit";
     /**
      * Interfaces for changes on service database updates.
-     * @hide
      */
     public static final String ISERVICE_LIST_EDIT_LISTENER = SERVICE_DATABASE_PACKAGE
             + "IServiceListEditListener";
@@ -587,8 +580,7 @@
     public static final String ICHANNEL_LIST_TRANSFER = SERVICE_DATABASE_PACKAGE
             + "IChannelListTransfer";
     /**
-     * Interfaces for record contents updates.
-     * @hide
+     * Interface for operations related to recorded contents.
      */
     public static final String IRECORDED_CONTENTS = PVR_PACKAGE + "IRecordedContents";
     /**
@@ -605,7 +597,6 @@
             + "IGetInfoRecordedContentsCallback";
     /**
      * Interfaces for monitoring present event information.
-     * @hide
      */
     public static final String IEVENT_MONITOR = EVENT_PACKAGE + "IEventMonitor";
     /**
@@ -784,20 +775,22 @@
     }
 
     /**
-     * This function should be used by OEM to register IBinder objects that implement
-     * standardized AIDL interfaces.
+     * Registers IBinder objects that implement standardized AIDL interfaces.
+     * <p>This function should be used by SoCs/OEMs
      *
      * @param extensionName Extension Interface Name
      * @param binder        IBinder object to be registered
-     * @return REGISTER_SUCCESS on success of registering IBinder object
-     *         REGISTER_FAIL_NAME_NOT_STANDARDIZED on failure due to registering extension with
-     *              non-standardized name
-     *         REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED on failure due to IBinder not
+     * @return {@link #REGISTER_SUCCESS} on success of registering IBinder object
+     *         {@link #REGISTER_FAIL_NAME_NOT_STANDARDIZED} on failure due to registering extension
+     *              with non-standardized name
+     *         {@link #REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED} on failure due to IBinder not
      *              implementing standardized AIDL interface
-     *         REGISTER_FAIL_REMOTE_EXCEPTION on failure due to remote exception
+     *         {@link #REGISTER_FAIL_REMOTE_EXCEPTION} on failure due to remote exception
      *
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
     @RegisterResult
     public int registerExtensionIBinder(@StandardizedExtensionName @NonNull String extensionName,
             @NonNull IBinder binder) {
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 4de6863..6441652 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -80,3 +80,11 @@
     description : "Feature flag to add setResourceHolderRetain api to MediaCas and Tuner JAVA."
     bug: "372973197"
 }
+
+flag {
+    name: "apply_picture_profiles"
+    is_exported: true
+    namespace: "media_tv"
+    description : "Feature flag to enable APIs for applying picture profiles"
+    bug: "337330263"
+}
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index c44e26f..f09dc72 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -104,6 +104,7 @@
         "libgrallocusage",
         "libmedia_midiiowrapper",
         "android.companion.virtualdevice.flags-aconfig-cc",
+        "android.media.codec-aconfig-cc",
         "android.media.playback.flags-aconfig-cc",
     ],
 
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 4492c85..fc184fe 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -39,6 +39,8 @@
 #include <C2Buffer.h>
 #include <C2PlatformSupport.h>
 
+#include <android_media_codec.h>
+
 #include <android/hardware/cas/native/1.0/IDescrambler.h>
 
 #include <android_runtime/android_hardware_HardwareBuffer.h>
@@ -189,6 +191,22 @@
     jmethodID setId;
 } gBufferInfo;
 
+static struct {
+    jclass clazz;
+    jmethodID ctorId;
+    jfieldID resourceId;
+    jfieldID capacityId;
+    jfieldID availableId;
+} gGlobalResourceInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID ctorId;
+    jfieldID resourceId;
+    jfieldID staticCountId;
+    jfieldID perFrameCountId;
+} gInstanceResourceInfo;
+
 struct fields_t {
     jmethodID postEventFromNativeID;
     jmethodID lockAndGetContextID;
@@ -1129,6 +1147,37 @@
     return mCodec->unsubscribeFromVendorParameters(names);
 }
 
+static jobject getJavaResources(
+        JNIEnv *env,
+        const std::vector<MediaCodec::InstanceResourceInfo>& resources) {
+    jobject resourcesObj = env->NewObject(gArrayListInfo.clazz, gArrayListInfo.ctorId);
+    for (const MediaCodec::InstanceResourceInfo& res : resources) {
+        ScopedLocalRef<jobject> object{env, env->NewObject(
+                gInstanceResourceInfo.clazz, gInstanceResourceInfo.ctorId)};
+        ScopedLocalRef<jstring> nameStr{env, env->NewStringUTF(res.mName.c_str())};
+        env->SetObjectField(object.get(), gInstanceResourceInfo.resourceId, nameStr.get());
+        env->SetLongField(object.get(),
+                          gInstanceResourceInfo.staticCountId,
+                          (jlong)res.mStaticCount);
+        env->SetLongField(object.get(),
+                          gInstanceResourceInfo.perFrameCountId,
+                          (jlong)res.mPerFrameCount);
+        (void)env->CallBooleanMethod(resourcesObj, gArrayListInfo.addId, object.get());
+    }
+
+    return resourcesObj;
+}
+
+status_t JMediaCodec::getRequiredResources(JNIEnv *env, jobject *resourcesObj) {
+    std::vector<MediaCodec::InstanceResourceInfo> resources;
+    status_t status = mCodec->getRequiredResources(resources);
+    if (status != OK) {
+        return status;
+    }
+    *resourcesObj = getJavaResources(env, resources);
+    return OK;
+}
+
 static jthrowable createCodecException(
         JNIEnv *env, status_t err, int32_t actionCode, const char *msg = NULL) {
     ScopedLocalRef<jclass> clazz(
@@ -1461,6 +1510,25 @@
             break;
         }
 
+        case MediaCodec::CB_METRICS_FLUSHED:
+        {
+            sp<WrapperObject<std::unique_ptr<mediametrics::Item>>> metrics;
+            CHECK(msg->findObject("metrics", (sp<RefBase>*)&metrics));
+
+            // metrics should never be null. Not sure if checking it here adds any value.
+            if (metrics == nullptr) {
+                return;
+            }
+
+            mediametrics::Item *item = metrics->value.get();
+            obj = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL);
+            break;
+        }
+        case MediaCodec::CB_REQUIRED_RESOURCES_CHANGED:
+        {
+            break;
+        }
+
         default:
             TRESPASS();
     }
@@ -3545,6 +3613,64 @@
     return;
 }
 
+static jobject getJavaResources(
+        JNIEnv *env,
+        const std::vector<MediaCodec::GlobalResourceInfo>& resources) {
+    jobject resourcesObj = env->NewObject(gArrayListInfo.clazz, gArrayListInfo.ctorId);
+    for (const MediaCodec::GlobalResourceInfo& res : resources) {
+        ScopedLocalRef<jobject> object{env, env->NewObject(
+                gGlobalResourceInfo.clazz, gGlobalResourceInfo.ctorId)};
+        ScopedLocalRef<jstring> nameStr{env, env->NewStringUTF(res.mName.c_str())};
+        env->SetObjectField(object.get(), gInstanceResourceInfo.resourceId, nameStr.get());
+        env->SetLongField(object.get(), gGlobalResourceInfo.capacityId, (jlong)res.mCapacity);
+        env->SetLongField(object.get(), gGlobalResourceInfo.availableId, (jlong)res.mAvailable);
+        (void)env->CallBooleanMethod(resourcesObj, gArrayListInfo.addId, object.get());
+    }
+
+    return resourcesObj;
+}
+
+static jobject android_media_MediaCodec_getGloballyAvailableResources(
+        JNIEnv *env, jobject thiz) {
+    (void)thiz;
+    std::vector<MediaCodec::GlobalResourceInfo> resources;
+    status_t status = MediaCodec::getGloballyAvailableResources(resources);
+    if (status != OK) {
+        if (status == ERROR_UNSUPPORTED) {
+            jniThrowException(env, "java/lang/UnsupportedOperationException",
+                              "Function Not Implemented");
+        } else {
+            throwExceptionAsNecessary(env, status, nullptr);
+        }
+        return nullptr;
+    }
+
+    return getJavaResources(env, resources);
+}
+
+static jobject android_media_MediaCodec_getRequiredResources(
+        JNIEnv *env, jobject thiz) {
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+    if (codec == nullptr || codec->initCheck() != OK) {
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
+        return nullptr;
+    }
+
+    jobject ret = nullptr;
+    status_t status = codec->getRequiredResources(env, &ret);
+    if (status != OK) {
+        if (status == ERROR_UNSUPPORTED) {
+            jniThrowException(env, "java/lang/UnsupportedOperationException",
+                              "Function Not Implemented");
+        } else {
+            throwExceptionAsNecessary(env, status, nullptr);
+        }
+        return nullptr;
+    }
+
+    return ret;
+}
+
 static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) {
     ScopedLocalRef<jclass> clazz(
             env, env->FindClass("android/media/MediaCodec"));
@@ -3890,6 +4016,36 @@
     gFields.bufferInfoOffset = env->GetFieldID(clazz.get(), "offset", "I");
     gFields.bufferInfoPresentationTimeUs =
             env->GetFieldID(clazz.get(), "presentationTimeUs", "J");
+
+    // Since these TestApis are defined under the flag, make sure they are
+    // accessed only when the flag is set.
+    if (android::media::codec::codec_availability()) {
+        clazz.reset(env->FindClass("android/media/MediaCodec$GlobalResourceInfo"));
+        CHECK(clazz.get() != NULL);
+        gGlobalResourceInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+        gGlobalResourceInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+        CHECK(gGlobalResourceInfo.ctorId != NULL);
+        gGlobalResourceInfo.resourceId =
+                env->GetFieldID(clazz.get(), "mName", "Ljava/lang/String;");
+        CHECK(gGlobalResourceInfo.resourceId != NULL);
+        gGlobalResourceInfo.capacityId = env->GetFieldID(clazz.get(), "mCapacity", "J");
+        CHECK(gGlobalResourceInfo.capacityId != NULL);
+        gGlobalResourceInfo.availableId = env->GetFieldID(clazz.get(), "mAvailable", "J");
+        CHECK(gGlobalResourceInfo.availableId != NULL);
+
+        clazz.reset(env->FindClass("android/media/MediaCodec$InstanceResourceInfo"));
+        CHECK(clazz.get() != NULL);
+        gInstanceResourceInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+        gInstanceResourceInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+        CHECK(gInstanceResourceInfo.ctorId != NULL);
+        gInstanceResourceInfo.resourceId =
+                env->GetFieldID(clazz.get(), "mName", "Ljava/lang/String;");
+        CHECK(gInstanceResourceInfo.resourceId != NULL);
+        gInstanceResourceInfo.staticCountId= env->GetFieldID(clazz.get(), "mStaticCount", "J");
+        CHECK(gInstanceResourceInfo.staticCountId != NULL);
+        gInstanceResourceInfo.perFrameCountId = env->GetFieldID(clazz.get(), "mPerFrameCount", "J");
+        CHECK(gInstanceResourceInfo.perFrameCountId != NULL);
+    }
 }
 
 static void android_media_MediaCodec_native_setup(
@@ -4246,6 +4402,12 @@
 
     { "native_finalize", "()V",
       (void *)android_media_MediaCodec_native_finalize },
+
+    { "native_getGloballyAvailableResources", "()Ljava/util/List;",
+      (void *)android_media_MediaCodec_getGloballyAvailableResources},
+
+    { "native_getRequiredResources", "()Ljava/util/List;",
+      (void *)android_media_MediaCodec_getRequiredResources},
 };
 
 static const JNINativeMethod gLinearBlockMethods[] = {
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index c9b6b7f6..930dbbe 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -185,6 +185,8 @@
 
     status_t unsubscribeFromVendorParameters(JNIEnv *env, jobject names);
 
+    status_t getRequiredResources(JNIEnv *env, jobject *resourcesObj);
+
     bool hasCryptoOrDescrambler() { return mHasCryptoOrDescrambler; }
 
     const sp<ICrypto> &getCrypto() { return mCrypto; }
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index a23845f..c25f77b 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -107,6 +107,7 @@
     method public void onRfDiscoveryStarted(boolean);
     method public void onRfFieldActivated(boolean);
     method public void onRoutingChanged();
+    method public void onRoutingTableFull();
     method public void onStateUpdated(int);
     method public void onTagConnected(boolean);
     method public void onTagDispatch(@NonNull java.util.function.Consumer<java.lang.Boolean>);
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index b102e87..fb793b0 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -52,5 +52,6 @@
    void onNdefMessage(in Tag tag, in NdefMessage message, in ResultReceiver hasOemExecutableContent);
    void onLaunchHceAppChooserActivity(in String selectedAid, in List<ApduServiceInfo> services, in ComponentName failedComponent, in String category);
    void onLaunchHceTapAgainActivity(in ApduServiceInfo service, in String category);
+   void onRoutingTableFull();
    void onLogEventNotified(in OemLogItems item);
 }
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index abd99bc..57ee981 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -394,6 +394,13 @@
         void onLaunchHceTapAgainDialog(@NonNull ApduServiceInfo service, @NonNull String category);
 
         /**
+         * Callback to indicate that routing table is full and the OEM can optionally launch a
+         * dialog to request the user to remove some Card Emulation apps from the device to free
+         * routing table space.
+         */
+        void onRoutingTableFull();
+
+        /**
          * Callback when OEM specified log event are notified.
          * @param item the log items that contains log information of NFC event.
          */
@@ -853,6 +860,12 @@
                     handleVoidCallback(enabled, cb::onReaderOptionChanged, ex));
         }
 
+        public void onRoutingTableFull() throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(null,
+                            (Object input) -> cb.onRoutingTableFull(), ex));
+        }
+
         @Override
         public void onGetOemAppSearchIntent(List<String> packages, ResultReceiver intentConsumer)
                 throws RemoteException {
diff --git a/nfc/java/android/nfc/OemLogItems.java b/nfc/java/android/nfc/OemLogItems.java
index 6671941..4f3e199 100644
--- a/nfc/java/android/nfc/OemLogItems.java
+++ b/nfc/java/android/nfc/OemLogItems.java
@@ -142,8 +142,11 @@
         dest.writeByteArray(mCommandApdus);

         dest.writeInt(mResponseApdus.length);

         dest.writeByteArray(mResponseApdus);

-        dest.writeLong(mRfFieldOnTime.getEpochSecond());

-        dest.writeInt(mRfFieldOnTime.getNano());

+        dest.writeBoolean(mRfFieldOnTime != null);

+        if (mRfFieldOnTime != null) {

+            dest.writeLong(mRfFieldOnTime.getEpochSecond());

+            dest.writeInt(mRfFieldOnTime.getNano());

+        }

         dest.writeParcelable(mTag, 0);

     }

 

@@ -305,7 +308,12 @@
         in.readByteArray(this.mCommandApdus);

         this.mResponseApdus = new byte[in.readInt()];

         in.readByteArray(this.mResponseApdus);

-        this.mRfFieldOnTime = Instant.ofEpochSecond(in.readLong(), in.readInt());

+        boolean isRfFieldOnTimeSet = in.readBoolean();

+        if (isRfFieldOnTimeSet) {

+            this.mRfFieldOnTime = Instant.ofEpochSecond(in.readLong(), in.readInt());

+        } else {

+            this.mRfFieldOnTime = null;

+        }

         this.mTag = in.readParcelable(Tag.class.getClassLoader(), Tag.class);

     }

 

diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index b2dcb7f..a3da93d 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -61,9 +61,11 @@
         "SettingsLibUtils",
         "SettingsLibZeroStatePreference",
         "settingslib_media_flags_lib",
+    ],
+    libs:[
+        // This flag library has been added in frameworks jar
         "aconfig_settingslib_flags_java_lib",
     ],
-
     plugins: ["androidx.room_room-compiler-plugin"],
     use_resource_processor: true,
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt
index bde4217..a2b826a 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt
@@ -166,10 +166,6 @@
         }
         return null
     }
-
-    /** Returns all the [PreferenceHierarchyNode]s appear in the hierarchy. */
-    fun getAllPreferences(): List<PreferenceHierarchyNode> =
-        mutableListOf<PreferenceHierarchyNode>().apply { forEachRecursively { add(it) } }
 }
 
 /**
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 49acc1d..6b7be91 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
@@ -108,6 +108,9 @@
     }
 }
 
+/** Interface indicates that a virtual [Preference] should be created for binding. */
+interface PreferenceBindingPlaceholder
+
 /** Abstract preference screen to provide preference hierarchy and binding factory. */
 interface PreferenceScreenCreator : PreferenceScreenMetadata, PreferenceScreenProvider {
 
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index fbe8927..cfe6089 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -218,34 +218,47 @@
             preferenceScreen: PreferenceScreen,
             preferenceBindingFactory: PreferenceBindingFactory,
             preferenceHierarchy: PreferenceHierarchy,
-        ) =
-            preferenceScreen.bindRecursively(
-                preferenceBindingFactory,
-                preferenceHierarchy.getAllPreferences().associateBy { it.metadata.key },
-            )
-
-        private fun PreferenceGroup.bindRecursively(
-            preferenceBindingFactory: PreferenceBindingFactory,
-            preferences: Map<String, PreferenceHierarchyNode>,
-            storages: MutableMap<KeyValueStore, PreferenceDataStore> = mutableMapOf(),
         ) {
-            preferences[key]?.let { preferenceBindingFactory.bind(this, it) }
-            val count = preferenceCount
-            for (index in 0 until count) {
-                val preference = getPreference(index)
-                if (preference is PreferenceGroup) {
-                    preference.bindRecursively(preferenceBindingFactory, preferences, storages)
-                } else {
-                    preferences[preference.key]?.let {
-                        val metadata = it.metadata
-                        (metadata as? PersistentPreference<*>)?.storage(context)?.let { storage ->
-                            preference.preferenceDataStore =
-                                storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) }
+            val preferences = mutableMapOf<String, PreferenceHierarchyNode>()
+            preferenceHierarchy.forEachRecursively {
+                val metadata = it.metadata
+                preferences[metadata.key] = it
+            }
+            val storages = mutableMapOf<KeyValueStore, PreferenceDataStore>()
+
+            fun Preference.setPreferenceDataStore(metadata: PreferenceMetadata) {
+                (metadata as? PersistentPreference<*>)?.storage(context)?.let { storage ->
+                    preferenceDataStore =
+                        storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) }
+                }
+            }
+
+            fun PreferenceGroup.bindRecursively() {
+                preferences.remove(key)?.let { preferenceBindingFactory.bind(this, it) }
+                val count = preferenceCount
+                for (index in 0 until count) {
+                    val preference = getPreference(index)
+                    if (preference is PreferenceGroup) {
+                        preference.bindRecursively()
+                    } else {
+                        preferences.remove(preference.key)?.let {
+                            preference.setPreferenceDataStore(it.metadata)
+                            preferenceBindingFactory.bind(preference, it)
                         }
-                        preferenceBindingFactory.bind(preference, it)
                     }
                 }
             }
+
+            preferenceScreen.bindRecursively()
+            for (node in preferences.values) {
+                val metadata = node.metadata
+                val binding = preferenceBindingFactory.getPreferenceBinding(metadata)
+                if (binding !is PreferenceBindingPlaceholder) continue
+                val preference = binding.createWidget(preferenceScreen.context)
+                preference.setPreferenceDataStore(metadata)
+                preferenceBindingFactory.bind(preference, node, binding)
+                preferenceScreen.addPreference(preference)
+            }
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
index 4d3a78a5..f2bc380 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
@@ -17,8 +17,12 @@
 package com.android.settingslib.spa.gallery.ui
 
 import android.os.Bundle
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -30,6 +34,7 @@
 import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.android.settingslib.spa.widget.ui.Category
+import com.android.settingslib.spa.widget.ui.LazyCategory
 
 private const val TITLE = "Sample Category"
 
@@ -65,7 +70,7 @@
         )
         entryList.add(
             SettingsEntryBuilder.create("Preference 3", owner)
-                .setMacro { SimplePreferenceMacro(title = "Preference 2", summary = "Summary 3") }
+                .setMacro { SimplePreferenceMacro(title = "Preference 3", summary = "Summary 3") }
                 .build()
         )
         entryList.add(
@@ -88,6 +93,13 @@
                 entries[2].UiLayout()
                 entries[3].UiLayout()
             }
+            Column(Modifier.height(200.dp)) {
+                LazyCategory(
+                    list = entries,
+                    entry = { index: Int -> @Composable { entries[index].UiLayout() } },
+                    title = { index: Int -> if (index == 0 || index == 2) "LazyCategory" else null },
+                ) {}
+            }
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
index 66680fa..28b2b4a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
@@ -19,8 +19,13 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.TouchApp
 import androidx.compose.material3.MaterialTheme
@@ -34,6 +39,7 @@
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsShape
@@ -98,6 +104,57 @@
     }
 }
 
+/**
+ * A container that is used to group items with lazy loading.
+ *
+ * @param list The list of items to display.
+ * @param entry The entry for each list item according to its index in list.
+ * @param key Optional. The key for each item in list to provide unique item identifiers, making
+ * the list more efficient.
+ * @param title Optional. Category title for each item or each group of items in the list. It
+ * should be decided by the index.
+ * @param bottomPadding Optional. Bottom outside padding of the category.
+ * @param state Optional. State of LazyList.
+ * @param content Optional. Content to be shown at the top of the category.
+ */
+
+@Composable
+fun LazyCategory(
+    list: List<Any>,
+    entry: (Int) -> @Composable () -> Unit,
+    key: ((Int) -> Any)? = null,
+    title: ((Int) -> String?)? = null,
+    bottomPadding: Dp = SettingsDimension.paddingSmall,
+    state: LazyListState = rememberLazyListState(),
+    content: @Composable () -> Unit,
+) {
+    Column(
+        Modifier.padding(
+                PaddingValues(
+                    start = SettingsDimension.paddingLarge,
+                    end = SettingsDimension.paddingLarge,
+                    top = SettingsDimension.paddingSmall,
+                    bottom = bottomPadding,
+                )
+            )
+            .clip(SettingsShape.CornerMedium2)
+    ) {
+        LazyColumn(
+            modifier = Modifier.fillMaxSize(),
+            verticalArrangement = Arrangement.spacedBy(SettingsDimension.paddingTiny),
+            state = state,
+        ) {
+            item { content() }
+
+            items(count = list.size, key = key) {
+                title?.invoke(it)?.let { title -> CategoryTitle(title) }
+                val entryPreference = entry(it)
+                entryPreference()
+            }
+        }
+    }
+}
+
 @Preview
 @Composable
 private fun CategoryPreview() {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
index 09a6e6d..4b4a8c2 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
@@ -16,10 +16,16 @@
 
 package com.android.settingslib.spa.widget.ui
 
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -30,14 +36,11 @@
 @RunWith(AndroidJUnit4::class)
 class CategoryTest {
 
-    @get:Rule
-    val composeTestRule = createComposeRule()
+    @get:Rule val composeTestRule = createComposeRule()
 
     @Test
     fun categoryTitle() {
-        composeTestRule.setContent {
-            CategoryTitle(title = "CategoryTitle")
-        }
+        composeTestRule.setContent { CategoryTitle(title = "CategoryTitle") }
 
         composeTestRule.onNodeWithText("CategoryTitle").assertIsDisplayed()
     }
@@ -46,12 +49,14 @@
     fun category_hasContent_titleDisplayed() {
         composeTestRule.setContent {
             Category(title = "CategoryTitle") {
-                Preference(remember {
-                    object : PreferenceModel {
-                        override val title = "Some Preference"
-                        override val summary = { "Some summary" }
+                Preference(
+                    remember {
+                        object : PreferenceModel {
+                            override val title = "Some Preference"
+                            override val summary = { "Some summary" }
+                        }
                     }
-                })
+                )
             }
         }
 
@@ -60,10 +65,45 @@
 
     @Test
     fun category_noContent_titleNotDisplayed() {
-        composeTestRule.setContent {
-            Category(title = "CategoryTitle") {}
-        }
+        composeTestRule.setContent { Category(title = "CategoryTitle") {} }
 
         composeTestRule.onNodeWithText("CategoryTitle").assertDoesNotExist()
     }
+
+    @Test
+    fun lazyCategory_content_displayed() {
+        composeTestRule.setContent { TestLazyCategory() }
+
+        composeTestRule.onNodeWithText("text").assertExists()
+    }
+
+    @Test
+    fun lazyCategory_title_displayed() {
+        composeTestRule.setContent { TestLazyCategory() }
+
+        composeTestRule.onNodeWithText("LazyCategory 0").assertExists()
+        composeTestRule.onNodeWithText("LazyCategory 1").assertDoesNotExist()
+    }
+}
+
+@Composable
+private fun TestLazyCategory() {
+    val list: List<PreferenceModel> =
+        listOf(
+            object : PreferenceModel {
+                override val title = "title"
+            },
+            object : PreferenceModel {
+                override val title = "title"
+            },
+        )
+    Column(Modifier.height(200.dp)) {
+        LazyCategory(
+            list = list,
+            entry = { index: Int -> @Composable { Preference(list[index]) } },
+            title = { index: Int -> if (index == 0) "LazyCategory $index" else null },
+        ) {
+            Text("text")
+        }
+    }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index bededf0..2a214b6 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -37,7 +37,9 @@
 import com.android.settingslib.spa.framework.compose.LogCompositions
 import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
 import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
 import com.android.settingslib.spa.widget.ui.CategoryTitle
+import com.android.settingslib.spa.widget.ui.LazyCategory
 import com.android.settingslib.spa.widget.ui.PlaceholderTitle
 import com.android.settingslib.spa.widget.ui.Spinner
 import com.android.settingslib.spa.widget.ui.SpinnerOption
@@ -55,19 +57,14 @@
 private const val TAG = "AppList"
 private const val CONTENT_TYPE_HEADER = "header"
 
-/**
- * The config used to load the App List.
- */
+/** The config used to load the App List. */
 data class AppListConfig(
     val userIds: List<Int>,
     val showInstantApps: Boolean,
     val matchAnyUserForAdmin: Boolean,
 )
 
-data class AppListState(
-    val showSystem: () -> Boolean,
-    val searchQuery: () -> String,
-)
+data class AppListState(val showSystem: () -> Boolean, val searchQuery: () -> String)
 
 data class AppListInput<T : AppRecord>(
     val config: AppListConfig,
@@ -90,7 +87,7 @@
 
 @Composable
 internal fun <T : AppRecord> AppListInput<T>.AppListImpl(
-    viewModelSupplier: @Composable () -> IAppListViewModel<T>,
+    viewModelSupplier: @Composable () -> IAppListViewModel<T>
 ) {
     LogCompositions(TAG, config.userIds.toString())
     val viewModel = viewModelSupplier()
@@ -125,7 +122,7 @@
     appListData: State<AppListData<T>?>,
     header: @Composable () -> Unit,
     bottomPadding: Dp,
-    noItemMessage: String?
+    noItemMessage: String?,
 ) {
     val timeMeasurer = rememberTimeMeasurer(TAG)
     appListData.value?.let { (list, option) ->
@@ -135,40 +132,61 @@
             PlaceholderTitle(noItemMessage ?: stringResource(R.string.no_applications))
             return
         }
-        LazyColumn(
-            modifier = Modifier.fillMaxSize(),
-            state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
-            contentPadding = PaddingValues(bottom = bottomPadding),
-        ) {
-            item(contentType = CONTENT_TYPE_HEADER) {
+        if (isSpaExpressiveEnabled) {
+            LazyCategory(
+                list = list,
+                entry = { index: Int ->
+                    @Composable {
+                        val appEntry = list[index]
+                        val summary = getSummary(option, appEntry.record) ?: { "" }
+                        remember(appEntry) {
+                                AppListItemModel(appEntry.record, appEntry.label, summary)
+                            }
+                            .AppItem()
+                    }
+                },
+                key = { index: Int -> list[index].record.itemKey(option) },
+                title = { index: Int -> getGroupTitle(option, list[index].record) },
+                bottomPadding = bottomPadding,
+                state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
+            ) {
                 header()
             }
+        } else {
+            LazyColumn(
+                modifier = Modifier.fillMaxSize(),
+                state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
+                contentPadding = PaddingValues(bottom = bottomPadding),
+            ) {
+                item(contentType = CONTENT_TYPE_HEADER) { header() }
 
-            items(count = list.size, key = { list[it].record.itemKey(option) }) {
-                remember(list) { getGroupTitleIfFirst(option, list, it) }
-                    ?.let { group -> CategoryTitle(title = group) }
+                items(count = list.size, key = { list[it].record.itemKey(option) }) {
+                    remember(list) { getGroupTitleIfFirst(option, list, it) }
+                        ?.let { group -> CategoryTitle(title = group) }
 
-                val appEntry = list[it]
-                val summary = getSummary(option, appEntry.record) ?: { "" }
-                remember(appEntry) {
-                    AppListItemModel(appEntry.record, appEntry.label, summary)
-                }.AppItem()
+                    val appEntry = list[it]
+                    val summary = getSummary(option, appEntry.record) ?: { "" }
+                    remember(appEntry) {
+                            AppListItemModel(appEntry.record, appEntry.label, summary)
+                        }
+                        .AppItem()
+                }
             }
         }
     }
 }
 
-private fun <T : AppRecord> T.itemKey(option: Int) =
-    listOf(option, app.packageName, app.userId)
+private fun <T : AppRecord> T.itemKey(option: Int) = listOf(option, app.packageName, app.userId)
 
 /** Returns group title if this is the first item of the group. */
 private fun <T : AppRecord> AppListModel<T>.getGroupTitleIfFirst(
     option: Int,
     list: List<AppEntry<T>>,
     index: Int,
-): String? = getGroupTitle(option, list[index].record)?.takeIf {
-    index == 0 || it != getGroupTitle(option, list[index - 1].record)
-}
+): String? =
+    getGroupTitle(option, list[index].record)?.takeIf {
+        index == 0 || it != getGroupTitle(option, list[index - 1].record)
+    }
 
 @Composable
 private fun <T : AppRecord> rememberViewModel(
@@ -183,16 +201,19 @@
     viewModel.searchQuery.Sync(state.searchQuery)
 
     LifecycleEffect(onStart = { viewModel.reloadApps() })
-    val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply {
-        addAction(Intent.ACTION_PACKAGE_REMOVED)
-        addAction(Intent.ACTION_PACKAGE_CHANGED)
-        addDataScheme("package")
-    }
+    val intentFilter =
+        IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply {
+            addAction(Intent.ACTION_PACKAGE_REMOVED)
+            addAction(Intent.ACTION_PACKAGE_CHANGED)
+            addDataScheme("package")
+        }
     for (userId in config.userIds) {
         DisposableBroadcastReceiverAsUser(
             intentFilter = intentFilter,
             userHandle = UserHandle.of(userId),
-        ) { viewModel.reloadApps() }
+        ) {
+            viewModel.reloadApps()
+        }
     }
     return viewModel
 }
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 81a2e6a..bf419cc 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -74,16 +74,6 @@
 }
 
 flag {
-    name: "volume_panel_broadcast_fix"
-    namespace: "systemui"
-    description: "Make the volume panel's repository listen for the new ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED broadcast instead of ACTION_NOTIFICATION_POLICY_CHANGED"
-    bug: "347707024"
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
-}
-
-flag {
     name: "volume_dialog_audio_sharing_fix"
     namespace: "cross_device_experiences"
     description: "Gates whether to show separate volume bars during audio sharing"
@@ -111,6 +101,14 @@
 }
 
 flag {
+    name: "write_system_preference_permission_enabled"
+    is_fixed_read_only: true
+    namespace: "android_settings"
+    description: "Enable WRITE_SYSTEM_PREFERENCE permission and appop"
+    bug: "375193223"
+}
+
+flag {
   name: "asha_profile_access_profile_enabled_true"
   namespace: "accessibility"
   description: "Changes the return value of HearingAidProfile.accessProfileEnabled() to true"
@@ -166,3 +164,10 @@
     description: "Enable the ambient volume control in device details and hearing devices dialog."
     bug: "357878944"
 }
+
+flag {
+    name: "settings_preference_write_consent_enabled"
+    namespace: "android_settings"
+    description: "Enable the user consent prompt before writing sensitive preferences via service"
+    bug: "378552675"
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index bc144d6..f03014c 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -821,8 +821,10 @@
 
     <!-- Title of checkbox setting that enables the Linux terminal app. [CHAR LIMIT=32] -->
     <string name="enable_linux_terminal_title">Linux development environment</string>
-    <!-- Summary of checkbox setting that enables the Linux terminal app. [CHAR LIMIT=64] -->
-    <string name="enable_linux_terminal_summary">Run Linux terminal on Android</string>
+    <!-- Summary of checkbox setting that enables the Linux terminal app. [CHAR LIMIT=none] -->
+    <string name="enable_linux_terminal_summary">(Experimental) Run Linux terminal on Android</string>
+    <!-- Disclaimer below the checkbox that disabling the Linux terminal app would clear its data. [CHAR LIMIT=none] -->
+    <string name="disable_linux_terminal_disclaimer">If you disable, Linux terminal data will be cleared</string>
 
     <!-- HDCP checking title, used for debug purposes only. [CHAR LIMIT=25] -->
     <string name="hdcp_checking_title">HDCP checking</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index a87b815..216574a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -1,6 +1,7 @@
 package com.android.settingslib.bluetooth;
 
 import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.UNKNOWN_VALUE_PLACEHOLDER;
+import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
 import static com.android.settingslib.widget.AdaptiveOutlineDrawable.ICON_TYPE_ADVANCED;
 
 import android.annotation.SuppressLint;
@@ -651,6 +652,13 @@
                 context.getContentResolver()));
     }
 
+    /** Returns if the le audio sharing hysteresis mode fix is available. */
+    @WorkerThread
+    public static boolean isAudioSharingHysteresisModeFixAvailable(@Nullable Context context) {
+        return (audioSharingHysteresisModeFix() && Flags.enableLeAudioSharing())
+                || (context != null && isAudioSharingPreviewEnabled(context.getContentResolver()));
+    }
+
     /** Returns if the le audio sharing is enabled. */
     public static boolean isAudioSharingEnabled() {
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -733,13 +741,15 @@
     @WorkerThread
     public static boolean hasConnectedBroadcastSourceForBtDevice(
             @Nullable BluetoothDevice device, @Nullable LocalBluetoothManager localBtManager) {
-        if (Flags.audioSharingHysteresisModeFix()) {
+        if (localBtManager == null) {
+            Log.d(TAG, "Skip check hasConnectedBroadcastSourceForBtDevice due to arg is null");
+            return false;
+        }
+        if (isAudioSharingHysteresisModeFixAvailable(localBtManager.getContext())) {
             return hasActiveLocalBroadcastSourceForBtDevice(device, localBtManager);
         }
         LocalBluetoothLeBroadcastAssistant assistant =
-                localBtManager == null
-                        ? null
-                        : localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+                localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
         if (device == null || assistant == null) {
             Log.d(TAG, "Skip check hasConnectedBroadcastSourceForBtDevice due to arg is null");
             return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 8851e14..dc52b4d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -56,7 +56,6 @@
 import androidx.annotation.RequiresApi;
 
 import com.android.settingslib.R;
-import com.android.settingslib.flags.Flags;
 
 import com.google.common.collect.ImmutableList;
 
@@ -1142,7 +1141,7 @@
         int targetGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
         int fallbackActiveGroupId = BluetoothUtils.getPrimaryGroupIdForBroadcast(
                 mContext.getContentResolver());
-        if (Flags.audioSharingHysteresisModeFix()) {
+        if (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)) {
             int userPreferredPrimaryGroupId = getUserPreferredPrimaryGroupId();
             if (userPreferredPrimaryGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
                     && deviceGroupsInBroadcast.containsKey(userPreferredPrimaryGroupId)) {
@@ -1183,7 +1182,8 @@
 
     @NonNull
     private Map<Integer, List<BluetoothDevice>> getDeviceGroupsInBroadcast() {
-        boolean hysteresisModeFixEnabled = Flags.audioSharingHysteresisModeFix();
+        boolean hysteresisModeFixEnabled =
+                BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext);
         List<BluetoothDevice> connectedDevices = mServiceBroadcastAssistant.getConnectedDevices();
         return connectedDevices.stream()
                 .filter(
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
index 7fdbcda..f446bb8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
@@ -93,33 +93,23 @@
                     IntentFilter().apply {
                         addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED)
                         addAction(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED)
-                        if (Flags.volumePanelBroadcastFix() && android.app.Flags.modesApi())
+                        if (android.app.Flags.modesApi())
                             addAction(
-                                NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED)
+                                NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED
+                            )
                     },
                     /* broadcastPermission = */ null,
-                    /* scheduler = */ if (Flags.volumePanelBroadcastFix()) {
-                        backgroundHandler
-                    } else {
-                        null
-                    },
+                    /* scheduler = */ backgroundHandler,
                 )
 
                 awaitClose { context.unregisterReceiver(receiver) }
             }
-            .let {
-                if (Flags.volumePanelBroadcastFix()) {
-                    // Share the flow to avoid having multiple broadcasts.
-                    it.flowOn(backgroundCoroutineContext)
-                        .shareIn(started = SharingStarted.WhileSubscribed(), scope = scope)
-                } else {
-                    it.shareIn(started = SharingStarted.WhileSubscribed(), scope = scope)
-                }
-            }
+            .flowOn(backgroundCoroutineContext)
+            .shareIn(started = SharingStarted.WhileSubscribed(), scope = scope)
     }
 
     override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> by lazy {
-        if (Flags.volumePanelBroadcastFix() && android.app.Flags.modesApi())
+        if (android.app.Flags.modesApi())
             flowFromBroadcast(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED) {
                 // If available, get the value from extras to avoid a potential binder call.
                 it?.extras?.getParcelable(EXTRA_NOTIFICATION_POLICY)
@@ -161,11 +151,13 @@
                     contentResolver.registerContentObserver(
                         Settings.Global.getUriFor(Settings.Global.ZEN_MODE),
                         /* notifyForDescendants= */ false,
-                        observer)
+                        observer,
+                    )
                     contentResolver.registerContentObserver(
                         Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG),
                         /* notifyForDescendants= */ false,
-                        observer)
+                        observer,
+                    )
 
                     awaitClose { contentResolver.unregisterContentObserver(observer) }
                 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 6d481db..fa5d542 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -1256,4 +1256,40 @@
 
         assertThat(BluetoothUtils.isAudioSharingUIAvailable(mContext)).isTrue();
     }
+
+    @Test
+    public void isAudioSharingHysteresisModeFixAvailable_mainAndPreviewFlagOff_returnsFalse() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+
+        assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isFalse();
+    }
+
+    @Test
+    public void isAudioSharingHysteresisModeFixAvailable_hysteresisFixFlagOff_returnsFalse() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+
+        assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isFalse();
+    }
+
+    @Test
+    public void isAudioSharingHysteresisModeFixAvailable_previewFlagOn_returnsTrue() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+
+        assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isTrue();
+    }
+
+    @Test
+    public void isAudioSharingHysteresisModeFixAvailable_mainAndPreviewFlagOn_returnsTrue() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+
+        assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isTrue();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
index c136644..388af61 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
@@ -23,12 +23,10 @@
 import android.content.Intent
 import android.database.ContentObserver
 import android.os.Parcelable
-import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import android.provider.Settings.Global
 import androidx.test.filters.SmallTest
-import com.android.settingslib.flags.Flags
 import com.android.settingslib.notification.modes.TestModeBuilder
 import com.android.settingslib.notification.modes.ZenMode
 import com.android.settingslib.notification.modes.ZenModesBackend
@@ -93,26 +91,7 @@
             )
     }
 
-    @DisableFlags(Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX)
-    @Test
-    fun consolidatedPolicyChanges_repositoryEmits_flagsOff() {
-        testScope.runTest {
-            val values = mutableListOf<NotificationManager.Policy?>()
-            `when`(notificationManager.consolidatedNotificationPolicy).thenReturn(testPolicy1)
-            underTest.consolidatedNotificationPolicy
-                .onEach { values.add(it) }
-                .launchIn(backgroundScope)
-            runCurrent()
-
-            `when`(notificationManager.consolidatedNotificationPolicy).thenReturn(testPolicy2)
-            triggerIntent(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED)
-            runCurrent()
-
-            assertThat(values).containsExactly(null, testPolicy1, testPolicy2).inOrder()
-        }
-    }
-
-    @EnableFlags(android.app.Flags.FLAG_MODES_API, Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX)
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
     @Test
     fun consolidatedPolicyChanges_repositoryEmits_flagsOn() {
         testScope.runTest {
@@ -131,7 +110,7 @@
         }
     }
 
-    @EnableFlags(android.app.Flags.FLAG_MODES_API, Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX)
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
     @Test
     fun consolidatedPolicyChanges_repositoryEmitsFromExtras() {
         testScope.runTest {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index fbce6ca..aca2c4e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -16,6 +16,7 @@
 
 package com.android.providers.settings;
 
+import static android.provider.DeviceConfig.DUMP_ARG_NAMESPACE;
 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_NONE;
 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT;
 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT;
@@ -42,6 +43,7 @@
 import android.provider.Settings;
 import android.provider.Settings.Config.SyncDisabledMode;
 import android.provider.UpdatableDeviceConfigServiceReadiness;
+import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.util.FastPrintWriter;
@@ -55,11 +57,13 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Pattern;
 
 /**
  * Receives shell commands from the command line related to device config flags, and dispatches them
@@ -80,6 +84,7 @@
     final SettingsProvider mProvider;
 
     private static final String TAG = "DeviceConfigService";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     public DeviceConfigService(SettingsProvider provider) {
         mProvider = provider;
@@ -97,14 +102,35 @@
         }
     }
 
+    // TODO(b/364399200): add unit test
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        String filter = null;
         if (android.provider.flags.Flags.dumpImprovements()) {
-            pw.print("SyncDisabledForTests: ");
-            MyShellCommand.getSyncDisabledForTests(pw, pw);
+            if (args.length > 0) {
+                switch (args[0]) {
+                    case DUMP_ARG_NAMESPACE:
+                        if (args.length < 2) {
+                            throw new IllegalArgumentException("argument " + DUMP_ARG_NAMESPACE
+                                    + " requires an extra argument");
+                        }
+                        filter = args[1];
+                        if (DEBUG) {
+                            Slog.d(TAG, "dump(): setting filter as " + filter);
+                        }
+                        break;
+                    default:
+                        Slog.w(TAG, "dump(): ignoring invalid arguments: " + Arrays.toString(args));
+                        break;
+                }
+            }
+            if (filter == null) {
+                pw.print("SyncDisabledForTests: ");
+                MyShellCommand.getSyncDisabledForTests(pw, pw);
 
-            pw.print("UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService(): ");
-            pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService());
+                pw.print("UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService(): ");
+                pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService());
+            }
 
             pw.println("DeviceConfig provider: ");
             try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd)) {
@@ -117,8 +143,16 @@
 
         IContentProvider iprovider = mProvider.getIContentProvider();
         pw.println("DeviceConfig flags:");
+        Pattern lineFilter = filter == null ? null : Pattern.compile("^.*" + filter + ".*\\/.*$");
         for (String line : MyShellCommand.listAll(iprovider)) {
-            pw.println(line);
+            if (lineFilter == null || lineFilter.matcher(line).matches()) {
+                pw.println(line);
+            }
+        }
+
+        if (filter != null) {
+            // TODO(b/364399200): use filter to skip instead?
+            return;
         }
 
         ArrayList<String> missingFiles = new ArrayList<String>();
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index bffda8b..0948931 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -497,6 +497,9 @@
     resource_dirs: [],
     static_libs: [
         "//frameworks/libs/systemui:compilelib",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/util/settings:api",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail:impl",
         "SystemUI-res",
         "WifiTrackerLib",
         "WindowManager-Shell",
@@ -770,6 +773,9 @@
     ],
     static_libs: [
         "//frameworks/libs/systemui:compilelib",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/util/settings:api",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail:impl",
         "SystemUI-tests-base",
         "androidx.test.uiautomator_uiautomator",
         "androidx.core_core-animation-testing",
diff --git a/packages/SystemUI/common/Android.bp b/packages/SystemUI/common/Android.bp
index 91dc3e3..9f15983 100644
--- a/packages/SystemUI/common/Android.bp
+++ b/packages/SystemUI/common/Android.bp
@@ -30,5 +30,9 @@
         "src/**/*.kt",
     ],
 
+    static_libs: ["SystemUI-shared-utils"],
+
+    libs: ["//frameworks/libs/systemui:tracinglib-platform"],
+
     kotlincflags: ["-Xjvm-default=all"],
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt b/packages/SystemUI/common/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
rename to packages/SystemUI/common/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
diff --git a/packages/SystemUI/src/com/android/systemui/coroutines/Tracing.kt b/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/coroutines/Tracing.kt
rename to packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 9392b1a..96e99b1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -66,13 +66,13 @@
                             interactionHandler = interactionHandler,
                             dialogFactory = dialogFactory,
                             widgetSection = widgetSection,
-                            modifier = Modifier.element(Communal.Elements.Grid)
+                            modifier = Modifier.element(Communal.Elements.Grid),
                         )
                     }
                     with(lockSection) {
                         LockIcon(
                             overrideColor = MaterialTheme.colorScheme.onPrimaryContainer,
-                            modifier = Modifier.element(Communal.Elements.LockIcon)
+                            modifier = Modifier.element(Communal.Elements.LockIcon),
                         )
                     }
                     with(bottomAreaSection) {
@@ -80,17 +80,13 @@
                             Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth()
                         )
                     }
-                }
+                },
             ) { measurables, constraints ->
                 val communalGridMeasurable = measurables[0]
                 val lockIconMeasurable = measurables[1]
                 val bottomAreaMeasurable = measurables[2]
 
-                val noMinConstraints =
-                    constraints.copy(
-                        minWidth = 0,
-                        minHeight = 0,
-                    )
+                val noMinConstraints = constraints.copy(minWidth = 0, minHeight = 0)
 
                 val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
                 val lockIconBounds =
@@ -109,14 +105,8 @@
                     )
 
                 layout(constraints.maxWidth, constraints.maxHeight) {
-                    communalGridPlaceable.place(
-                        x = 0,
-                        y = 0,
-                    )
-                    lockIconPlaceable.place(
-                        x = lockIconBounds.left,
-                        y = lockIconBounds.top,
-                    )
+                    communalGridPlaceable.place(x = 0, y = 0)
+                    lockIconPlaceable.place(x = lockIconBounds.left, y = lockIconBounds.top)
                     bottomAreaPlaceable.place(
                         x = 0,
                         y = constraints.maxHeight - bottomAreaPlaceable.height,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 5e1ac1f..df1185c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -807,7 +807,6 @@
                 ) {
                     ResizeableItemFrameViewModel()
                 }
-
             if (viewModel.isEditMode && dragDropState != null) {
                 val isItemDragging = dragDropState.draggingItemKey == item.key
                 val outlineAlpha by
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 6e30575..16002bc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -59,7 +59,14 @@
     private val onAddWidget: (componentName: ComponentName, user: UserHandle, rank: Int) -> Unit,
     private val onDeleteWidget: (id: Int, componentName: ComponentName, rank: Int) -> Unit,
     private val onReorderWidgets: (widgetIdToRankMap: Map<Int, Int>) -> Unit,
-    private val onResizeWidget: (id: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) -> Unit,
+    private val onResizeWidget:
+        (
+            id: Int,
+            spanY: Int,
+            widgetIdToRankMap: Map<Int, Int>,
+            componentName: ComponentName,
+            rank: Int,
+        ) -> Unit,
 ) {
     var list = communalContent.toMutableStateList()
         private set
@@ -105,7 +112,9 @@
             } else {
                 emptyMap()
             }
-        onResizeWidget(item.appWidgetId, newSpan, widgetIdToRankMap)
+        val componentName = item.componentName
+        val rank = item.rank
+        onResizeWidget(item.appWidgetId, newSpan, widgetIdToRankMap, componentName, rank)
     }
 
     /**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 041cd15..c33d655 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -25,7 +25,6 @@
 import androidx.compose.ui.unit.round
 import androidx.compose.ui.util.fastCoerceIn
 import com.android.compose.animation.scene.content.Content
-import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
 import com.android.compose.nestedscroll.OnStopScope
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
@@ -36,11 +35,11 @@
 
 internal interface DraggableHandler {
     /**
-     * Start a drag with the given [pointersInfo] and [overSlop].
+     * Start a drag with the given [pointersDown] and [overSlop].
      *
      * The returned [DragController] should be used to continue or stop the drag.
      */
-    fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController
+    fun onDragStarted(pointersDown: PointersInfo.PointersDown?, overSlop: Float): DragController
 }
 
 /**
@@ -95,7 +94,7 @@
      * Note: if this returns true, then [onDragStarted] will be called with overSlop equal to 0f,
      * indicating that the transition should be intercepted.
      */
-    internal fun shouldImmediatelyIntercept(pointersInfo: PointersInfo?): Boolean {
+    internal fun shouldImmediatelyIntercept(pointersDown: PointersInfo.PointersDown?): Boolean {
         // We don't intercept the touch if we are not currently driving the transition.
         val dragController = dragController
         if (dragController?.isDrivingTransition != true) {
@@ -106,7 +105,7 @@
 
         // Only intercept the current transition if one of the 2 swipes results is also a transition
         // between the same pair of contents.
-        val swipes = computeSwipes(pointersInfo)
+        val swipes = computeSwipes(pointersDown)
         val fromContent = layoutImpl.content(swipeAnimation.currentContent)
         val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromContent)
         val currentScene = layoutImpl.state.currentScene
@@ -123,7 +122,10 @@
                 ))
     }
 
-    override fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController {
+    override fun onDragStarted(
+        pointersDown: PointersInfo.PointersDown?,
+        overSlop: Float,
+    ): DragController {
         if (overSlop == 0f) {
             val oldDragController = dragController
             check(oldDragController != null && oldDragController.isDrivingTransition) {
@@ -148,7 +150,7 @@
             return updateDragController(swipes, swipeAnimation)
         }
 
-        val swipes = computeSwipes(pointersInfo)
+        val swipes = computeSwipes(pointersDown)
         val fromContent = layoutImpl.contentForUserActions()
 
         swipes.updateSwipesResults(fromContent)
@@ -194,11 +196,11 @@
         )
     }
 
-    private fun computeSwipes(pointersInfo: PointersInfo?): Swipes {
-        val fromSource = pointersInfo?.let { resolveSwipeSource(it.startedPosition) }
+    private fun computeSwipes(pointersDown: PointersInfo.PointersDown?): Swipes {
+        val fromSource = pointersDown?.let { resolveSwipeSource(it.startedPosition) }
         return Swipes(
-            upOrLeft = resolveSwipe(orientation, isUpOrLeft = true, pointersInfo, fromSource),
-            downOrRight = resolveSwipe(orientation, isUpOrLeft = false, pointersInfo, fromSource),
+            upOrLeft = resolveSwipe(orientation, isUpOrLeft = true, pointersDown, fromSource),
+            downOrRight = resolveSwipe(orientation, isUpOrLeft = false, pointersDown, fromSource),
         )
     }
 }
@@ -206,7 +208,7 @@
 private fun resolveSwipe(
     orientation: Orientation,
     isUpOrLeft: Boolean,
-    pointersInfo: PointersInfo?,
+    pointersDown: PointersInfo.PointersDown?,
     fromSource: SwipeSource.Resolved?,
 ): Swipe.Resolved {
     return Swipe.Resolved(
@@ -227,9 +229,9 @@
                     }
             },
         // If the number of pointers is not specified, 1 is assumed.
-        pointerCount = pointersInfo?.pointersDown ?: 1,
+        pointerCount = pointersDown?.count ?: 1,
         // Resolves the pointer type only if all pointers are of the same type.
-        pointersType = pointersInfo?.pointersDownByType?.keys?.singleOrNull(),
+        pointersType = pointersDown?.countByType?.keys?.singleOrNull(),
         fromSource = fromSource,
     )
 }
@@ -540,40 +542,15 @@
 
     val connection: PriorityNestedScrollConnection = nestedScrollConnection()
 
-    private fun resolveSwipe(isUpOrLeft: Boolean, pointersInfo: PointersInfo?): Swipe.Resolved {
-        return resolveSwipe(
-            orientation = draggableHandler.orientation,
-            isUpOrLeft = isUpOrLeft,
-            pointersInfo = pointersInfo,
-            fromSource =
-                pointersInfo?.let { draggableHandler.resolveSwipeSource(it.startedPosition) },
-        )
-    }
-
     private fun nestedScrollConnection(): PriorityNestedScrollConnection {
         // If we performed a long gesture before entering priority mode, we would have to avoid
         // moving on to the next scene.
         var canChangeScene = false
 
-        var lastPointersInfo: PointersInfo? = null
+        var lastPointersDown: PointersInfo.PointersDown? = null
 
-        fun hasNextScene(amount: Float): Boolean {
-            val transitionState = layoutState.transitionState
-            val scene = transitionState.currentScene
-            val fromScene = layoutImpl.scene(scene)
-            val resolvedSwipe =
-                when {
-                    amount < 0f -> resolveSwipe(isUpOrLeft = true, lastPointersInfo)
-                    amount > 0f -> resolveSwipe(isUpOrLeft = false, lastPointersInfo)
-                    else -> null
-                }
-            val nextScene = resolvedSwipe?.let { fromScene.findActionResultBestMatch(it) }
-            if (nextScene != null) return true
-
-            if (transitionState !is TransitionState.Idle) return false
-
-            val overscrollSpec = layoutImpl.state.transitions.overscrollSpec(scene, orientation)
-            return overscrollSpec != null
+        fun shouldEnableSwipes(): Boolean {
+            return layoutImpl.contentForUserActions().shouldEnableSwipes(orientation)
         }
 
         var isIntercepting = false
@@ -581,14 +558,24 @@
         return PriorityNestedScrollConnection(
             orientation = orientation,
             canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
-                val pointersInfo = pointersInfoOwner.pointersInfo()
+                val pointersDown: PointersInfo.PointersDown? =
+                    when (val info = pointersInfoOwner.pointersInfo()) {
+                        PointersInfo.MouseWheel -> {
+                            // Do not support mouse wheel interactions
+                            return@PriorityNestedScrollConnection false
+                        }
+
+                        is PointersInfo.PointersDown -> info
+                        null -> null
+                    }
+
                 canChangeScene =
                     if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
 
                 val canInterceptSwipeTransition =
                     canChangeScene &&
                         offsetAvailable != 0f &&
-                        draggableHandler.shouldImmediatelyIntercept(pointersInfo)
+                        draggableHandler.shouldImmediatelyIntercept(pointersDown)
                 if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
 
                 val threshold = layoutImpl.transitionInterceptionThreshold
@@ -599,11 +586,7 @@
                     return@PriorityNestedScrollConnection false
                 }
 
-                if (pointersInfo?.isMouseWheel == true) {
-                    // Do not support mouse wheel interactions
-                    return@PriorityNestedScrollConnection false
-                }
-                lastPointersInfo = pointersInfo
+                lastPointersDown = pointersDown
 
                 // If the current swipe transition is *not* closed to 0f or 1f, then we want the
                 // scroll events to intercept the current transition to continue the scene
@@ -622,28 +605,33 @@
                 val isZeroOffset =
                     if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
 
-                val pointersInfo = pointersInfoOwner.pointersInfo()
-                if (pointersInfo?.isMouseWheel == true) {
-                    // Do not support mouse wheel interactions
-                    return@PriorityNestedScrollConnection false
-                }
-                lastPointersInfo = pointersInfo
+                val pointersDown: PointersInfo.PointersDown? =
+                    when (val info = pointersInfoOwner.pointersInfo()) {
+                        PointersInfo.MouseWheel -> {
+                            // Do not support mouse wheel interactions
+                            return@PriorityNestedScrollConnection false
+                        }
+
+                        is PointersInfo.PointersDown -> info
+                        null -> null
+                    }
+                lastPointersDown = pointersDown
 
                 val canStart =
                     when (behavior) {
                         NestedScrollBehavior.EdgeNoPreview -> {
                             canChangeScene = isZeroOffset
-                            isZeroOffset && hasNextScene(offsetAvailable)
+                            isZeroOffset && shouldEnableSwipes()
                         }
 
                         NestedScrollBehavior.EdgeWithPreview -> {
                             canChangeScene = isZeroOffset
-                            hasNextScene(offsetAvailable)
+                            shouldEnableSwipes()
                         }
 
                         NestedScrollBehavior.EdgeAlways -> {
                             canChangeScene = true
-                            hasNextScene(offsetAvailable)
+                            shouldEnableSwipes()
                         }
                     }
 
@@ -664,14 +652,19 @@
                 // We could start an overscroll animation
                 canChangeScene = false
 
-                val pointersInfo = pointersInfoOwner.pointersInfo()
-                if (pointersInfo?.isMouseWheel == true) {
-                    // Do not support mouse wheel interactions
-                    return@PriorityNestedScrollConnection false
-                }
-                lastPointersInfo = pointersInfo
+                val pointersDown: PointersInfo.PointersDown? =
+                    when (val info = pointersInfoOwner.pointersInfo()) {
+                        PointersInfo.MouseWheel -> {
+                            // Do not support mouse wheel interactions
+                            return@PriorityNestedScrollConnection false
+                        }
 
-                val canStart = behavior.canStartOnPostFling && hasNextScene(velocityAvailable)
+                        is PointersInfo.PointersDown -> info
+                        null -> null
+                    }
+                lastPointersDown = pointersDown
+
+                val canStart = behavior.canStartOnPostFling && shouldEnableSwipes()
                 if (canStart) {
                     isIntercepting = false
                 }
@@ -679,11 +672,10 @@
                 canStart
             },
             onStart = { firstScroll ->
-                val pointersInfo = lastPointersInfo
                 scrollController(
                     dragController =
                         draggableHandler.onDragStarted(
-                            pointersInfo = pointersInfo,
+                            pointersDown = lastPointersDown,
                             overSlop = if (isIntercepting) 0f else firstScroll,
                         ),
                     canChangeScene = canChangeScene,
@@ -701,8 +693,7 @@
 ): ScrollController {
     return object : ScrollController {
         override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
-            val pointersInfo = pointersInfoOwner.pointersInfo()
-            if (pointersInfo?.isMouseWheel == true) {
+            if (pointersInfoOwner.pointersInfo() == PointersInfo.MouseWheel) {
                 // Do not support mouse wheel interactions
                 return 0f
             }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index ab2324a..1603267 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -80,8 +80,8 @@
 @Stable
 internal fun Modifier.multiPointerDraggable(
     orientation: Orientation,
-    startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
-    onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
+    startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
+    onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
     onFirstPointerDown: () -> Unit = {},
     swipeDetector: SwipeDetector = DefaultSwipeDetector,
     dispatcher: NestedScrollDispatcher,
@@ -99,8 +99,9 @@
 
 private data class MultiPointerDraggableElement(
     private val orientation: Orientation,
-    private val startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
-    private val onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
+    private val startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
+    private val onDragStarted:
+        (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
     private val onFirstPointerDown: () -> Unit,
     private val swipeDetector: SwipeDetector,
     private val dispatcher: NestedScrollDispatcher,
@@ -126,8 +127,8 @@
 
 internal class MultiPointerDraggableNode(
     orientation: Orientation,
-    var startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
-    var onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
+    var startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
+    var onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
     var onFirstPointerDown: () -> Unit,
     swipeDetector: SwipeDetector = DefaultSwipeDetector,
     private val dispatcher: NestedScrollDispatcher,
@@ -185,20 +186,27 @@
 
     private var lastPointerEvent: PointerEvent? = null
     private var startedPosition: Offset? = null
-    private var pointersDown: Int = 0
+    private var countPointersDown: Int = 0
 
     internal fun pointersInfo(): PointersInfo? {
-        val startedPosition = startedPosition
-        val lastPointerEvent = lastPointerEvent
-        if (startedPosition == null || lastPointerEvent == null) {
-            // This may be null, i.e. when the user uses TalkBack
-            return null
-        }
+        // This may be null, i.e. when the user uses TalkBack
+        val lastPointerEvent = lastPointerEvent ?: return null
 
-        return PointersInfo(
+        if (lastPointerEvent.type == PointerEventType.Scroll) return PointersInfo.MouseWheel
+
+        val startedPosition = startedPosition ?: return null
+
+        return PointersInfo.PointersDown(
             startedPosition = startedPosition,
-            pointersDown = pointersDown,
-            lastPointerEvent = lastPointerEvent,
+            count = countPointersDown,
+            countByType =
+                buildMap {
+                    lastPointerEvent.changes.fastForEach { change ->
+                        if (!change.pressed) return@fastForEach
+                        val newValue = (get(change.type) ?: 0) + 1
+                        put(change.type, newValue)
+                    }
+                },
         )
     }
 
@@ -218,11 +226,11 @@
 
                 val changes = pointerEvent.changes
                 lastPointerEvent = pointerEvent
-                pointersDown = changes.countDown()
+                countPointersDown = changes.countDown()
 
                 when {
                     // There are no more pointers down.
-                    pointersDown == 0 -> {
+                    countPointersDown == 0 -> {
                         startedPosition = null
 
                         // In case of multiple events with 0 pointers down (not pressed) we may have
@@ -290,8 +298,8 @@
                     detectDragGestures(
                         orientation = orientation,
                         startDragImmediately = startDragImmediately,
-                        onDragStart = { pointersInfo, overSlop ->
-                            onDragStarted(pointersInfo, overSlop)
+                        onDragStart = { pointersDown, overSlop ->
+                            onDragStarted(pointersDown, overSlop)
                         },
                         onDrag = { controller, amount ->
                             dispatchScrollEvents(
@@ -440,8 +448,8 @@
      */
     private suspend fun AwaitPointerEventScope.detectDragGestures(
         orientation: Orientation,
-        startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
-        onDragStart: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
+        startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
+        onDragStart: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
         onDrag: (controller: DragController, dragAmount: Float) -> Unit,
         onDragEnd: (controller: DragController) -> Unit,
         onDragCancel: (controller: DragController) -> Unit,
@@ -466,13 +474,14 @@
                 .first()
 
         var overSlop = 0f
-        var lastPointersInfo =
+        var lastPointersDown: PointersInfo.PointersDown =
             checkNotNull(pointersInfo()) {
                 "We should have pointers down, last event: $currentEvent"
             }
+                as PointersInfo.PointersDown
 
         val drag =
-            if (startDragImmediately(lastPointersInfo)) {
+            if (startDragImmediately(lastPointersDown)) {
                 consumablePointer.consume()
                 consumablePointer
             } else {
@@ -499,10 +508,11 @@
                             )
                     } ?: return
 
-                lastPointersInfo =
+                lastPointersDown =
                     checkNotNull(pointersInfo()) {
                         "We should have pointers down, last event: $currentEvent"
                     }
+                        as PointersInfo.PointersDown
                 // Make sure that overSlop is not 0f. This can happen when the user drags by exactly
                 // the touch slop. However, the overSlop we pass to onDragStarted() is used to
                 // compute the direction we are dragging in, so overSlop should never be 0f unless
@@ -516,7 +526,7 @@
                 drag
             }
 
-        val controller = onDragStart(lastPointersInfo, overSlop)
+        val controller = onDragStart(lastPointersDown, overSlop)
 
         val successful: Boolean
         try {
@@ -666,48 +676,31 @@
     fun pointersInfo(): PointersInfo?
 }
 
-/**
- * Holds information about pointer interactions within a composable.
- *
- * This class stores details such as the starting position of a gesture, the number of pointers
- * down, and whether the last pointer event was a mouse wheel scroll.
- *
- * @param startedPosition The starting position of the gesture. This is the position where the first
- *   pointer touched the screen, not necessarily the point where dragging begins. This may be
- *   different from the initial touch position if a child composable intercepts the gesture before
- *   this one.
- * @param pointersDown The number of pointers currently down.
- * @param isMouseWheel Indicates whether the last pointer event was a mouse wheel scroll.
- * @param pointersDownByType Provide a map of pointer types to the count of pointers of that type
- *   currently down/pressed.
- */
-internal data class PointersInfo(
-    val startedPosition: Offset,
-    val pointersDown: Int,
-    val isMouseWheel: Boolean,
-    val pointersDownByType: Map<PointerType, Int>,
-) {
-    init {
-        check(pointersDown > 0) { "We should have at least 1 pointer down, $pointersDown instead" }
+internal sealed interface PointersInfo {
+    /**
+     * Holds information about pointer interactions within a composable.
+     *
+     * This class stores details such as the starting position of a gesture, the number of pointers
+     * down, and whether the last pointer event was a mouse wheel scroll.
+     *
+     * @param startedPosition The starting position of the gesture. This is the position where the
+     *   first pointer touched the screen, not necessarily the point where dragging begins. This may
+     *   be different from the initial touch position if a child composable intercepts the gesture
+     *   before this one.
+     * @param count The number of pointers currently down.
+     * @param countByType Provide a map of pointer types to the count of pointers of that type
+     *   currently down/pressed.
+     */
+    data class PointersDown(
+        val startedPosition: Offset,
+        val count: Int,
+        val countByType: Map<PointerType, Int>,
+    ) : PointersInfo {
+        init {
+            check(count > 0) { "We should have at least 1 pointer down, $count instead" }
+        }
     }
-}
 
-private fun PointersInfo(
-    startedPosition: Offset,
-    pointersDown: Int,
-    lastPointerEvent: PointerEvent,
-): PointersInfo {
-    return PointersInfo(
-        startedPosition = startedPosition,
-        pointersDown = pointersDown,
-        isMouseWheel = lastPointerEvent.type == PointerEventType.Scroll,
-        pointersDownByType =
-            buildMap {
-                lastPointerEvent.changes.fastForEach { change ->
-                    if (!change.pressed) return@fastForEach
-                    val newValue = (get(change.type) ?: 0) + 1
-                    put(change.type, newValue)
-                }
-            },
-    )
+    /** Indicates whether the last pointer event was a mouse wheel scroll. */
+    data object MouseWheel : PointersInfo
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 61332b6..72b29ee 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -25,17 +25,13 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.util.fastAny
-import androidx.compose.ui.util.fastFilter
 import androidx.compose.ui.util.fastForEach
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.transformation.SharedElementTransformation
-import com.android.compose.animation.scene.transition.link.LinkedTransition
-import com.android.compose.animation.scene.transition.link.StateLink
 import kotlin.math.absoluteValue
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 
 /**
@@ -236,7 +232,6 @@
     canShowOverlay: (OverlayKey) -> Boolean = { true },
     canHideOverlay: (OverlayKey) -> Boolean = { true },
     canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
-    stateLinks: List<StateLink> = emptyList(),
 ): MutableSceneTransitionLayoutState {
     return MutableSceneTransitionLayoutStateImpl(
         initialScene,
@@ -246,7 +241,6 @@
         canShowOverlay,
         canHideOverlay,
         canReplaceOverlay,
-        stateLinks,
     )
 }
 
@@ -258,10 +252,7 @@
     internal val canChangeScene: (SceneKey) -> Boolean = { true },
     internal val canShowOverlay: (OverlayKey) -> Boolean = { true },
     internal val canHideOverlay: (OverlayKey) -> Boolean = { true },
-    internal val canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ ->
-        true
-    },
-    private val stateLinks: List<StateLink> = emptyList(),
+    internal val canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
 ) : MutableSceneTransitionLayoutState {
     private val creationThread: Thread = Thread.currentThread()
 
@@ -364,20 +355,11 @@
         checkThread()
 
         try {
-            // Keep a reference to the previous transition (if any).
-            val previousTransition = currentTransition
-
             // Start the transition.
             startTransitionInternal(transition, chain)
 
-            // Handle transition links.
-            previousTransition?.let { cancelActiveTransitionLinks(it) }
-            if (stateLinks.isNotEmpty()) {
-                coroutineScope { setupTransitionLinks(transition) }
-            }
-
             // Run the transition until it is finished.
-            transition.run()
+            transition.runInternal()
         } finally {
             finishTransition(transition)
         }
@@ -471,42 +453,6 @@
         )
     }
 
-    private fun cancelActiveTransitionLinks(transition: TransitionState.Transition) {
-        transition.activeTransitionLinks.forEach { (link, linkedTransition) ->
-            link.target.finishTransition(linkedTransition)
-        }
-        transition.activeTransitionLinks.clear()
-    }
-
-    private fun CoroutineScope.setupTransitionLinks(transition: TransitionState.Transition) {
-        stateLinks.fastForEach { stateLink ->
-            val matchingLinks =
-                stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) }
-            if (matchingLinks.isEmpty()) return@fastForEach
-            if (matchingLinks.size > 1) error("More than one link matched.")
-
-            val targetCurrentScene = stateLink.target.transitionState.currentScene
-            val matchingLink = matchingLinks[0]
-
-            if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach
-
-            val linkedTransition =
-                LinkedTransition(
-                    originalTransition = transition,
-                    fromScene = targetCurrentScene,
-                    toScene = matchingLink.targetTo,
-                    key = matchingLink.targetTransitionKey,
-                )
-
-            // Start with UNDISPATCHED so that startTransition is called directly and the new linked
-            // transition is observable directly.
-            launch(start = CoroutineStart.UNDISPATCHED) {
-                stateLink.target.startTransition(linkedTransition)
-            }
-            transition.activeTransitionLinks[stateLink] = linkedTransition
-        }
-    }
-
     /**
      * Notify that [transition] was finished and that it settled to its
      * [currentScene][TransitionState.currentScene]. This will do nothing if [transition] was
@@ -535,9 +481,6 @@
         // Mark this transition as finished.
         finishedTransitions.add(transition)
 
-        // Finish all linked transitions.
-        finishActiveTransitionLinks(transition)
-
         // Keep a reference to the last transition, in case we remove all transitions and should
         // settle to Idle.
         val lastTransition = transitionStates.last()
@@ -584,13 +527,6 @@
         transitionStates = listOf(TransitionState.Idle(scene, currentOverlays))
     }
 
-    private fun finishActiveTransitionLinks(transition: TransitionState.Transition) {
-        for ((link, linkedTransition) in transition.activeTransitionLinks) {
-            link.target.finishTransition(linkedTransition)
-        }
-        transition.activeTransitionLinks.clear()
-    }
-
     /**
      * Check if a transition is in progress. If the progress value is near 0 or 1, immediately snap
      * to the closest scene.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index ba5f414..a448ee4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -56,7 +56,7 @@
 }
 
 /** Whether swipe should be enabled in the given [orientation]. */
-private fun Content.shouldEnableSwipes(orientation: Orientation): Boolean {
+internal fun Content.shouldEnableSwipes(orientation: Orientation): Boolean {
     if (userActions.isEmpty()) {
         return false
     }
@@ -200,10 +200,10 @@
 
     override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput()
 
-    private fun startDragImmediately(pointersInfo: PointersInfo): Boolean {
+    private fun startDragImmediately(pointersDown: PointersInfo.PointersDown): Boolean {
         // Immediately start the drag if the user can't swipe in the other direction and the gesture
         // handler can intercept it.
-        return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersInfo)
+        return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersDown)
     }
 
     private fun canOppositeSwipe(): Boolean {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index 7d29a68..e3118d67 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.spring
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.runtime.Stable
@@ -34,8 +35,6 @@
 import com.android.compose.animation.scene.TransformationSpec
 import com.android.compose.animation.scene.TransformationSpecImpl
 import com.android.compose.animation.scene.TransitionKey
-import com.android.compose.animation.scene.transition.link.LinkedTransition
-import com.android.compose.animation.scene.transition.link.StateLink
 import kotlinx.coroutines.launch
 
 /** The state associated to a [SceneTransitionLayout] at some specific point in time. */
@@ -280,8 +279,8 @@
          */
         private var interruptionDecay: Animatable<Float, AnimationVector1D>? = null
 
-        /** The map of active links that connects this transition to other transitions. */
-        internal val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()
+        /** Whether this transition was already started. */
+        private var wasStarted = false
 
         init {
             check(fromContent != toContent)
@@ -328,7 +327,7 @@
         }
 
         /** Run this transition and return once it is finished. */
-        abstract suspend fun run()
+        protected abstract suspend fun run()
 
         /**
          * Freeze this transition state so that neither [currentScene] nor [currentOverlays] will
@@ -341,6 +340,13 @@
          */
         abstract fun freezeAndAnimateToCurrentState()
 
+        internal suspend fun runInternal() {
+            check(!wasStarted) { "A Transition can be started only once." }
+            wasStarted = true
+
+            run()
+        }
+
         internal fun updateOverscrollSpecs(
             fromSpec: OverscrollSpecImpl?,
             toSpec: OverscrollSpecImpl?,
@@ -376,7 +382,7 @@
                     val progressSpec =
                         spring(
                             stiffness = swipeSpec.stiffness,
-                            dampingRatio = swipeSpec.dampingRatio,
+                            dampingRatio = Spring.DampingRatioNoBouncy,
                             visibilityThreshold = ProgressVisibilityThreshold,
                         )
                     animatable.animateTo(0f, progressSpec)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
deleted file mode 100644
index 42ba9ba..0000000
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
+++ /dev/null
@@ -1,59 +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.compose.animation.scene.transition.link
-
-import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.TransitionKey
-import com.android.compose.animation.scene.content.state.TransitionState
-
-/** A linked transition which is driven by a [originalTransition]. */
-internal class LinkedTransition(
-    private val originalTransition: TransitionState.Transition,
-    fromScene: SceneKey,
-    toScene: SceneKey,
-    override val key: TransitionKey? = null,
-) : TransitionState.Transition.ChangeScene(fromScene, toScene) {
-
-    override val currentScene: SceneKey
-        get() {
-            return when (originalTransition.currentScene) {
-                originalTransition.fromContent -> fromScene
-                originalTransition.toContent -> toScene
-                else -> error("Original currentScene is neither FromScene nor ToScene")
-            }
-        }
-
-    override val isInitiatedByUserInput: Boolean
-        get() = originalTransition.isInitiatedByUserInput
-
-    override val isUserInputOngoing: Boolean
-        get() = originalTransition.isUserInputOngoing
-
-    override val progress: Float
-        get() = originalTransition.progress
-
-    override val progressVelocity: Float
-        get() = originalTransition.progressVelocity
-
-    override suspend fun run() {
-        originalTransition.run()
-    }
-
-    override fun freezeAndAnimateToCurrentState() {
-        originalTransition.freezeAndAnimateToCurrentState()
-    }
-}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
deleted file mode 100644
index 2aec509..0000000
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
+++ /dev/null
@@ -1,63 +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.compose.animation.scene.transition.link
-
-import com.android.compose.animation.scene.ContentKey
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl
-import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.SceneTransitionLayoutState
-import com.android.compose.animation.scene.TransitionKey
-import com.android.compose.animation.scene.content.state.TransitionState
-
-/** A link between a source (implicit) and [target] `SceneTransitionLayoutState`. */
-class StateLink(target: SceneTransitionLayoutState, val transitionLinks: List<TransitionLink>) {
-
-    internal val target = target as MutableSceneTransitionLayoutStateImpl
-
-    /**
-     * Links two transitions (source and target) together.
-     *
-     * `null` can be passed to indicate that any SceneKey should match. e.g. passing `null`, `null`,
-     * `null`, `SceneA` means that any transition at the source will trigger a transition in the
-     * target to `SceneA` from any current scene.
-     */
-    class TransitionLink(
-        val sourceFrom: ContentKey?,
-        val sourceTo: ContentKey?,
-        val targetFrom: SceneKey?,
-        val targetTo: SceneKey,
-        val targetTransitionKey: TransitionKey? = null,
-    ) {
-        init {
-            if (
-                (sourceFrom != null && sourceFrom == sourceTo) ||
-                    (targetFrom != null && targetFrom == targetTo)
-            )
-                error("From and To can't be the same")
-        }
-
-        internal fun isMatchingLink(transition: TransitionState.Transition): Boolean {
-            return (sourceFrom == null || sourceFrom == transition.fromContent) &&
-                (sourceTo == null || sourceTo == transition.toContent)
-        }
-
-        internal fun targetIsInValidState(targetCurrentContent: ContentKey): Boolean {
-            return (targetFrom == null || targetFrom == targetCurrentContent) &&
-                targetTo != targetCurrentContent
-        }
-    }
-}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 098673e..7e6f3a8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -52,17 +52,15 @@
 private const val SCREEN_SIZE = 100f
 private val LAYOUT_SIZE = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt())
 
-private fun pointersInfo(
+private fun pointersDown(
     startedPosition: Offset = Offset.Zero,
     pointersDown: Int = 1,
-    isMouseWheel: Boolean = false,
     pointersDownByType: Map<PointerType, Int> = mapOf(PointerType.Touch to pointersDown),
-): PointersInfo {
-    return PointersInfo(
+): PointersInfo.PointersDown {
+    return PointersInfo.PointersDown(
         startedPosition = startedPosition,
-        pointersDown = pointersDown,
-        isMouseWheel = isMouseWheel,
-        pointersDownByType = pointersDownByType,
+        count = pointersDown,
+        countByType = pointersDownByType,
     )
 }
 
@@ -138,7 +136,7 @@
         val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical)
         val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal)
 
-        var pointerInfoOwner: () -> PointersInfo = { pointersInfo() }
+        var pointerInfoOwner: () -> PointersInfo = { pointersDown() }
 
         fun nestedScrollConnection(
             nestedScrollBehavior: NestedScrollBehavior,
@@ -221,7 +219,7 @@
         }
 
         fun onDragStarted(
-            pointersInfo: PointersInfo = pointersInfo(),
+            pointersInfo: PointersInfo.PointersDown = pointersDown(),
             overSlop: Float,
             expectedConsumedOverSlop: Float = overSlop,
         ): DragController {
@@ -235,18 +233,20 @@
             )
         }
 
-        fun onDragStartedImmediately(pointersInfo: PointersInfo = pointersInfo()): DragController {
+        fun onDragStartedImmediately(
+            pointersInfo: PointersInfo.PointersDown = pointersDown()
+        ): DragController {
             return onDragStarted(draggableHandler, pointersInfo, overSlop = 0f)
         }
 
         fun onDragStarted(
             draggableHandler: DraggableHandler,
-            pointersInfo: PointersInfo = pointersInfo(),
+            pointersInfo: PointersInfo.PointersDown = pointersDown(),
             overSlop: Float = 0f,
             expectedConsumedOverSlop: Float = overSlop,
         ): DragController {
             val dragController =
-                draggableHandler.onDragStarted(pointersInfo = pointersInfo, overSlop = overSlop)
+                draggableHandler.onDragStarted(pointersDown = pointersInfo, overSlop = overSlop)
 
             // MultiPointerDraggable will always call onDelta with the initial overSlop right after
             dragController.onDragDelta(pixels = overSlop, expectedConsumedOverSlop)
@@ -529,7 +529,7 @@
         val dragController =
             onDragStarted(
                 pointersInfo =
-                    pointersInfo(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f)),
+                    pointersDown(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f)),
                 overSlop = up(fractionOfScreen = 0.2f),
             )
         assertTransition(
@@ -555,7 +555,7 @@
 
         // Start dragging from the bottom
         onDragStarted(
-            pointersInfo = pointersInfo(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE)),
+            pointersInfo = pointersDown(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE)),
             overSlop = up(fractionOfScreen = 0.1f),
         )
         assertTransition(
@@ -1052,7 +1052,7 @@
         navigateToSceneC()
 
         // Swipe up from the middle to transition to scene B.
-        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
         onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
         assertTransition(
             currentScene = SceneC,
@@ -1084,7 +1084,7 @@
         // Start a new gesture from the bottom of the screen. Because swiping up from the bottom of
         // C leads to scene A (and not B), the previous transitions is *not* intercepted and we
         // instead animate from C to A.
-        val bottom = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2, SCREEN_SIZE))
+        val bottom = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2, SCREEN_SIZE))
         assertThat(draggableHandler.shouldImmediatelyIntercept(bottom)).isFalse()
         onDragStarted(pointersInfo = bottom, overSlop = up(0.1f))
 
@@ -1103,7 +1103,7 @@
         navigateToSceneC()
 
         // Swipe up from the middle to transition to scene B.
-        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
         onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
         assertTransition(fromScene = SceneC, toScene = SceneB, isUserInputOngoing = true)
 
@@ -1120,15 +1120,15 @@
 
     @Test
     fun interruptedTransitionCanNotBeImmediatelyIntercepted() = runGestureTest {
-        assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isFalse()
+        assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isFalse()
         onDragStarted(overSlop = up(0.1f))
-        assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isTrue()
+        assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isTrue()
 
         layoutState.startTransitionImmediately(
             animationScope = testScope.backgroundScope,
             transition(SceneA, SceneB),
         )
-        assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isFalse()
+        assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isFalse()
     }
 
     @Test
@@ -1160,7 +1160,7 @@
         assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
 
         // Intercept the transition and swipe down back to scene A.
-        assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isTrue()
+        assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isTrue()
         val dragController2 = onDragStartedImmediately()
 
         // Block the transition when the user release their finger.
@@ -1204,7 +1204,7 @@
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
 
         // Drag from the **top** of the screen
-        pointerInfoOwner = { pointersInfo() }
+        pointerInfoOwner = { pointersDown() }
         assertIdle(currentScene = SceneC)
 
         nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
@@ -1221,7 +1221,7 @@
         advanceUntilIdle()
 
         // Drag from the **bottom** of the screen
-        pointerInfoOwner = { pointersInfo(startedPosition = Offset(0f, SCREEN_SIZE)) }
+        pointerInfoOwner = { pointersDown(startedPosition = Offset(0f, SCREEN_SIZE)) }
         assertIdle(currentScene = SceneC)
 
         nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
@@ -1241,7 +1241,7 @@
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
 
         // Use mouse wheel
-        pointerInfoOwner = { pointersInfo(isMouseWheel = true) }
+        pointerInfoOwner = { PointersInfo.MouseWheel }
         assertIdle(currentScene = SceneC)
 
         nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
@@ -1251,7 +1251,7 @@
     @Test
     fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest {
         // Swipe up from the middle to transition to scene B.
-        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
         val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneB, isUserInputOngoing = true)
 
@@ -1265,7 +1265,7 @@
         layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) }
 
         // Swipe up to scene B at progress = 200%.
-        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
         val dragController =
             onDragStarted(
                 pointersInfo = middle,
@@ -1296,7 +1296,7 @@
         layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) }
 
         // Swipe up to scene B at progress = 200%.
-        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
         val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.99f))
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.99f)
 
@@ -1342,7 +1342,7 @@
             overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
         }
 
-        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
 
         val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.5f))
         val transition = assertThat(transitionState).isSceneTransition()
@@ -1374,7 +1374,7 @@
             overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
         }
 
-        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
 
         val dragController = onDragStarted(pointersInfo = middle, overSlop = down(0.5f))
         val transition = assertThat(transitionState).isSceneTransition()
@@ -1405,7 +1405,7 @@
             overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
         }
 
-        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
 
         val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1.5f))
         val transition = assertThat(transitionState).isSceneTransition()
@@ -1437,7 +1437,7 @@
             overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
         }
 
-        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
 
         val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1.5f))
         val transition = assertThat(transitionState).isSceneTransition()
@@ -1471,7 +1471,7 @@
 
         mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB))
 
-        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
         val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1f))
         val transition = assertThat(transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
@@ -1504,7 +1504,7 @@
 
         mutableUserActionsA = mapOf(Swipe.Down to UserActionResult(SceneC))
 
-        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
         val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1f))
         val transition = assertThat(transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
@@ -1676,4 +1676,33 @@
         assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
         assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayB)
     }
+
+    @Test
+    fun replaceOverlayNestedScroll() = runGestureTest {
+        layoutState.showOverlay(OverlayA, animationScope = testScope)
+        advanceUntilIdle()
+
+        // Initial state.
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+        assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayA)
+
+        // Swipe down to replace overlay A by overlay B.
+
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
+        nestedScroll.scroll(downOffset(0.1f))
+        val transition = assertThat(layoutState.transitionState).isReplaceOverlayTransition()
+        assertThat(transition).hasCurrentScene(SceneA)
+        assertThat(transition).hasFromOverlay(OverlayA)
+        assertThat(transition).hasToOverlay(OverlayB)
+        assertThat(transition).hasCurrentOverlays(OverlayA)
+        assertThat(transition).hasProgress(0.1f)
+
+        nestedScroll.preFling(Velocity(0f, velocityThreshold))
+        advanceUntilIdle()
+        // Commit the gesture. The overlays are instantly swapped in the set of current overlays.
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+        assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayB)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index a2b263b..2b70908 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -26,10 +26,8 @@
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
 import com.android.compose.animation.scene.TestScenes.SceneC
-import com.android.compose.animation.scene.TestScenes.SceneD
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.subjects.assertThat
-import com.android.compose.animation.scene.transition.link.StateLink
 import com.android.compose.animation.scene.transition.seekToScene
 import com.android.compose.test.MonotonicClockTestScope
 import com.android.compose.test.TestSceneTransition
@@ -42,6 +40,7 @@
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.flow.consumeAsFlow
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertThrows
 import org.junit.Rule
@@ -132,147 +131,6 @@
         assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
     }
 
-    private fun setupLinkedStates(
-        parentInitialScene: SceneKey = SceneC,
-        childInitialScene: SceneKey = SceneA,
-        sourceFrom: SceneKey? = SceneA,
-        sourceTo: SceneKey? = SceneB,
-        targetFrom: SceneKey? = SceneC,
-        targetTo: SceneKey = SceneD,
-    ): Pair<MutableSceneTransitionLayoutStateImpl, MutableSceneTransitionLayoutStateImpl> {
-        val parentState = MutableSceneTransitionLayoutState(parentInitialScene)
-        val link =
-            listOf(
-                StateLink(
-                    parentState,
-                    listOf(StateLink.TransitionLink(sourceFrom, sourceTo, targetFrom, targetTo)),
-                )
-            )
-        val childState = MutableSceneTransitionLayoutState(childInitialScene, stateLinks = link)
-        return Pair(
-            parentState as MutableSceneTransitionLayoutStateImpl,
-            childState as MutableSceneTransitionLayoutStateImpl,
-        )
-    }
-
-    @Test
-    fun linkedTransition_startsLinkAndFinishesLinkInToState() = runTest {
-        val (parentState, childState) = setupLinkedStates()
-
-        val childTransition = transition(SceneA, SceneB)
-
-        val job =
-            childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
-        assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
-        assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
-
-        childTransition.finish()
-        job.join()
-        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
-        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
-    }
-
-    @Test
-    fun linkedTransition_transitiveLink() = runTest {
-        val parentParentState =
-            MutableSceneTransitionLayoutState(SceneB) as MutableSceneTransitionLayoutStateImpl
-        val parentLink =
-            listOf(
-                StateLink(
-                    parentParentState,
-                    listOf(StateLink.TransitionLink(SceneC, SceneD, SceneB, SceneC)),
-                )
-            )
-        val parentState =
-            MutableSceneTransitionLayoutState(SceneC, stateLinks = parentLink)
-                as MutableSceneTransitionLayoutStateImpl
-        val link =
-            listOf(
-                StateLink(
-                    parentState,
-                    listOf(StateLink.TransitionLink(SceneA, SceneB, SceneC, SceneD)),
-                )
-            )
-        val childState =
-            MutableSceneTransitionLayoutState(SceneA, stateLinks = link)
-                as MutableSceneTransitionLayoutStateImpl
-
-        val childTransition = transition(SceneA, SceneB)
-
-        val job =
-            childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
-        assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
-        assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
-        assertThat(parentParentState.isTransitioning(SceneB, SceneC)).isTrue()
-
-        childTransition.finish()
-        job.join()
-        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
-        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
-        assertThat(parentParentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
-    }
-
-    @Test
-    fun linkedTransition_linkProgressIsEqual() = runTest {
-        val (parentState, childState) = setupLinkedStates()
-
-        var progress = 0f
-        val childTransition = transition(SceneA, SceneB, progress = { progress })
-
-        childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
-        assertThat(parentState.currentTransition?.progress).isEqualTo(0f)
-
-        progress = .5f
-        assertThat(parentState.currentTransition?.progress).isEqualTo(.5f)
-    }
-
-    @Test
-    fun linkedTransition_reverseTransitionIsNotLinked() = runTest {
-        val (parentState, childState) = setupLinkedStates()
-
-        val childTransition = transition(SceneB, SceneA, current = { SceneB })
-
-        val job =
-            childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
-        assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue()
-        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
-
-        childTransition.finish()
-        job.join()
-        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
-        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
-    }
-
-    @Test
-    fun linkedTransition_startsLinkAndFinishesLinkInFromState() = runTest {
-        val (parentState, childState) = setupLinkedStates()
-
-        val childTransition = transition(SceneA, SceneB, current = { SceneA })
-        val job =
-            childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
-
-        childTransition.finish()
-        job.join()
-        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
-        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
-    }
-
-    @Test
-    fun linkedTransition_startsLinkButLinkedStateIsTakenOver() = runTest {
-        val (parentState, childState) = setupLinkedStates()
-
-        val childTransition = transition(SceneA, SceneB)
-        val parentTransition = transition(SceneC, SceneA)
-        val job =
-            childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
-        parentState.startTransitionImmediately(animationScope = backgroundScope, parentTransition)
-
-        childTransition.finish()
-        job.join()
-        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
-        assertThat(parentState.transitionState).isEqualTo(parentTransition)
-    }
-
     @Test
     fun setTargetScene_withTransitionKey() = runMonotonicClockTest {
         val transitionkey = TransitionKey(debugName = "foo")
@@ -393,51 +251,6 @@
         assertThat(state.isTransitioning()).isTrue()
     }
 
-    @Test
-    fun linkedTransition_fuzzyLinksAreMatchedAndStarted() = runTest {
-        val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD)
-        val childTransition = transition(SceneA, SceneB)
-
-        val job =
-            childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
-        assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
-        assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
-
-        childTransition.finish()
-        job.join()
-        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
-        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
-    }
-
-    @Test
-    fun linkedTransition_fuzzyLinksAreMatchedAndResetToProperPreviousScene() = runTest {
-        val (parentState, childState) =
-            setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD)
-
-        val childTransition = transition(SceneA, SceneB, current = { SceneA })
-
-        val job =
-            childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
-        assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
-        assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
-
-        childTransition.finish()
-        job.join()
-        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
-        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
-    }
-
-    @Test
-    fun linkedTransition_fuzzyLinksAreNotMatched() = runTest {
-        val (parentState, childState) =
-            setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD)
-        val childTransition = transition(SceneA, SceneB)
-
-        childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
-        assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
-        assertThat(parentState.isTransitioning(SceneC, SceneD)).isFalse()
-    }
-
     private fun MonotonicClockTestScope.startOverscrollableTransistionFromAtoB(
         progress: () -> Float,
         sceneTransitions: SceneTransitions,
@@ -777,4 +590,15 @@
         assertThat(transition.progressTo(SceneA)).isEqualTo(1f - 0.2f)
         assertThrows(IllegalArgumentException::class.java) { transition.progressTo(SceneC) }
     }
+
+    @Test
+    fun transitionCanBeStartedOnlyOnce() = runTest {
+        val state = MutableSceneTransitionLayoutState(SceneA)
+        val transition = transition(from = SceneA, to = SceneB)
+
+        state.startTransitionImmediately(backgroundScope, transition)
+        assertThrows(IllegalStateException::class.java) {
+            runBlocking { state.startTransition(transition) }
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 3b2ee98..97a96a4 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -574,7 +574,7 @@
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
             SceneTransitionLayout(layoutState, Modifier.size(LayoutWidth, LayoutHeight)) {
-                scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+                scene(SceneA, userActions = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneB)) {
                     Box(
                         Modifier.fillMaxSize()
                             // A scrollable that does not consume the scroll gesture
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
deleted file mode 100644
index dd58ea7..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard;
-
-import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.hardware.display.DisplayManagerGlobal;
-import android.testing.TestableLooper;
-import android.view.Display;
-import android.view.DisplayInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.settings.FakeDisplayTracker;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.concurrent.Executor;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@TestableLooper.RunWithLooper
-public class KeyguardDisplayManagerTest extends SysuiTestCase {
-
-    @Mock
-    private NavigationBarController mNavigationBarController;
-    @Mock
-    private ConnectedDisplayKeyguardPresentation.Factory
-            mConnectedDisplayKeyguardPresentationFactory;
-    @Mock
-    private ConnectedDisplayKeyguardPresentation mConnectedDisplayKeyguardPresentation;
-    @Mock
-    private KeyguardDisplayManager.DeviceStateHelper mDeviceStateHelper;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-
-    private Executor mMainExecutor = Runnable::run;
-    private Executor mBackgroundExecutor = Runnable::run;
-    private KeyguardDisplayManager mManager;
-    private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
-    // The default and secondary displays are both in the default group
-    private Display mDefaultDisplay;
-    private Display mSecondaryDisplay;
-
-    // This display is in a different group from the default and secondary displays.
-    private Display mAlwaysUnlockedDisplay;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController,
-                mDisplayTracker, mMainExecutor, mBackgroundExecutor, mDeviceStateHelper,
-                mKeyguardStateController, mConnectedDisplayKeyguardPresentationFactory));
-        doReturn(mConnectedDisplayKeyguardPresentation).when(
-                mConnectedDisplayKeyguardPresentationFactory).create(any());
-        doReturn(mConnectedDisplayKeyguardPresentation).when(mManager)
-                .createPresentation(any());
-        mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
-                new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
-        mSecondaryDisplay = new Display(DisplayManagerGlobal.getInstance(),
-                Display.DEFAULT_DISPLAY + 1,
-                new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
-
-        DisplayInfo alwaysUnlockedDisplayInfo = new DisplayInfo();
-        alwaysUnlockedDisplayInfo.displayId = Display.DEFAULT_DISPLAY + 2;
-        alwaysUnlockedDisplayInfo.flags = Display.FLAG_ALWAYS_UNLOCKED;
-        mAlwaysUnlockedDisplay = new Display(DisplayManagerGlobal.getInstance(),
-                Display.DEFAULT_DISPLAY,
-                alwaysUnlockedDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
-    }
-
-    @Test
-    public void testShow_defaultDisplayOnly() {
-        mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay});
-        mManager.show();
-        verify(mManager, never()).createPresentation(any());
-    }
-
-    @Test
-    public void testShow_includeSecondaryDisplay() {
-        mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
-        mManager.show();
-        verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay));
-    }
-
-    @Test
-    public void testShow_includeAlwaysUnlockedDisplay() {
-        mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mAlwaysUnlockedDisplay});
-
-        mManager.show();
-        verify(mManager, never()).createPresentation(any());
-    }
-
-    @Test
-    public void testShow_includeSecondaryAndAlwaysUnlockedDisplays() {
-        mDisplayTracker.setAllDisplays(
-                new Display[]{mDefaultDisplay, mSecondaryDisplay, mAlwaysUnlockedDisplay});
-
-        mManager.show();
-        verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay));
-    }
-
-    @Test
-    public void testShow_concurrentDisplayActive_occluded() {
-        mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
-
-        when(mDeviceStateHelper.isConcurrentDisplayActive(mSecondaryDisplay)).thenReturn(true);
-        when(mKeyguardStateController.isOccluded()).thenReturn(true);
-        verify(mManager, never()).createPresentation(eq(mSecondaryDisplay));
-    }
-
-    @Test
-    public void testShow_presentationCreated() {
-        when(mManager.createPresentation(any())).thenCallRealMethod();
-        mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
-
-        mManager.show();
-
-        verify(mConnectedDisplayKeyguardPresentationFactory).create(eq(mSecondaryDisplay));
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt
new file mode 100644
index 0000000..57a6797
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt
@@ -0,0 +1,225 @@
+/*
+ * 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.keyguard
+
+import android.hardware.display.DisplayManagerGlobal
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardDisplayManager.DeviceStateHelper
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.navigationbar.NavigationBarController
+import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.shade.data.repository.FakeShadePositionRepository
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import java.util.concurrent.Executor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
+class KeyguardDisplayManagerTest : SysuiTestCase() {
+    @Mock private val navigationBarController = mock(NavigationBarController::class.java)
+    @Mock
+    private val presentationFactory = mock(ConnectedDisplayKeyguardPresentation.Factory::class.java)
+    @Mock
+    private val connectedDisplayKeyguardPresentation =
+        mock(ConnectedDisplayKeyguardPresentation::class.java)
+    @Mock private val deviceStateHelper = mock(DeviceStateHelper::class.java)
+    @Mock private val keyguardStateController = mock(KeyguardStateController::class.java)
+    private val shadePositionRepository = FakeShadePositionRepository()
+
+    private val mainExecutor = Executor { it.run() }
+    private val backgroundExecutor = Executor { it.run() }
+    private lateinit var manager: KeyguardDisplayManager
+    private val displayTracker = FakeDisplayTracker(mContext)
+    // The default and secondary displays are both in the default group
+    private lateinit var defaultDisplay: Display
+    private lateinit var secondaryDisplay: Display
+
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+
+    // This display is in a different group from the default and secondary displays.
+    private lateinit var alwaysUnlockedDisplay: Display
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        manager =
+            KeyguardDisplayManager(
+                mContext,
+                { navigationBarController },
+                displayTracker,
+                mainExecutor,
+                backgroundExecutor,
+                deviceStateHelper,
+                keyguardStateController,
+                presentationFactory,
+                { shadePositionRepository },
+                testScope.backgroundScope,
+            )
+        whenever(presentationFactory.create(any())).doReturn(connectedDisplayKeyguardPresentation)
+
+        defaultDisplay =
+            Display(
+                DisplayManagerGlobal.getInstance(),
+                Display.DEFAULT_DISPLAY,
+                DisplayInfo(),
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS,
+            )
+        secondaryDisplay =
+            Display(
+                DisplayManagerGlobal.getInstance(),
+                Display.DEFAULT_DISPLAY + 1,
+                DisplayInfo(),
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS,
+            )
+
+        val alwaysUnlockedDisplayInfo = DisplayInfo()
+        alwaysUnlockedDisplayInfo.displayId = Display.DEFAULT_DISPLAY + 2
+        alwaysUnlockedDisplayInfo.flags = Display.FLAG_ALWAYS_UNLOCKED
+        alwaysUnlockedDisplay =
+            Display(
+                DisplayManagerGlobal.getInstance(),
+                Display.DEFAULT_DISPLAY,
+                alwaysUnlockedDisplayInfo,
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS,
+            )
+    }
+
+    @Test
+    fun testShow_defaultDisplayOnly() {
+        displayTracker.allDisplays = arrayOf(defaultDisplay)
+        manager.show()
+        verify(presentationFactory, never()).create(any())
+    }
+
+    @Test
+    fun testShow_includeSecondaryDisplay() {
+        displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+        manager.show()
+        verify(presentationFactory).create(eq(secondaryDisplay))
+    }
+
+    @Test
+    fun testShow_includeAlwaysUnlockedDisplay() {
+        displayTracker.allDisplays = arrayOf(defaultDisplay, alwaysUnlockedDisplay)
+
+        manager.show()
+        verify(presentationFactory, never()).create(any())
+    }
+
+    @Test
+    fun testShow_includeSecondaryAndAlwaysUnlockedDisplays() {
+        displayTracker.allDisplays =
+            arrayOf(defaultDisplay, secondaryDisplay, alwaysUnlockedDisplay)
+
+        manager.show()
+        verify(presentationFactory).create(eq(secondaryDisplay))
+    }
+
+    @Test
+    fun testShow_concurrentDisplayActive_occluded() {
+        displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+
+        whenever(deviceStateHelper.isConcurrentDisplayActive(secondaryDisplay)).thenReturn(true)
+        whenever(keyguardStateController.isOccluded).thenReturn(true)
+        verify(presentationFactory, never()).create(eq(secondaryDisplay))
+    }
+
+    @Test
+    fun testShow_presentationCreated() {
+        displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+
+        manager.show()
+
+        verify(presentationFactory).create(eq(secondaryDisplay))
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    fun show_shadeMovesDisplay_newPresentationCreated() {
+        displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+        // Shade in the default display, we expect the presentation to be in the secondary only
+        shadePositionRepository.setDisplayId(defaultDisplay.displayId)
+
+        manager.show()
+
+        verify(presentationFactory).create(eq(secondaryDisplay))
+        verify(presentationFactory, never()).create(eq(defaultDisplay))
+        reset(presentationFactory)
+        whenever(presentationFactory.create(any())).thenReturn(connectedDisplayKeyguardPresentation)
+
+        // Let's move it to the secondary display. We expect it will be added in the default
+        // one.
+        shadePositionRepository.setDisplayId(secondaryDisplay.displayId)
+        testScope.advanceUntilIdle()
+
+        verify(presentationFactory).create(eq(defaultDisplay))
+        reset(presentationFactory)
+        whenever(presentationFactory.create(any())).thenReturn(connectedDisplayKeyguardPresentation)
+
+        // Let's move it back! it should be re-created (it means it was removed before)
+        shadePositionRepository.setDisplayId(defaultDisplay.displayId)
+        testScope.advanceUntilIdle()
+
+        verify(presentationFactory).create(eq(secondaryDisplay))
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    fun show_shadeInSecondaryDisplay_defaultOneHasPresentation() {
+        displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+        shadePositionRepository.setDisplayId(secondaryDisplay.displayId)
+
+        manager.show()
+
+        verify(presentationFactory).create(eq(defaultDisplay))
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    fun show_shadeInDefaultDisplay_secondaryOneHasPresentation() {
+        displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+        shadePositionRepository.setDisplayId(defaultDisplay.displayId)
+
+        manager.show()
+
+        verify(presentationFactory).create(eq(secondaryDisplay))
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index fcb433b..9d471f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -45,9 +45,9 @@
 import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 import android.testing.TestableLooper;
+import android.util.Pair;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.LinearLayout;
 import android.widget.Space;
 import android.widget.Spinner;
 
@@ -166,6 +166,7 @@
         when(mCachedDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
         when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
         when(mCachedDevice.isConnectedHapClientDevice()).thenReturn(true);
+        when(mCachedDevice.getDrawableWithDescription()).thenReturn(new Pair<>(mDrawable, ""));
         when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice);
 
         mContext.setMockPackageManager(mPackageManager);
@@ -217,6 +218,18 @@
 
     @Test
     @EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS)
+    public void showDialog_noLiveCaption_noRelatedToolsInConfig_relatedToolLayoutGone() {
+        mContext.getOrCreateTestableResources().addOverride(
+                R.array.config_quickSettingsHearingDevicesRelatedToolName, new String[]{});
+
+        setUpPairNewDeviceDialog();
+        mDialog.show();
+
+        assertToolsUi(0);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS)
     public void showDialog_hasLiveCaption_noRelatedToolsInConfig_showOneRelatedTool() {
         when(mPackageManager.queryIntentActivities(
                 eq(LIVE_CAPTION_INTENT), anyInt())).thenReturn(
@@ -227,8 +240,7 @@
         setUpPairNewDeviceDialog();
         mDialog.show();
 
-        LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog);
-        assertThat(countChildWithoutSpace(relatedToolsView)).isEqualTo(1);
+        assertToolsUi(1);
     }
 
     @Test
@@ -251,8 +263,7 @@
         setUpPairNewDeviceDialog();
         mDialog.show();
 
-        LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog);
-        assertThat(countChildWithoutSpace(relatedToolsView)).isEqualTo(2);
+        assertToolsUi(2);
     }
 
     @Test
@@ -263,8 +274,8 @@
         setUpDeviceListDialog();
         mDialog.show();
 
-        Spinner spinner = (Spinner) getPresetSpinner(mDialog);
-        assertThat(spinner.getVisibility()).isEqualTo(View.GONE);
+        ViewGroup presetLayout = getPresetLayout(mDialog);
+        assertThat(presetLayout.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
@@ -276,8 +287,9 @@
         setUpDeviceListDialog();
         mDialog.show();
 
-        Spinner spinner = (Spinner) getPresetSpinner(mDialog);
-        assertThat(spinner.getVisibility()).isEqualTo(View.VISIBLE);
+        ViewGroup presetLayout = getPresetLayout(mDialog);
+        assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
+        Spinner spinner = getPresetSpinner(mDialog);
         assertThat(spinner.getSelectedItemPosition()).isEqualTo(0);
     }
 
@@ -292,8 +304,9 @@
         mDialogDelegate.onActiveDeviceChanged(mCachedDevice, BluetoothProfile.LE_AUDIO);
         mTestableLooper.processAllMessages();
 
-        Spinner spinner = (Spinner) getPresetSpinner(mDialog);
-        assertThat(spinner.getVisibility()).isEqualTo(View.VISIBLE);
+        ViewGroup presetLayout = getPresetLayout(mDialog);
+        assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
+        Spinner spinner = getPresetSpinner(mDialog);
         assertThat(spinner.getSelectedItemPosition()).isEqualTo(0);
     }
 
@@ -359,14 +372,23 @@
         return dialog.requireViewById(R.id.pair_new_device_button);
     }
 
-    private View getRelatedToolsView(SystemUIDialog dialog) {
-        return dialog.requireViewById(R.id.related_tools_container);
+    private ViewGroup getToolsContainer(SystemUIDialog dialog) {
+        return dialog.requireViewById(R.id.tools_container);
     }
 
-    private View getPresetSpinner(SystemUIDialog dialog) {
+    private ViewGroup getToolsLayout(SystemUIDialog dialog) {
+        return dialog.requireViewById(R.id.tools_layout);
+    }
+
+    private Spinner getPresetSpinner(SystemUIDialog dialog) {
         return dialog.requireViewById(R.id.preset_spinner);
     }
 
+    private ViewGroup getPresetLayout(SystemUIDialog dialog) {
+        return dialog.requireViewById(R.id.preset_layout);
+    }
+
+
     private int countChildWithoutSpace(ViewGroup viewGroup) {
         int spaceCount = 0;
         for (int i = 0; i < viewGroup.getChildCount(); i++) {
@@ -377,6 +399,15 @@
         return viewGroup.getChildCount() - spaceCount;
     }
 
+    private void assertToolsUi(int childCount) {
+        ViewGroup toolsContainer = getToolsContainer(mDialog);
+        assertThat(countChildWithoutSpace(toolsContainer)).isEqualTo(childCount);
+
+        int targetVisibility = childCount == 0 ? View.GONE : View.VISIBLE;
+        ViewGroup toolsLayout = getToolsLayout(mDialog);
+        assertThat(toolsLayout.getVisibility()).isEqualTo(targetVisibility);
+    }
+
     @After
     public void reset() {
         if (mDialogDelegate != null) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
index f06b105..cee17c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
@@ -18,7 +18,9 @@
 
 import android.bluetooth.BluetoothAdapter
 import android.bluetooth.BluetoothDevice
+import android.graphics.drawable.Drawable
 import android.testing.TestableLooper
+import android.util.Pair
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
@@ -64,6 +66,7 @@
     @Mock private lateinit var bluetoothDevice1: BluetoothDevice
     @Mock private lateinit var cachedDevice2: CachedBluetoothDevice
     @Mock private lateinit var bluetoothDevice2: BluetoothDevice
+    @Mock private lateinit var drawable: Drawable
     @Captor
     private lateinit var argumentCaptor: ArgumentCaptor<BluetoothAdapter.OnMetadataChangedListener>
     private lateinit var interactor: BluetoothDeviceMetadataInteractor
@@ -77,12 +80,14 @@
             whenever(cachedDevice1.name).thenReturn(DEVICE_NAME)
             whenever(cachedDevice1.address).thenReturn(DEVICE_ADDRESS)
             whenever(cachedDevice1.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+            whenever(cachedDevice1.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
             whenever(bluetoothDevice1.address).thenReturn(DEVICE_ADDRESS)
 
             whenever(cachedDevice2.device).thenReturn(bluetoothDevice2)
             whenever(cachedDevice2.name).thenReturn(DEVICE_NAME)
             whenever(cachedDevice2.address).thenReturn(DEVICE_ADDRESS)
             whenever(cachedDevice2.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+            whenever(cachedDevice2.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
             whenever(bluetoothDevice2.address).thenReturn(DEVICE_ADDRESS)
 
             interactor = bluetoothDeviceMetadataInteractor
@@ -175,14 +180,14 @@
                     .addOnMetadataChangedListener(
                         eq(bluetoothDevice1),
                         any(),
-                        argumentCaptor.capture()
+                        argumentCaptor.capture(),
                     )
 
                 val listener = argumentCaptor.value
                 listener.onMetadataChanged(
                     bluetoothDevice1,
                     BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
-                    ByteArray(0)
+                    ByteArray(0),
                 )
                 assertThat(update).isEqualTo(Unit)
             }
@@ -203,14 +208,14 @@
                     .addOnMetadataChangedListener(
                         eq(bluetoothDevice1),
                         any(),
-                        argumentCaptor.capture()
+                        argumentCaptor.capture(),
                     )
 
                 val listener = argumentCaptor.value
                 listener.onMetadataChanged(
                     bluetoothDevice1,
                     BluetoothDevice.METADATA_MODEL_NAME,
-                    ByteArray(0)
+                    ByteArray(0),
                 )
 
                 assertThat(update).isNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
index 82918a5..af2d7e4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
@@ -47,20 +47,14 @@
 
     @Test
     fun logAddWidget_componentNotLoggable_doNotLog() {
-        underTest.logAddWidget(
-            componentName = "com.green.package/my_test_widget",
-            rank = 1,
-        )
+        underTest.logAddWidget(componentName = "com.green.package/my_test_widget", rank = 1)
         verify(statsLogProxy, never())
-            .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt())
+            .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt(), anyInt())
     }
 
     @Test
     fun logAddWidget_componentLoggable_logAddEvent() {
-        underTest.logAddWidget(
-            componentName = "com.blue.package/my_test_widget",
-            rank = 1,
-        )
+        underTest.logAddWidget(componentName = "com.blue.package/my_test_widget", rank = 1)
         verify(statsLogProxy)
             .writeCommunalHubWidgetEventReported(
                 SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__ADD,
@@ -71,20 +65,14 @@
 
     @Test
     fun logRemoveWidget_componentNotLoggable_doNotLog() {
-        underTest.logRemoveWidget(
-            componentName = "com.yellow.package/my_test_widget",
-            rank = 2,
-        )
+        underTest.logRemoveWidget(componentName = "com.yellow.package/my_test_widget", rank = 2)
         verify(statsLogProxy, never())
-            .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt())
+            .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt(), anyInt())
     }
 
     @Test
     fun logRemoveWidget_componentLoggable_logRemoveEvent() {
-        underTest.logRemoveWidget(
-            componentName = "com.red.package/my_test_widget",
-            rank = 2,
-        )
+        underTest.logRemoveWidget(componentName = "com.red.package/my_test_widget", rank = 2)
         verify(statsLogProxy)
             .writeCommunalHubWidgetEventReported(
                 SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__REMOVE,
@@ -95,20 +83,14 @@
 
     @Test
     fun logTapWidget_componentNotLoggable_doNotLog() {
-        underTest.logTapWidget(
-            componentName = "com.yellow.package/my_test_widget",
-            rank = 2,
-        )
+        underTest.logTapWidget(componentName = "com.yellow.package/my_test_widget", rank = 2)
         verify(statsLogProxy, never())
-            .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt())
+            .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt(), anyInt())
     }
 
     @Test
     fun logTapWidget_componentLoggable_logRemoveEvent() {
-        underTest.logTapWidget(
-            componentName = "com.red.package/my_test_widget",
-            rank = 2,
-        )
+        underTest.logTapWidget(componentName = "com.red.package/my_test_widget", rank = 2)
         verify(statsLogProxy)
             .writeCommunalHubWidgetEventReported(
                 SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__TAP,
@@ -140,4 +122,43 @@
             )
         assertThat(statsEvents).hasSize(1)
     }
+
+    @Test
+    fun logResizeWidget_componentNotLoggable_doNotLog() {
+        underTest.logResizeWidget(
+            componentName = "com.green.package/my_test_widget",
+            rank = 1,
+            spanY = 2,
+        )
+        verify(statsLogProxy, never())
+            .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt(), anyInt())
+    }
+
+    @Test
+    fun logResizeWidget_componentLoggable_logResizeEvent() {
+        underTest.logResizeWidget(
+            componentName = "com.blue.package/my_test_widget",
+            rank = 1,
+            spanY = 2,
+        )
+        verify(statsLogProxy)
+            .writeCommunalHubWidgetEventReported(
+                SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__RESIZE,
+                "com.blue.package/my_test_widget",
+                rank = 1,
+                spanY = 2,
+            )
+    }
+
+    @Test
+    fun logResizeWidget_defaultSpanY_usesDefaultValue() {
+        underTest.logResizeWidget(componentName = "com.blue.package/my_test_widget", rank = 1)
+        verify(statsLogProxy)
+            .writeCommunalHubWidgetEventReported(
+                SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__RESIZE,
+                "com.blue.package/my_test_widget",
+                rank = 1,
+                spanY = 0,
+            )
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index cecc11e..6b851cb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -353,6 +353,32 @@
         assertThat(event.contentDescription).isEqualTo("Test Clock widget added to lock screen")
     }
 
+    @Test
+    fun onResizeWidget_logsMetrics() =
+        testScope.runTest {
+            val appWidgetId = 123
+            val spanY = 2
+            val widgetIdToRankMap = mapOf(appWidgetId to 1)
+            val componentName = ComponentName("test.package", "TestWidget")
+            val rank = 1
+
+            underTest.onResizeWidget(
+                appWidgetId = appWidgetId,
+                spanY = spanY,
+                widgetIdToRankMap = widgetIdToRankMap,
+                componentName = componentName,
+                rank = rank,
+            )
+
+            verify(communalInteractor).resizeWidget(appWidgetId, spanY, widgetIdToRankMap)
+            verify(metricsLogger)
+                .logResizeWidget(
+                    componentName = componentName.flattenToString(),
+                    rank = rank,
+                    spanY = spanY,
+                )
+        }
+
     private companion object {
         val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
         const val WIDGET_PICKER_PACKAGE_NAME = "widget_picker_package_name"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
index 5df9b7b..2efa2f3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
@@ -85,63 +85,62 @@
     @Test
     fun connectKeyboard_delayElapse_launchForKeyboard() =
         testScope.runTest {
-            launchAndAssert(TutorialType.KEYBOARD)
-
             keyboardRepository.setIsAnyKeyboardConnected(true)
             advanceTimeBy(LAUNCH_DELAY)
+
+            launchAndAssert(TutorialType.KEYBOARD)
         }
 
     @Test
     fun connectBothDevices_delayElapse_launchForBoth() =
         testScope.runTest {
-            launchAndAssert(TutorialType.BOTH)
-
             keyboardRepository.setIsAnyKeyboardConnected(true)
             touchpadRepository.setIsAnyTouchpadConnected(true)
             advanceTimeBy(LAUNCH_DELAY)
+
+            launchAndAssert(TutorialType.BOTH)
         }
 
     @Test
     fun connectBothDevice_delayNotElapse_launchNothing() =
         testScope.runTest {
-            launchAndAssert(TutorialType.NONE)
-
             keyboardRepository.setIsAnyKeyboardConnected(true)
             touchpadRepository.setIsAnyTouchpadConnected(true)
             advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
+
+            launchAndAssert(TutorialType.NONE)
         }
 
     @Test
     fun nothingConnect_delayElapse_launchNothing() =
         testScope.runTest {
-            launchAndAssert(TutorialType.NONE)
-
             keyboardRepository.setIsAnyKeyboardConnected(false)
             touchpadRepository.setIsAnyTouchpadConnected(false)
             advanceTimeBy(LAUNCH_DELAY)
+
+            launchAndAssert(TutorialType.NONE)
         }
 
     @Test
     fun connectKeyboard_thenTouchpad_delayElapse_launchForBoth() =
         testScope.runTest {
-            launchAndAssert(TutorialType.BOTH)
-
             keyboardRepository.setIsAnyKeyboardConnected(true)
             advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
             touchpadRepository.setIsAnyTouchpadConnected(true)
             advanceTimeBy(REMAINING_TIME)
+
+            launchAndAssert(TutorialType.BOTH)
         }
 
     @Test
     fun connectKeyboard_thenTouchpad_removeKeyboard_delayElapse_launchNothing() =
         testScope.runTest {
-            launchAndAssert(TutorialType.NONE)
-
             keyboardRepository.setIsAnyKeyboardConnected(true)
             advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
             touchpadRepository.setIsAnyTouchpadConnected(true)
             keyboardRepository.setIsAnyKeyboardConnected(false)
             advanceTimeBy(REMAINING_TIME)
+            launchAndAssert(TutorialType.NONE)
         }
 
     private suspend fun launchAndAssert(expectedTutorial: TutorialType) =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
new file mode 100644
index 0000000..c646d8f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
@@ -0,0 +1,291 @@
+/*
+ * 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.keyboard.shortcut.data.repository
+
+import android.content.Context
+import android.content.Context.INPUT_SERVICE
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputGestureData.createKeyTrigger
+import android.hardware.input.KeyGestureEvent
+import android.hardware.input.fakeInputManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.KeyEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.hardware.input.Flags.FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES
+import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.shortcut.customShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.userTracker
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
+
+    private val mockUserContext: Context = mock()
+    private val kosmos =
+        testKosmos().also {
+            it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
+        }
+
+    private val fakeInputManager = kosmos.fakeInputManager
+    private val testScope = kosmos.testScope
+    private val helper = kosmos.shortcutHelperTestHelper
+    private val repo = kosmos.customShortcutCategoriesRepository
+
+    @Before
+    fun setup() {
+        whenever(mockUserContext.getSystemService(INPUT_SERVICE))
+            .thenReturn(fakeInputManager.inputManager)
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+    fun categories_emitsCorrectlyConvertedShortcutCategories() {
+        testScope.runTest {
+            whenever(
+                    fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull())
+                )
+                .thenReturn(customizableInputGesturesWithSimpleShortcutCombinations)
+
+            helper.toggle(deviceId = 123)
+            val categories by collectLastValue(repo.categories)
+
+            assertThat(categories)
+                .containsExactlyElementsIn(expectedShortcutCategoriesWithSimpleShortcutCombination)
+        }
+    }
+
+    @Test
+    @DisableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+    fun categories_emitsEmptyListWhenFlagIsDisabled() {
+        testScope.runTest {
+            whenever(
+                    fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull())
+                )
+                .thenReturn(customizableInputGesturesWithSimpleShortcutCombinations)
+
+            helper.toggle(deviceId = 123)
+            val categories by collectLastValue(repo.categories)
+
+            assertThat(categories).isEmpty()
+        }
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+    fun categories_ignoresUnknownKeyGestureTypes() {
+        testScope.runTest {
+            whenever(
+                    fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull())
+                )
+                .thenReturn(customizableInputGestureWithUnknownKeyGestureType)
+
+            helper.toggle(deviceId = 123)
+            val categories by collectLastValue(repo.categories)
+
+            assertThat(categories).isEmpty()
+        }
+    }
+
+    private fun simpleInputGestureData(
+        keyCode: Int = KeyEvent.KEYCODE_A,
+        modifiers: Int = KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
+        keyGestureType: Int,
+    ): InputGestureData {
+        val builder = InputGestureData.Builder()
+        builder.setKeyGestureType(keyGestureType)
+        builder.setTrigger(createKeyTrigger(keyCode, modifiers))
+        return builder.build()
+    }
+
+    private fun simpleShortcutCategory(
+        category: ShortcutCategoryType,
+        subcategoryLabel: String,
+        shortcutLabel: String,
+    ): ShortcutCategory {
+        return ShortcutCategory(
+            type = category,
+            subCategories =
+                listOf(
+                    ShortcutSubCategory(
+                        label = subcategoryLabel,
+                        shortcuts = listOf(simpleShortcut(shortcutLabel)),
+                    )
+                ),
+        )
+    }
+
+    private fun simpleShortcut(label: String) =
+        Shortcut(
+            label = label,
+            commands =
+                listOf(
+                    ShortcutCommand(
+                        isCustom = true,
+                        keys =
+                            listOf(
+                                ShortcutKey.Text("Ctrl"),
+                                ShortcutKey.Text("Alt"),
+                                ShortcutKey.Text("A"),
+                            ),
+                    )
+                ),
+        )
+
+    private val customizableInputGestureWithUnknownKeyGestureType =
+        // These key gesture events are currently not supported by shortcut helper customizer
+        listOf(
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS
+            ),
+            simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY),
+        )
+
+    private val expectedShortcutCategoriesWithSimpleShortcutCombination =
+        listOf(
+            simpleShortcutCategory(System, "System apps", "Open assistant"),
+            simpleShortcutCategory(System, "System controls", "Go to home screen"),
+            simpleShortcutCategory(System, "System apps", "Open settings"),
+            simpleShortcutCategory(System, "System controls", "Lock screen"),
+            simpleShortcutCategory(System, "System controls", "View notifications"),
+            simpleShortcutCategory(System, "System apps", "Take a note"),
+            simpleShortcutCategory(System, "System controls", "Take screenshot"),
+            simpleShortcutCategory(System, "System controls", "Go back"),
+            simpleShortcutCategory(
+                MultiTasking,
+                "Split screen",
+                "Switch from split screen to full screen",
+            ),
+            simpleShortcutCategory(
+                MultiTasking,
+                "Split screen",
+                "Use split screen with current app on the left",
+            ),
+            simpleShortcutCategory(
+                MultiTasking,
+                "Split screen",
+                "Switch to app on left or above while using split screen",
+            ),
+            simpleShortcutCategory(
+                MultiTasking,
+                "Split screen",
+                "Use split screen with current app on the right",
+            ),
+            simpleShortcutCategory(
+                MultiTasking,
+                "Split screen",
+                "Switch to app on right or below while using split screen",
+            ),
+            simpleShortcutCategory(System, "System controls", "Show shortcuts"),
+            simpleShortcutCategory(System, "System controls", "View recent apps"),
+            simpleShortcutCategory(AppCategories, "Applications", "Calculator"),
+            simpleShortcutCategory(AppCategories, "Applications", "Calendar"),
+            simpleShortcutCategory(AppCategories, "Applications", "Chrome"),
+            simpleShortcutCategory(AppCategories, "Applications", "Contacts"),
+            simpleShortcutCategory(AppCategories, "Applications", "Gmail"),
+            simpleShortcutCategory(AppCategories, "Applications", "Maps"),
+            simpleShortcutCategory(AppCategories, "Applications", "Messages"),
+            simpleShortcutCategory(MultiTasking, "Recent apps", "Cycle forward through recent apps"),
+        )
+
+    private val customizableInputGesturesWithSimpleShortcutCombinations =
+        listOf(
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
+            ),
+            simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
+            ),
+            simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
+            ),
+            simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
+            ),
+            simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_BACK),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION
+            ),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT
+            ),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
+            ),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT
+            ),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT
+            ),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER
+            ),
+            simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR
+            ),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR
+            ),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER
+            ),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS
+            ),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL
+            ),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS
+            ),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING
+            ),
+            simpleInputGestureData(
+                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER
+            ),
+        )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt
similarity index 98%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt
index 620b8b6..f90ab1f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
 import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.defaultShortcutCategoriesRepository
 import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
@@ -47,7 +48,6 @@
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
 import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
-import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesRepository
 import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
 import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
 import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
@@ -71,7 +71,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class ShortcutHelperCategoriesRepositoryTest : SysuiTestCase() {
+class DefaultShortcutCategoriesRepositoryTest : SysuiTestCase() {
 
     private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
     private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
@@ -87,7 +87,7 @@
             it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
         }
 
-    private val repo = kosmos.shortcutHelperCategoriesRepository
+    private val repo = kosmos.defaultShortcutCategoriesRepository
     private val helper = kosmos.shortcutHelperTestHelper
     private val testScope = kosmos.testScope
     private val fakeInputManager = kosmos.fakeInputManager
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
index 93dede5..f1f6c61 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
@@ -38,7 +38,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.res.R;
 import com.android.systemui.retail.data.repository.FakeRetailModeRepository;
-import com.android.systemui.retail.domain.interactor.RetailModeInteractorImpl;
+import com.android.systemui.retail.domain.interactor.impl.RetailModeInteractorImpl;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index 03feceb..b72668b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -18,6 +18,7 @@
 
 import android.app.StatusBarManager
 import android.content.testableContext
+import android.graphics.Rect
 import android.testing.TestableLooper.RunWithLooper
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -43,6 +44,7 @@
 import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
 import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
 import com.android.systemui.statusbar.sysuiStatusBarStateController
+import com.android.systemui.util.animation.DisappearParameters
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -375,6 +377,92 @@
             }
         }
 
+    @Test
+    fun applyQsScrollPositionForClipping() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                val left = 1f
+                val top = 3f
+                val right = 5f
+                val bottom = 7f
+
+                underTest.applyNewQsScrollerBounds(left, top, right, bottom)
+
+                assertThat(qsMediaHost.currentClipping)
+                    .isEqualTo(Rect(left.toInt(), top.toInt(), right.toInt(), bottom.toInt()))
+            }
+        }
+
+    @Test
+    fun shouldUpdateMediaSquishiness_inSplitShadeFalse_mediaSquishinessSet() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                underTest.isInSplitShade = false
+                underTest.squishinessFraction = 0.3f
+
+                underTest.shouldUpdateSquishinessOnMedia = true
+                Snapshot.sendApplyNotifications()
+                runCurrent()
+
+                assertThat(underTest.qsMediaHost.squishFraction).isWithin(0.01f).of(0.3f)
+
+                underTest.shouldUpdateSquishinessOnMedia = false
+                Snapshot.sendApplyNotifications()
+                runCurrent()
+                assertThat(underTest.qsMediaHost.squishFraction).isWithin(0.01f).of(1f)
+            }
+        }
+
+    @Test
+    fun inSplitShade_differentStatusBarState_mediaSquishinessSet() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                underTest.isInSplitShade = true
+                underTest.squishinessFraction = 0.3f
+
+                sysuiStatusBarStateController.setState(StatusBarState.SHADE)
+                Snapshot.sendApplyNotifications()
+                runCurrent()
+                assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(0.3f)
+
+                sysuiStatusBarStateController.setState(StatusBarState.KEYGUARD)
+                runCurrent()
+                Snapshot.sendApplyNotifications()
+                runCurrent()
+                assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(1f)
+
+                sysuiStatusBarStateController.setState(StatusBarState.SHADE_LOCKED)
+                runCurrent()
+                Snapshot.sendApplyNotifications()
+                runCurrent()
+                assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(1f)
+            }
+        }
+
+    @Test
+    fun disappearParams() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                setMediaState(ACTIVE_MEDIA)
+
+                setConfigurationForMediaInRow(false)
+                Snapshot.sendApplyNotifications()
+                runCurrent()
+
+                assertThat(underTest.qqsMediaHost.disappearParameters)
+                    .isEqualTo(disappearParamsColumn)
+                assertThat(underTest.qsMediaHost.disappearParameters)
+                    .isEqualTo(disappearParamsColumn)
+
+                setConfigurationForMediaInRow(true)
+                Snapshot.sendApplyNotifications()
+                runCurrent()
+
+                assertThat(underTest.qqsMediaHost.disappearParameters).isEqualTo(disappearParamsRow)
+                assertThat(underTest.qsMediaHost.disappearParameters).isEqualTo(disappearParamsRow)
+            }
+        }
+
     private fun TestScope.setMediaState(state: MediaState) {
         with(kosmos) {
             val activeMedia = state == ACTIVE_MEDIA
@@ -404,6 +492,26 @@
         }
 
         private const val epsilon = 0.001f
+
+        private val disappearParamsColumn =
+            DisappearParameters().apply {
+                fadeStartPosition = 0.95f
+                disappearStart = 0f
+                disappearEnd = 0.95f
+                disappearSize.set(1f, 0f)
+                gonePivot.set(0f, 0f)
+                contentTranslationFraction.set(0f, 1f)
+            }
+
+        private val disappearParamsRow =
+            DisappearParameters().apply {
+                fadeStartPosition = 0.95f
+                disappearStart = 0f
+                disappearEnd = 0.6f
+                disappearSize.set(0f, 0.4f)
+                gonePivot.set(1f, 0f)
+                contentTranslationFraction.set(0.25f, 1f)
+            }
     }
 }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
index ba7a65d..47bfda4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.retail.data.repository.impl.RetailModeSettingsRepository
 import com.android.systemui.util.settings.FakeGlobalSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
index b536520..b47dcb5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.retail.data.repository.FakeRetailModeRepository
+import com.android.systemui.retail.domain.interactor.impl.RetailModeInteractorImpl
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 152911a..af0274b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
 import com.android.systemui.classifier.FalsingCollector
@@ -54,6 +55,7 @@
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.flags.EnableSceneContainer
@@ -114,6 +116,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
@@ -2356,6 +2359,58 @@
             assertThat(isLockscreenEnabled).isTrue()
         }
 
+    @Test
+    fun replacesLockscreenSceneOnBackStack_whenUnlockdViaAlternateBouncer_fromShade() =
+        testScope.runTest {
+            val transitionState =
+                prepareState(
+                    isDeviceUnlocked = false,
+                    initialSceneKey = Scenes.Lockscreen,
+                    authenticationMethod = AuthenticationMethodModel.Pin,
+                )
+            underTest.start()
+
+            val isUnlocked by
+                collectLastValue(
+                    kosmos.deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked }
+                )
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val backStack by collectLastValue(sceneBackInteractor.backStack)
+            val isAlternateBouncerVisible by
+                collectLastValue(kosmos.alternateBouncerInteractor.isVisible)
+            assertThat(isUnlocked).isFalse()
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(isAlternateBouncerVisible).isFalse()
+
+            // Change to shade.
+            sceneInteractor.changeScene(Scenes.Shade, "")
+            transitionState.value = ObservableTransitionState.Idle(Scenes.Shade)
+            runCurrent()
+            assertThat(isUnlocked).isFalse()
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+            assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Lockscreen)
+            assertThat(isAlternateBouncerVisible).isFalse()
+
+            // Show the alternate bouncer.
+            kosmos.alternateBouncerInteractor.forceShow()
+            kosmos.sysuiStatusBarStateController.leaveOpen = true // leave shade open
+            runCurrent()
+            assertThat(isUnlocked).isFalse()
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+            assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Lockscreen)
+            assertThat(isAlternateBouncerVisible).isTrue()
+
+            // Trigger a fingerprint unlock.
+            kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
+            assertThat(isUnlocked).isTrue()
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+            assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Gone)
+            assertThat(isAlternateBouncerVisible).isFalse()
+        }
+
     private fun TestScope.emulateSceneTransition(
         transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
         toScene: SceneKey,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index a862fdf..778e822 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -19,6 +19,8 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.graphics.Rect
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.view.Display
 import android.view.DisplayCutout
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -29,6 +31,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.leak.RotationUtils
 import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE
@@ -1051,7 +1054,8 @@
     }
 
     @Test
-    fun onMaxBoundsChanged_beforeStart_listenerNotNotified() {
+    @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+    fun onMaxBoundsChanged_beforeStart_flagEnabled_listenerNotNotified() {
         // Start out with an existing configuration with bounds
         configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
         configurationController.onConfigurationChanged(configuration)
@@ -1083,7 +1087,41 @@
     }
 
     @Test
-    fun onDensityOrFontScaleChanged_beforeStart_listenerNotNotified() {
+    @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+    fun onMaxBoundsChanged_beforeStart_flagDisabled_listenerNotNotified() {
+        // Start out with an existing configuration with bounds
+        configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+        configurationController.onConfigurationChanged(configuration)
+        val provider =
+            StatusBarContentInsetsProviderImpl(
+                contextMock,
+                configurationController,
+                mock<DumpManager>(),
+                mock<CommandRegistry>(),
+                mock<SysUICutoutProvider>(),
+            )
+        val listener =
+            object : StatusBarContentInsetsChangedListener {
+                var triggered = false
+
+                override fun onStatusBarContentInsetsChanged() {
+                    triggered = true
+                }
+            }
+        provider.addCallback(listener)
+
+        // WHEN the config is updated with new bounds
+        // but provider is not started
+        configuration.windowConfiguration.setMaxBounds(0, 0, 456, 789)
+        configurationController.onConfigurationChanged(configuration)
+
+        // THEN the listener is notified
+        assertThat(listener.triggered).isTrue()
+    }
+
+    @Test
+    @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+    fun onDensityOrFontScaleChanged_beforeStart_flagEnabled_listenerNotNotified() {
         configuration.densityDpi = 12
         val provider =
             StatusBarContentInsetsProviderImpl(
@@ -1112,6 +1150,36 @@
     }
 
     @Test
+    @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+    fun onDensityOrFontScaleChanged_beforeStart_flagDisabled_listenerNotified() {
+        configuration.densityDpi = 12
+        val provider =
+            StatusBarContentInsetsProviderImpl(
+                contextMock,
+                configurationController,
+                mock<DumpManager>(),
+                mock<CommandRegistry>(),
+                mock<SysUICutoutProvider>(),
+            )
+        val listener =
+            object : StatusBarContentInsetsChangedListener {
+                var triggered = false
+
+                override fun onStatusBarContentInsetsChanged() {
+                    triggered = true
+                }
+            }
+        provider.addCallback(listener)
+
+        // WHEN the config is updated, but the provider is not started
+        configuration.densityDpi = 20
+        configurationController.onConfigurationChanged(configuration)
+
+        // THEN the listener is notified
+        assertThat(listener.triggered).isTrue()
+    }
+
+    @Test
     fun onDensityOrFontScaleChanged_afterStart_listenerNotified() {
         configuration.densityDpi = 12
         val provider =
@@ -1169,7 +1237,8 @@
     }
 
     @Test
-    fun onThemeChanged_beforeStart_listenerNotNotified() {
+    @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+    fun onThemeChanged_beforeStart_flagEnabled_listenerNotNotified() {
         val provider =
             StatusBarContentInsetsProviderImpl(
                 contextMock,
@@ -1193,6 +1262,32 @@
         assertThat(listener.triggered).isFalse()
     }
 
+    @Test
+    @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+    fun onThemeChanged_beforeStart_flagDisabled_listenerNotified() {
+        val provider =
+            StatusBarContentInsetsProviderImpl(
+                contextMock,
+                configurationController,
+                mock<DumpManager>(),
+                mock<CommandRegistry>(),
+                mock<SysUICutoutProvider>(),
+            )
+        val listener =
+            object : StatusBarContentInsetsChangedListener {
+                var triggered = false
+
+                override fun onStatusBarContentInsetsChanged() {
+                    triggered = true
+                }
+            }
+        provider.addCallback(listener)
+
+        configurationController.notifyThemeChanged()
+
+        assertThat(listener.triggered).isTrue()
+    }
+
     private fun assertRects(
         expected: Rect,
         actual: Rect,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt
index cdc7aa2..9888574 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt
@@ -32,6 +32,8 @@
     override fun bind(
         view: View,
         viewModel: HomeStatusBarViewModel,
+        systemEventChipAnimateIn: ((View) -> Unit)?,
+        systemEventChipAnimateOut: ((View) -> Unit)?,
         listener: StatusBarVisibilityChangeListener,
     ) {
         this.listener = listener
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index 02c1540..eef5753 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -19,6 +19,7 @@
 import android.view.View
 import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -53,11 +54,14 @@
             )
         )
 
-    override val isSystemInfoVisible =
+    override val systemInfoCombinedVis =
         MutableStateFlow(
-            HomeStatusBarViewModel.VisibilityModel(
-                visibility = View.GONE,
-                shouldAnimateChange = false,
+            HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel(
+                HomeStatusBarViewModel.VisibilityModel(
+                    visibility = View.GONE,
+                    shouldAnimateChange = false,
+                ),
+                Idle,
             )
         )
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index b3a73d8..c4d2569 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -60,11 +60,16 @@
 import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
 import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
 import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.statusbar.events.data.repository.systemStatusEventAnimationRepository
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingIn
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingOut
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.emptyFlow
@@ -90,6 +95,7 @@
     private val activeNotificationListRepository = kosmos.activeNotificationListRepository
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val disableFlagsRepository = kosmos.fakeDisableFlagsRepository
+    private val systemStatusEventAnimationRepository = kosmos.systemStatusEventAnimationRepository
 
     private lateinit var underTest: HomeStatusBarViewModel
 
@@ -546,25 +552,50 @@
     @Test
     fun isSystemInfoVisible_allowedByDisableFlags_visible() =
         testScope.runTest {
-            val latest by collectLastValue(underTest.isSystemInfoVisible)
+            val latest by collectLastValue(underTest.systemInfoCombinedVis)
             transitionKeyguardToGone()
 
             disableFlagsRepository.disableFlags.value =
                 DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
 
-            assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
+            assertThat(latest!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
         }
 
     @Test
     fun isSystemInfoVisible_notAllowedByDisableFlags_gone() =
         testScope.runTest {
-            val latest by collectLastValue(underTest.isSystemInfoVisible)
+            val latest by collectLastValue(underTest.systemInfoCombinedVis)
             transitionKeyguardToGone()
 
             disableFlagsRepository.disableFlags.value =
                 DisableFlagsModel(DISABLE_SYSTEM_INFO, DISABLE2_NONE)
 
-            assertThat(latest!!.visibility).isEqualTo(View.GONE)
+            assertThat(latest!!.baseVisibility.visibility).isEqualTo(View.GONE)
+        }
+
+    @Test
+    fun systemInfoCombineVis_animationsPassThrough() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.systemInfoCombinedVis)
+            transitionKeyguardToGone()
+
+            assertThat(latest!!.baseVisibility)
+                .isEqualTo(VisibilityModel(visibility = View.VISIBLE, shouldAnimateChange = false))
+            assertThat(latest!!.animationState).isEqualTo(Idle)
+
+            // WHEN the animation state changes, but the visibility state doesn't change
+            systemStatusEventAnimationRepository.animationState.value = AnimatingIn
+
+            // THEN the visibility is the same
+            assertThat(latest!!.baseVisibility)
+                .isEqualTo(VisibilityModel(visibility = View.VISIBLE, shouldAnimateChange = false))
+            // THEN the animation state updates
+            assertThat(latest!!.animationState).isEqualTo(AnimatingIn)
+
+            systemStatusEventAnimationRepository.animationState.value = AnimatingOut
+            assertThat(latest!!.baseVisibility)
+                .isEqualTo(VisibilityModel(visibility = View.VISIBLE, shouldAnimateChange = false))
+            assertThat(latest!!.animationState).isEqualTo(AnimatingOut)
         }
 
     @Test
@@ -573,7 +604,7 @@
         testScope.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
-            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
 
             keyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.GONE,
@@ -583,7 +614,7 @@
 
             assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
-            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
         }
 
     @Test
@@ -592,13 +623,13 @@
         testScope.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
-            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
 
             kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
 
             assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
-            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
         }
 
     @Test
@@ -607,7 +638,7 @@
         testScope.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
-            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
 
             keyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
@@ -617,7 +648,7 @@
 
             assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
-            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
         }
 
     @Test
@@ -626,13 +657,13 @@
         testScope.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
-            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
 
             kosmos.sceneContainerRepository.snapToScene(Scenes.Bouncer)
 
             assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
-            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
         }
 
     @Test
@@ -641,7 +672,7 @@
         testScope.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
-            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
 
             keyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
@@ -651,7 +682,7 @@
 
             assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
-            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
+            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
         }
 
     @Test
@@ -660,14 +691,14 @@
         testScope.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
-            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
 
             kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
             kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, taskInfo = null)
 
             assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
-            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
+            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
         }
 
     @Test
@@ -676,13 +707,13 @@
         testScope.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
-            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
 
             transitionKeyguardToGone()
 
             assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
-            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
+            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
         }
 
     @Test
@@ -691,14 +722,14 @@
         testScope.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
-            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
             transitionKeyguardToGone()
 
             kosmos.shadeTestUtil.setShadeExpansion(0f)
 
             assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
-            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
+            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
         }
 
     @Test
@@ -707,13 +738,13 @@
         testScope.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
-            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
 
             kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)
 
             assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
-            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
+            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
         }
 
     @Test
@@ -722,14 +753,14 @@
         testScope.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
-            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
             transitionKeyguardToGone()
 
             kosmos.shadeTestUtil.setShadeExpansion(1f)
 
             assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
-            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
         }
 
     @Test
@@ -738,14 +769,14 @@
         testScope.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
-            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
             transitionKeyguardToGone()
 
             kosmos.sceneContainerRepository.snapToScene(Scenes.Shade)
 
             assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
-            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
         }
 
     @Test
@@ -754,7 +785,7 @@
         testScope.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
-            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
 
             // Secure camera is an occluding activity
             keyguardTransitionRepository.sendTransitionSteps(
@@ -766,7 +797,7 @@
 
             assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
-            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
         }
 
     @Test
@@ -775,7 +806,7 @@
         testScope.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
-            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
 
             kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
             // Secure camera is an occluding activity
@@ -784,7 +815,7 @@
 
             assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
-            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
         }
 
     private fun activeNotificationsStore(notifications: List<ActiveNotificationModel>) =
diff --git a/packages/SystemUI/pods/Android.bp b/packages/SystemUI/pods/Android.bp
new file mode 100644
index 0000000..e45f317
--- /dev/null
+++ b/packages/SystemUI/pods/Android.bp
@@ -0,0 +1,22 @@
+//
+// 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 {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+    // The package default_visibility specified here is inherited to subpackages that do not
+    // specify default_visibility:
+    default_visibility: ["//visibility:private"],
+}
diff --git a/packages/SystemUI/pods/com/android/systemui/dagger/Android.bp b/packages/SystemUI/pods/com/android/systemui/dagger/Android.bp
new file mode 100644
index 0000000..df90be8
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/Android.bp
@@ -0,0 +1,34 @@
+//
+// 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.
+//
+
+soong_namespace {
+}
+
+package {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+    name: "api",
+    srcs: [
+        "**/*.java",
+        "**/*.kt",
+    ],
+    libs: [
+        "jsr330",
+    ],
+    visibility: ["//frameworks/base/packages/SystemUI:__subpackages__"],
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUISingleton.java b/packages/SystemUI/pods/com/android/systemui/dagger/SysUISingleton.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/SysUISingleton.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/SysUISingleton.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Application.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Application.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Application.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Application.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Background.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Background.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Background.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Background.java
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/Android.bp b/packages/SystemUI/pods/com/android/systemui/retail/Android.bp
new file mode 100644
index 0000000..f047848
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/retail/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+soong_namespace {
+}
+
+package {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+    name: "impl",
+    srcs: ["*.kt"],
+    libs: [
+        "jsr330",
+        "dagger2",
+        "SystemUICommon",
+        "kotlinx_coroutines",
+    ],
+    static_libs: [
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/data:impl",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/domain:impl",
+    ],
+    visibility: ["//frameworks/base/packages/SystemUI"],
+}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt b/packages/SystemUI/pods/com/android/systemui/retail/RetailModeModule.kt
similarity index 83%
rename from packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt
rename to packages/SystemUI/pods/com/android/systemui/retail/RetailModeModule.kt
index e863949..c20e368 100644
--- a/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/RetailModeModule.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.retail.dagger
+package com.android.systemui.retail
 
 import com.android.systemui.retail.data.repository.RetailModeRepository
-import com.android.systemui.retail.data.repository.RetailModeSettingsRepository
+import com.android.systemui.retail.data.repository.impl.RetailModeSettingsRepository
 import com.android.systemui.retail.domain.interactor.RetailModeInteractor
-import com.android.systemui.retail.domain.interactor.RetailModeInteractorImpl
+import com.android.systemui.retail.domain.interactor.impl.RetailModeInteractorImpl
 import dagger.Binds
 import dagger.Module
 
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/data/Android.bp b/packages/SystemUI/pods/com/android/systemui/retail/data/Android.bp
new file mode 100644
index 0000000..f148a7c
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/retail/data/Android.bp
@@ -0,0 +1,55 @@
+//
+// 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.
+//
+
+soong_namespace {
+}
+
+package {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+    name: "api",
+    srcs: ["repository/*.kt"],
+    libs: [
+        "kotlinx_coroutines",
+    ],
+    visibility: [
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/dagger",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/domain",
+    ],
+}
+
+java_library {
+    name: "impl",
+    srcs: ["repository/impl/*.kt"],
+    libs: [
+        "jsr330",
+        "kotlinx_coroutines",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/util/settings:api",
+        "SystemUICommon",
+    ],
+    static_libs: [
+        "api",
+    ],
+    visibility: [
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/dagger",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/domain",
+    ],
+}
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/data/repository/RetailModeRepository.kt b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/RetailModeRepository.kt
new file mode 100644
index 0000000..c9eac25
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/RetailModeRepository.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.retail.data.repository
+
+import kotlinx.coroutines.flow.StateFlow
+
+/** Repository to track if the device is in Retail mode */
+interface RetailModeRepository {
+    /** Flow of whether the device is currently in retail mode. */
+    val retailMode: StateFlow<Boolean>
+
+    /** Last value of whether the device is in retail mode. */
+    val inRetailMode: Boolean
+        get() = retailMode.value
+}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/impl/RetailModeSettingsRepository.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
rename to packages/SystemUI/pods/com/android/systemui/retail/data/repository/impl/RetailModeSettingsRepository.kt
index 09fd7df..8955263 100644
--- a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/impl/RetailModeSettingsRepository.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.retail.data.repository
+package com.android.systemui.retail.data.repository.impl
 
 import android.database.ContentObserver
 import android.provider.Settings
@@ -22,6 +22,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.retail.data.repository.RetailModeRepository
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -34,16 +35,6 @@
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
 
-/** Repository to track if the device is in Retail mode */
-interface RetailModeRepository {
-    /** Flow of whether the device is currently in retail mode. */
-    val retailMode: StateFlow<Boolean>
-
-    /** Last value of whether the device is in retail mode. */
-    val inRetailMode: Boolean
-        get() = retailMode.value
-}
-
 /**
  * Tracks [Settings.Global.DEVICE_DEMO_MODE].
  *
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/domain/Android.bp b/packages/SystemUI/pods/com/android/systemui/retail/domain/Android.bp
new file mode 100644
index 0000000..787861c
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/retail/domain/Android.bp
@@ -0,0 +1,46 @@
+//
+// 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.
+//
+
+soong_namespace {
+}
+
+package {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+    name: "api",
+    srcs: ["interactor/*.kt"],
+    visibility: [
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail",
+    ],
+}
+
+java_library {
+    name: "impl",
+    srcs: ["interactor/impl/*.kt"],
+    libs: [
+        "jsr330",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/data:api",
+    ],
+    static_libs: [
+        "api",
+    ],
+    visibility: [
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail",
+    ],
+}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
similarity index 65%
copy from packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
copy to packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
index eea452c..748e34d 100644
--- a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -16,22 +16,8 @@
 
 package com.android.systemui.retail.domain.interactor
 
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.retail.data.repository.RetailModeRepository
-import javax.inject.Inject
-
 /** Interactor to determine if the device is currently in retail mode */
 interface RetailModeInteractor {
     /** Whether the device is currently in retail mode */
     val isInRetailMode: Boolean
 }
-
-@SysUISingleton
-class RetailModeInteractorImpl
-@Inject
-constructor(
-    private val repository: RetailModeRepository,
-) : RetailModeInteractor {
-    override val isInRetailMode: Boolean
-        get() = repository.inRetailMode
-}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/impl/RetailModeInteractorImpl.kt
similarity index 79%
rename from packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
rename to packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/impl/RetailModeInteractorImpl.kt
index eea452c..8dbe562 100644
--- a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/impl/RetailModeInteractorImpl.kt
@@ -14,18 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.retail.domain.interactor
+package com.android.systemui.retail.domain.interactor.impl
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.retail.data.repository.RetailModeRepository
+import com.android.systemui.retail.domain.interactor.RetailModeInteractor
 import javax.inject.Inject
 
-/** Interactor to determine if the device is currently in retail mode */
-interface RetailModeInteractor {
-    /** Whether the device is currently in retail mode */
-    val isInRetailMode: Boolean
-}
-
 @SysUISingleton
 class RetailModeInteractorImpl
 @Inject
diff --git a/packages/SystemUI/pods/com/android/systemui/util/settings/Android.bp b/packages/SystemUI/pods/com/android/systemui/util/settings/Android.bp
new file mode 100644
index 0000000..1aa7729
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/Android.bp
@@ -0,0 +1,40 @@
+//
+// 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.
+//
+
+soong_namespace {
+}
+
+package {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+    name: "api",
+    srcs: [
+        "*.java",
+        "*.kt",
+    ],
+    libs: [
+        "//frameworks/libs/systemui:tracinglib-platform",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api",
+        "SystemUICommon",
+        "androidx.annotation_annotation",
+        "kotlinx_coroutines_android",
+        "jsr330",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+    visibility: ["//frameworks/base/packages/SystemUI:__subpackages__"],
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettings.java b/packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettings.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettings.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettings.java
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettingsImpl.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettingsImpl.java
index 7fcabe4..d68501f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettingsImpl.java
@@ -23,7 +23,7 @@
 import android.net.Uri;
 import android.provider.Settings;
 
-import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
+import com.android.systemui.util.settings.SettingsSingleThreadBackground;
 
 import kotlinx.coroutines.CoroutineDispatcher;
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettings.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettings.java
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettingsImpl.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettingsImpl.java
index c296481..211a6f4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettingsImpl.java
@@ -21,7 +21,7 @@
 import android.net.Uri;
 import android.provider.Settings;
 
-import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
+import com.android.systemui.util.settings.SettingsSingleThreadBackground;
 
 import kotlinx.coroutines.CoroutineDispatcher;
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxyExt.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxyExt.kt
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SettingsSingleThreadBackground.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsSingleThreadBackground.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/util/kotlin/SettingsSingleThreadBackground.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SettingsSingleThreadBackground.java
index e13981d..5632a36 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SettingsSingleThreadBackground.java
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsSingleThreadBackground.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.kotlin;
+package com.android.systemui.util.settings;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettings.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettings.java
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettingsImpl.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettingsImpl.java
index e670b2c..1b3f74e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettingsImpl.java
@@ -21,7 +21,7 @@
 import android.net.Uri;
 import android.provider.Settings;
 
-import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
+import com.android.systemui.util.settings.SettingsSingleThreadBackground;
 
 import kotlinx.coroutines.CoroutineDispatcher;
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
rename to packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt
diff --git a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml b/packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml
similarity index 94%
rename from packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml
rename to packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml
index c83b6d3..dfefb9d 100644
--- a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml
+++ b/packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml
@@ -14,7 +14,8 @@
     limitations under the License.
 -->
 
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+<layer-list
+    xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:paddingMode="stack">
     <item>
@@ -30,8 +31,8 @@
         android:end="20dp"
         android:gravity="end|center_vertical">
         <vector
-            android:width="@dimen/hearing_devices_preset_spinner_arrow_size"
-            android:height="@dimen/hearing_devices_preset_spinner_arrow_size"
+            android:width="@dimen/hearing_devices_preset_spinner_icon_size"
+            android:height="@dimen/hearing_devices_preset_spinner_icon_size"
             android:viewportWidth="24"
             android:viewportHeight="24"
             android:tint="?androidprv:attr/colorControlNormal">
diff --git a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_popup_background.xml b/packages/SystemUI/res/drawable/hearing_devices_spinner_popup_background.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/hearing_devices_preset_spinner_popup_background.xml
rename to packages/SystemUI/res/drawable/hearing_devices_spinner_popup_background.xml
diff --git a/packages/SystemUI/res/drawable/hearing_devices_spinner_selected_background.xml b/packages/SystemUI/res/drawable/hearing_devices_spinner_selected_background.xml
new file mode 100644
index 0000000..c708d22
--- /dev/null
+++ b/packages/SystemUI/res/drawable/hearing_devices_spinner_selected_background.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    android:drawable="@drawable/settingslib_switch_bar_bg_on"
+    android:insetTop="8dp"
+    android:insetBottom="8dp"
+    android:insetLeft="11dp"
+    android:insetRight="11dp" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_check.xml b/packages/SystemUI/res/drawable/ic_check.xml
new file mode 100644
index 0000000..80707d8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_check.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2024 The Android Open Source Project
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+         http://www.apache.org/licenses/LICENSE-2.0
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:pathData="M0 0h24v24H0z"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml
index b9314c7..124aec6a 100644
--- a/packages/SystemUI/res/layout/bluetooth_device_item.xml
+++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml
@@ -27,8 +27,8 @@
 
     <ImageView
         android:id="@+id/bluetooth_device_icon"
-        android:layout_width="24dp"
-        android:layout_height="24dp"
+        android:layout_width="36dp"
+        android:layout_height="36dp"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
@@ -36,8 +36,8 @@
 
     <TextView
         android:layout_width="0dp"
+        android:layout_height="wrap_content"
         android:id="@+id/bluetooth_device_name"
-        style="@style/BluetoothTileDialog.DeviceName"
         android:textDirection="locale"
         android:textAlignment="gravity"
         android:paddingStart="20dp"
@@ -54,8 +54,8 @@
 
     <TextView
         android:layout_width="0dp"
+        android:layout_height="wrap_content"
         android:id="@+id/bluetooth_device_summary"
-        style="@style/BluetoothTileDialog.DeviceSummary"
         android:paddingStart="20dp"
         android:paddingEnd="10dp"
         android:paddingBottom="15dp"
@@ -65,7 +65,8 @@
         app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon"
         app:layout_constraintEnd_toStartOf="@+id/guideline"
         app:layout_constraintBottom_toBottomOf="parent"
-        android:gravity="center_vertical" />
+        android:gravity="center_vertical"
+        android:textSize="14sp" />
 
     <androidx.constraintlayout.widget.Guideline
         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index b4eaa40..1f93717 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -32,7 +32,7 @@
         android:ellipsize="end"
         android:gravity="center_vertical|center_horizontal"
         android:text="@string/quick_settings_bluetooth_label"
-        android:textAppearance="@style/TextAppearance.Dialog.Title"
+        android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
         android:textSize="24sp"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
@@ -49,7 +49,8 @@
         android:gravity="center_vertical|center_horizontal"
         android:maxLines="2"
         android:text="@string/quick_settings_bluetooth_tile_subtitle"
-        android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+        android:textSize="14sp"
+        android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_title" />
@@ -105,7 +106,7 @@
                 android:paddingStart="36dp"
                 android:text="@string/turn_on_bluetooth"
                 android:clickable="false"
-                android:textAppearance="@style/TextAppearance.Dialog.Title"
+                android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
                 android:textSize="16sp"
                 app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle"
                 app:layout_constraintStart_toStartOf="parent"
@@ -146,7 +147,7 @@
                 android:paddingEnd="15dp"
                 android:paddingStart="36dp"
                 android:clickable="false"
-                android:textAppearance="@style/TextAppearance.Dialog.Title"
+                android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
                 android:textSize="16sp"
                 app:layout_constraintEnd_toStartOf="@+id/bluetooth_auto_on_toggle"
                 app:layout_constraintStart_toStartOf="parent"
@@ -187,7 +188,8 @@
                 android:layout_marginTop="20dp"
                 android:paddingStart="36dp"
                 android:paddingEnd="40dp"
-                android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+                android:textSize="14sp"
+                android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toBottomOf="@id/bluetooth_auto_on_toggle_info_icon" />
@@ -204,7 +206,7 @@
                 android:id="@+id/see_all_button"
                 style="@style/BluetoothTileDialog.Device"
                 android:paddingEnd="0dp"
-                android:paddingStart="20dp"
+                android:paddingStart="26dp"
                 android:background="@drawable/bluetooth_tile_dialog_bg_off"
                 android:layout_width="0dp"
                 android:layout_height="64dp"
@@ -214,11 +216,11 @@
                 app:layout_constraintTop_toBottomOf="@+id/device_list"
                 app:layout_constraintBottom_toTopOf="@+id/pair_new_device_button"
                 android:drawableStart="@drawable/ic_arrow_forward"
-                android:drawablePadding="20dp"
+                android:drawablePadding="26dp"
                 android:drawableTint="?android:attr/textColorPrimary"
                 android:text="@string/see_all_bluetooth_devices"
                 android:textSize="14sp"
-                android:textAppearance="@style/TextAppearance.Dialog.Title"
+                android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
                 android:textDirection="locale"
                 android:textAlignment="viewStart"
                 android:maxLines="1"
@@ -229,7 +231,7 @@
                 android:id="@+id/pair_new_device_button"
                 style="@style/BluetoothTileDialog.Device"
                 android:paddingEnd="0dp"
-                android:paddingStart="20dp"
+                android:paddingStart="26dp"
                 android:background="@drawable/bluetooth_tile_dialog_bg_off"
                 android:layout_width="0dp"
                 android:layout_height="64dp"
@@ -238,11 +240,11 @@
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintTop_toBottomOf="@+id/see_all_button"
                 android:drawableStart="@drawable/ic_add"
-                android:drawablePadding="20dp"
+                android:drawablePadding="26dp"
                 android:drawableTint="?android:attr/textColorPrimary"
                 android:text="@string/pair_new_bluetooth_devices"
                 android:textSize="14sp"
-                android:textAppearance="@style/TextAppearance.Dialog.Title"
+                android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
                 android:textDirection="locale"
                 android:textAlignment="viewStart"
                 android:maxLines="1"
@@ -259,6 +261,7 @@
             <Button
                 android:id="@+id/audio_sharing_button"
                 style="@style/BluetoothTileDialog.AudioSharingButton"
+                android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="9dp"
@@ -282,6 +285,7 @@
             <Button
                 android:id="@+id/done_button"
                 style="@style/Widget.Dialog.Button"
+                android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="9dp"
diff --git a/packages/SystemUI/res/layout/hearing_devices_preset_dropdown_item.xml b/packages/SystemUI/res/layout/hearing_devices_preset_dropdown_item.xml
deleted file mode 100644
index 17c0222..0000000
--- a/packages/SystemUI/res/layout/hearing_devices_preset_dropdown_item.xml
+++ /dev/null
@@ -1,26 +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.
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/hearing_devices_preset_option_text"
-    style="?android:attr/spinnerDropDownItemStyle"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:minHeight="@dimen/hearing_devices_preset_spinner_height"
-    android:paddingStart="@dimen/hearing_devices_preset_spinner_text_padding_start"
-    android:gravity="center_vertical"
-    android:textDirection="locale"
-    android:ellipsize="end" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_devices_preset_spinner_selected.xml b/packages/SystemUI/res/layout/hearing_devices_preset_spinner_selected.xml
deleted file mode 100644
index d512e7c..0000000
--- a/packages/SystemUI/res/layout/hearing_devices_preset_spinner_selected.xml
+++ /dev/null
@@ -1,48 +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.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:minHeight="@dimen/hearing_devices_preset_spinner_height"
-    android:paddingStart="@dimen/hearing_devices_preset_spinner_text_padding_start"
-    android:paddingTop="@dimen/hearing_devices_preset_spinner_text_padding_vertical"
-    android:paddingBottom="@dimen/hearing_devices_preset_spinner_text_padding_vertical"
-    android:orientation="vertical">
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:textAppearance="@style/TextAppearance.Dialog.Title"
-        android:lineSpacingExtra="6dp"
-        android:text="@string/hearing_devices_preset_label"
-        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-        android:textSize="14sp"
-        android:gravity="center_vertical"
-        android:textDirection="locale"
-        android:layout_weight="1" />
-    <TextView
-        android:id="@+id/hearing_devices_preset_option_text"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:textAppearance="@style/TextAppearance.Dialog.Body"
-        android:lineSpacingExtra="6dp"
-        android:gravity="center_vertical"
-        android:ellipsize="end"
-        android:maxLines="1"
-        android:textDirection="locale"
-        android:layout_weight="1" />
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_devices_spinner_dropdown_view.xml b/packages/SystemUI/res/layout/hearing_devices_spinner_dropdown_view.xml
new file mode 100644
index 0000000..70f2cd5
--- /dev/null
+++ b/packages/SystemUI/res/layout/hearing_devices_spinner_dropdown_view.xml
@@ -0,0 +1,45 @@
+<!--
+    Copyright (C) 2024 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/bluetooth_dialog_device_height"
+    android:paddingStart="@dimen/hearing_devices_preset_spinner_padding"
+    android:paddingEnd="@dimen/hearing_devices_preset_spinner_padding"
+    android:orientation="horizontal">
+
+    <ImageView
+        android:id="@+id/hearing_devices_spinner_check_icon"
+        android:layout_width="@dimen/hearing_devices_preset_spinner_icon_size"
+        android:layout_height="@dimen/hearing_devices_preset_spinner_icon_size"
+        android:layout_gravity="center_vertical"
+        android:layout_marginEnd="@dimen/hearing_devices_layout_margin"
+        android:tint="?androidprv:attr/materialColorOnPrimaryContainer"
+        android:src="@drawable/ic_check"
+        android:contentDescription="@string/hearing_devices_spinner_item_selected"/>
+    <TextView
+        android:id="@+id/hearing_devices_spinner_text"
+        style="?android:attr/spinnerDropDownItemStyle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:textDirection="locale"
+        android:paddingStart="0dp"
+        android:maxLines="1"
+        android:ellipsize="end" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/hearing_devices_spinner_view.xml b/packages/SystemUI/res/layout/hearing_devices_spinner_view.xml
new file mode 100644
index 0000000..7574244
--- /dev/null
+++ b/packages/SystemUI/res/layout/hearing_devices_spinner_view.xml
@@ -0,0 +1,32 @@
+<!--
+    Copyright (C) 2024 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/bluetooth_dialog_device_height"
+    android:paddingStart="@dimen/hearing_devices_preset_spinner_padding"
+    android:paddingEnd="@dimen/hearing_devices_preset_spinner_padding">
+    <TextView
+        android:id="@+id/hearing_devices_spinner_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.Dialog.Body"
+        android:layout_gravity="center_vertical"
+        android:ellipsize="end"
+        android:maxLines="1"
+        android:textDirection="locale" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
index 80692f9..bf04a6f 100644
--- a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
@@ -28,50 +28,16 @@
         android:layout_height="wrap_content"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/preset_spinner" />
-
-    <Spinner
-        android:id="@+id/preset_spinner"
-        style="@style/BluetoothTileDialog.Device"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/hearing_devices_layout_margin"
-        android:minHeight="@dimen/hearing_devices_preset_spinner_height"
-        android:gravity="center_vertical"
-        android:background="@drawable/hearing_devices_preset_spinner_background"
-        android:popupBackground="@drawable/hearing_devices_preset_spinner_popup_background"
-        android:dropDownVerticalOffset="@dimen/hearing_devices_preset_spinner_height"
-        android:dropDownWidth="match_parent"
-        android:paddingStart="0dp"
-        android:paddingEnd="0dp"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/device_list"
-        app:layout_constraintBottom_toTopOf="@id/pair_new_device_button"
-        android:longClickable="false"
-        android:visibility="gone"/>
-
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/device_function_barrier"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:barrierAllowsGoneWidgets="false"
-        app:barrierDirection="bottom"
-        app:constraint_referenced_ids="device_list,preset_spinner" />
+        app:layout_constraintEnd_toEndOf="parent" />
 
     <Button
         android:id="@+id/pair_new_device_button"
         style="@style/BluetoothTileDialog.Device"
-        android:paddingEnd="0dp"
-        android:paddingStart="20dp"
-        android:background="@drawable/bluetooth_tile_dialog_bg_off"
-        android:layout_width="0dp"
-        android:layout_height="64dp"
-        android:contentDescription="@string/accessibility_hearing_device_pair_new_device"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/device_function_barrier"
+        app:layout_constraintTop_toBottomOf="@id/device_list"
+        android:layout_height="@dimen/bluetooth_dialog_device_height"
+        android:contentDescription="@string/accessibility_hearing_device_pair_new_device"
         android:drawableStart="@drawable/ic_add"
         android:drawablePadding="20dp"
         android:drawableTint="?android:attr/textColorPrimary"
@@ -81,26 +47,75 @@
         android:textDirection="locale"
         android:textAlignment="viewStart"
         android:maxLines="1"
-        android:ellipsize="end" />
-
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/device_barrier"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:barrierAllowsGoneWidgets="false"
-        app:barrierDirection="bottom"
-        app:constraint_referenced_ids="device_function_barrier, pair_new_device_button" />
+        android:ellipsize="end"
+        android:background="@drawable/bluetooth_tile_dialog_bg_off" />
 
     <LinearLayout
-        android:id="@+id/related_tools_container"
+        android:id="@+id/preset_layout"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
-        android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
-        android:layout_marginTop="@dimen/hearing_devices_layout_margin"
-        android:orientation="horizontal"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/device_barrier" />
+        app:layout_constraintTop_toBottomOf="@id/pair_new_device_button"
+        android:layout_marginTop="@dimen/hearing_devices_layout_margin"
+        android:orientation="vertical">
+        <TextView
+            android:id="@+id/preset_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
+            android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
+            android:paddingStart="@dimen/hearing_devices_small_title_padding_horizontal"
+            android:text="@string/hearing_devices_preset_label"
+            android:textAppearance="@style/TextAppearance.Dialog.Title"
+            android:textSize="14sp"
+            android:gravity="center_vertical"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+            android:textDirection="locale"/>
+        <Spinner
+            android:id="@+id/preset_spinner"
+            style="@style/BluetoothTileDialog.Device"
+            android:layout_height="@dimen/bluetooth_dialog_device_height"
+            android:layout_marginTop="4dp"
+            android:paddingStart="0dp"
+            android:paddingEnd="0dp"
+            android:background="@drawable/hearing_devices_spinner_background"
+            android:popupBackground="@drawable/hearing_devices_spinner_popup_background"
+            android:dropDownWidth="match_parent"
+            android:longClickable="false"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/tools_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/preset_layout"
+        android:layout_marginTop="@dimen/hearing_devices_layout_margin"
+        android:orientation="vertical">
+        <TextView
+            android:id="@+id/tools_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
+            android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
+            android:paddingStart="@dimen/hearing_devices_small_title_padding_horizontal"
+            android:text="@string/hearing_devices_tools_label"
+            android:textAppearance="@style/TextAppearance.Dialog.Title"
+            android:textSize="14sp"
+            android:gravity="center_vertical"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+            android:textDirection="locale"/>
+        <LinearLayout
+            android:id="@+id/tools_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
+            android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
+            android:layout_marginTop="4dp"
+            android:orientation="horizontal"/>
+    </LinearLayout>
+
 
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_tool_item.xml b/packages/SystemUI/res/layout/hearing_tool_item.xml
index f5baf2a..da9178b 100644
--- a/packages/SystemUI/res/layout/hearing_tool_item.xml
+++ b/packages/SystemUI/res/layout/hearing_tool_item.xml
@@ -46,7 +46,7 @@
         android:textAlignment="center"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/hearing_devices_layout_margin"
+        android:layout_marginTop="4dp"
         android:ellipsize="end"
         android:textSize="12sp"
         android:maxLines="3"
diff --git a/packages/SystemUI/res/layout/split_clock_view.xml b/packages/SystemUI/res/layout/split_clock_view.xml
deleted file mode 100644
index 8198f03..0000000
--- a/packages/SystemUI/res/layout/split_clock_view.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2014 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<!-- Extends LinearLayout -->
-<com.android.systemui.statusbar.policy.SplitClockView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    >
-    <TextClock
-        android:id="@+id/time_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:singleLine="true"
-        android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
-        android:textSize="@dimen/qs_time_collapsed_size"
-        android:textColor="?android:attr/textColorPrimary"
-        />
-    <TextClock
-        android:id="@+id/am_pm_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:singleLine="true"
-        android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
-        android:textSize="@dimen/qs_time_collapsed_size"
-        android:textColor="?android:attr/textColorPrimary"
-        android:importantForAccessibility="no"
-        />
-
-    <!-- Empty text view so we have the same height when expanded/collapsed-->
-    <TextView
-        android:id="@+id/empty_time_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        android:singleLine="true"
-        android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
-        android:textColor="?android:attr/textColorPrimary"
-        />
-</com.android.systemui.statusbar.policy.SplitClockView>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index e90f055f..f187ce6 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -13,50 +13,58 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:id="@+id/volume_dialog_container"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="right"
-    android:divider="@drawable/volume_dialog_floating_sliders_spacer"
-    android:orientation="horizontal"
-    android:showDividers="middle|end|beginning"
-    android:theme="@style/volume_dialog_theme">
+    android:id="@+id/volume_dialog_root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
 
     <LinearLayout
-        android:id="@+id/volume_dialog_floating_sliders_container"
+        android:id="@+id/volume_dialog_container"
         android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:divider="@drawable/volume_dialog_floating_sliders_spacer"
-        android:gravity="bottom"
-        android:orientation="horizontal"
-        android:paddingBottom="@dimen/volume_dialog_floating_sliders_bottom_padding"
-        android:showDividers="middle" />
-
-    <LinearLayout
-        android:id="@+id/volume_dialog"
-        android:layout_width="@dimen/volume_dialog_width"
         android:layout_height="wrap_content"
-        android:background="@drawable/volume_dialog_background"
-        android:divider="@drawable/volume_dialog_spacer"
-        android:paddingVertical="@dimen/volume_dialog_vertical_padding"
-        android:gravity="center_horizontal"
-        android:orientation="vertical"
-        android:showDividers="middle">
+        android:layout_gravity="center_vertical|end"
+        android:divider="@drawable/volume_dialog_floating_sliders_spacer"
+        android:orientation="horizontal"
+        android:showDividers="middle|end|beginning">
 
-        <include layout="@layout/volume_ringer_drawer" />
+        <LinearLayout
+            android:id="@+id/volume_dialog_floating_sliders_container"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:divider="@drawable/volume_dialog_floating_sliders_spacer"
+            android:gravity="bottom"
+            android:orientation="horizontal"
+            android:paddingBottom="@dimen/volume_dialog_floating_sliders_bottom_padding"
+            android:showDividers="middle" />
 
-        <include layout="@layout/volume_dialog_slider" />
+        <LinearLayout
+            android:id="@+id/volume_dialog"
+            android:layout_width="@dimen/volume_dialog_width"
+            android:layout_height="wrap_content"
+            android:background="@drawable/volume_dialog_background"
+            android:clipChildren="false"
+            android:clipToOutline="false"
+            android:clipToPadding="false"
+            android:divider="@drawable/volume_dialog_spacer"
+            android:gravity="center_horizontal"
+            android:orientation="vertical"
+            android:paddingVertical="@dimen/volume_dialog_vertical_padding"
+            android:showDividers="middle">
 
-        <ImageButton
-            android:id="@+id/volume_dialog_settings"
-            android:layout_width="@dimen/volume_dialog_button_size"
-            android:layout_height="@dimen/volume_dialog_button_size"
-            android:background="@drawable/ripple_drawable_20dp"
-            android:contentDescription="@string/accessibility_volume_settings"
-            android:soundEffectsEnabled="false"
-            android:src="@drawable/horizontal_ellipsis"
-            android:tint="?androidprv:attr/materialColorPrimary" />
+            <include layout="@layout/volume_ringer_drawer" />
+
+            <include layout="@layout/volume_dialog_slider" />
+
+            <ImageButton
+                android:id="@+id/volume_dialog_settings"
+                android:layout_width="@dimen/volume_dialog_button_size"
+                android:layout_height="@dimen/volume_dialog_button_size"
+                android:background="@drawable/ripple_drawable_20dp"
+                android:contentDescription="@string/accessibility_volume_settings"
+                android:soundEffectsEnabled="false"
+                android:src="@drawable/horizontal_ellipsis"
+                android:tint="?androidprv:attr/materialColorPrimary" />
+        </LinearLayout>
     </LinearLayout>
-</LinearLayout>
\ No newline at end of file
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 67eb5b0..7af0057 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1805,10 +1805,9 @@
 
     <!-- Hearing devices dialog related dimensions -->
     <dimen name="hearing_devices_layout_margin">12dp</dimen>
-    <dimen name="hearing_devices_preset_spinner_height">72dp</dimen>
-    <dimen name="hearing_devices_preset_spinner_text_padding_start">20dp</dimen>
-    <dimen name="hearing_devices_preset_spinner_text_padding_vertical">15dp</dimen>
-    <dimen name="hearing_devices_preset_spinner_arrow_size">24dp</dimen>
+    <dimen name="hearing_devices_small_title_padding_horizontal">16dp</dimen>
+    <dimen name="hearing_devices_preset_spinner_padding">22dp</dimen>
+    <dimen name="hearing_devices_preset_spinner_icon_size">24dp</dimen>
     <dimen name="hearing_devices_preset_spinner_background_radius">28dp</dimen>
     <dimen name="hearing_devices_tool_icon_size">28dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 53ab686..b45aadd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1002,6 +1002,10 @@
     <string name="hearing_devices_presets_error">Couldn\'t update preset</string>
     <!-- QuickSettings: Title for hearing aids presets. Preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=40]-->
     <string name="hearing_devices_preset_label">Preset</string>
+    <!-- QuickSettings: Content description for the icon that indicates the item is selected [CHAR LIMIT=NONE]-->
+    <string name="hearing_devices_spinner_item_selected">Selected</string>
+    <!-- QuickSettings: Title for related tools of hearing. [CHAR LIMIT=40]-->
+    <string name="hearing_devices_tools_label">Tools</string>
     <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40] [BACKUP_MESSAGE_ID=8916875614623730005]-->
     <string name="quick_settings_hearing_devices_live_caption_title">Live Caption</string>
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7d071cd..e14008a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -557,6 +557,16 @@
         <item name="android:showWhenLocked">true</item>
     </style>
 
+    <style name="Theme.SystemUI.Dialog.Volume">
+        <item name="android:backgroundDimEnabled">false</item>
+        <item name="android:showWhenLocked">true</item>
+        <item name="android:windowBackground">@color/transparent</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowIsFloating">false</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
     <style name="SystemUI.Material3.Slider.Volume">
         <item name="trackHeight">40dp</item>
         <item name="thumbHeight">52dp</item>
@@ -1471,28 +1481,14 @@
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
-    <style name="BluetoothTileDialog.Device.Active">
-        <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
-    </style>
-
-    <style name="BluetoothTileDialog.DeviceName">
-        <item name="android:textSize">14sp</item>
-        <item name="android:textAppearance">@style/TextAppearance.Dialog.Title</item>
+    <style name="TextAppearance.BluetoothTileDialog">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:textDirection">locale</item>
+        <item name="android:textAlignment">gravity</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
-    <style name="BluetoothTileDialog.DeviceSummary">
-        <item name="android:ellipsize">end</item>
-        <item name="android:maxLines">2</item>
-        <item name="android:textAppearance">@style/TextAppearance.Dialog.Body.Message</item>
-        <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
-    </style>
-
-    <style name="BluetoothTileDialog.DeviceName.Active">
-        <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
-    </style>
-
-    <style name="BluetoothTileDialog.DeviceSummary.Active">
+    <style name="TextAppearance.BluetoothTileDialog.Active">
         <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
     </style>
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 1342dd0..95830b5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -15,6 +15,8 @@
  */
 package com.android.keyguard;
 
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+
 import android.annotation.NonNull;
 import android.app.Presentation;
 import android.content.Context;
@@ -36,18 +38,24 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.views.NavigationBarView;
 import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.shade.data.repository.ShadePositionRepository;
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import dagger.Lazy;
 
+import kotlinx.coroutines.CoroutineScope;
+
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 @SysUISingleton
 public class KeyguardDisplayManager {
@@ -58,6 +66,7 @@
     private final DisplayManager mDisplayService;
     private final DisplayTracker mDisplayTracker;
     private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
+    private final Provider<ShadePositionRepository> mShadePositionRepositoryProvider;
     private final ConnectedDisplayKeyguardPresentation.Factory
             mConnectedDisplayKeyguardPresentationFactory;
     private final Context mContext;
@@ -102,9 +111,12 @@
             DeviceStateHelper deviceStateHelper,
             KeyguardStateController keyguardStateController,
             ConnectedDisplayKeyguardPresentation.Factory
-                    connectedDisplayKeyguardPresentationFactory) {
+                    connectedDisplayKeyguardPresentationFactory,
+            Provider<ShadePositionRepository> shadePositionRepositoryProvider,
+            @Application CoroutineScope appScope) {
         mContext = context;
         mNavigationBarControllerLazy = navigationBarControllerLazy;
+        mShadePositionRepositoryProvider = shadePositionRepositoryProvider;
         uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class));
         mDisplayService = mContext.getSystemService(DisplayManager.class);
         mDisplayTracker = displayTracker;
@@ -112,6 +124,17 @@
         mDeviceStateHelper = deviceStateHelper;
         mKeyguardStateController = keyguardStateController;
         mConnectedDisplayKeyguardPresentationFactory = connectedDisplayKeyguardPresentationFactory;
+        if (ShadeWindowGoesAround.isEnabled()) {
+            collectFlow(appScope, shadePositionRepositoryProvider.get().getDisplayId(),
+                    (id) -> onShadeWindowMovedToDisplayId(id));
+        }
+    }
+
+    private void onShadeWindowMovedToDisplayId(int shadeDisplayId) {
+        if (mShowing) {
+            hidePresentation(shadeDisplayId);
+            updateDisplays(/* showing= */ true);
+        }
     }
 
     private boolean isKeyguardShowable(Display display) {
@@ -119,9 +142,20 @@
             if (DEBUG) Log.i(TAG, "Cannot show Keyguard on null display");
             return false;
         }
-        if (display.getDisplayId() == mDisplayTracker.getDefaultDisplayId()) {
-            if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on the default display");
-            return false;
+        if (ShadeWindowGoesAround.isEnabled()) {
+            int shadeDisplayId = mShadePositionRepositoryProvider.get().getDisplayId().getValue();
+            if (display.getDisplayId() == shadeDisplayId) {
+                if (DEBUG) {
+                    Log.i(TAG,
+                            "Do not show KeyguardPresentation on the shade window display");
+                }
+                return false;
+            }
+        } else {
+            if (display.getDisplayId() == mDisplayTracker.getDefaultDisplayId()) {
+                if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on the default display");
+                return false;
+            }
         }
         display.getDisplayInfo(mTmpDisplayInfo);
         if ((mTmpDisplayInfo.flags & Display.FLAG_PRIVATE) != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 1978bb8..1f21af8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -35,10 +35,9 @@
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.View.Visibility;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
 import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -114,10 +113,9 @@
     private SystemUIDialog mDialog;
     private RecyclerView mDeviceList;
     private List<DeviceItem> mHearingDeviceItemList;
+    private View mPresetLayout;
     private Spinner mPresetSpinner;
-    private ArrayAdapter<String> mPresetInfoAdapter;
-    private Button mPairButton;
-    private LinearLayout mRelatedToolsContainer;
+    private HearingDevicesSpinnerAdapter mPresetInfoAdapter;
     private final HearingDevicesPresetsController.PresetCallback mPresetCallback =
             new HearingDevicesPresetsController.PresetCallback() {
                 @Override
@@ -245,7 +243,7 @@
                     mPresetsController.getAllPresetInfo();
             final int activePresetIndex = mPresetsController.getActivePresetIndex();
             refreshPresetInfoAdapter(presetInfos, activePresetIndex);
-            mPresetSpinner.setVisibility(
+            mPresetLayout.setVisibility(
                     (activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
                             && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
         });
@@ -291,14 +289,13 @@
         }
 
         mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW, mLaunchSourceId);
-        mPairButton = dialog.requireViewById(R.id.pair_new_device_button);
         mDeviceList = dialog.requireViewById(R.id.device_list);
+        mPresetLayout = dialog.requireViewById(R.id.preset_layout);
         mPresetSpinner = dialog.requireViewById(R.id.preset_spinner);
-        mRelatedToolsContainer = dialog.requireViewById(R.id.related_tools_container);
 
         setupDeviceListView(dialog);
         setupPresetSpinner(dialog);
-        setupPairNewDeviceButton(dialog, mShowPairNewDevice ? VISIBLE : GONE);
+        setupPairNewDeviceButton(dialog);
         if (com.android.systemui.Flags.hearingDevicesDialogRelatedTools()) {
             setupRelatedToolsView(dialog);
         }
@@ -353,11 +350,7 @@
                 mHearingDeviceItemList);
         mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice);
 
-        mPresetInfoAdapter = new ArrayAdapter<>(dialog.getContext(),
-                R.layout.hearing_devices_preset_spinner_selected,
-                R.id.hearing_devices_preset_option_text);
-        mPresetInfoAdapter.setDropDownViewResource(
-                R.layout.hearing_devices_preset_dropdown_item);
+        mPresetInfoAdapter = new HearingDevicesSpinnerAdapter(dialog.getContext());
         mPresetSpinner.setAdapter(mPresetInfoAdapter);
 
         // disable redundant Touch & Hold accessibility action for Switch Access
@@ -378,6 +371,7 @@
         mPresetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
             @Override
             public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+                mPresetInfoAdapter.setSelected(position);
                 mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PRESET_SELECT,
                         mLaunchSourceId);
                 mPresetsController.selectPreset(
@@ -389,14 +383,17 @@
                 // Do nothing
             }
         });
-        mPresetSpinner.setVisibility(
+        mPresetLayout.setVisibility(
                 (activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
                         && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
     }
 
-    private void setupPairNewDeviceButton(SystemUIDialog dialog, @Visibility int visibility) {
-        if (visibility == VISIBLE) {
-            mPairButton.setOnClickListener(v -> {
+    private void setupPairNewDeviceButton(SystemUIDialog dialog) {
+        final Button pairButton = dialog.requireViewById(R.id.pair_new_device_button);
+
+        pairButton.setVisibility(mShowPairNewDevice ? VISIBLE : GONE);
+        if (mShowPairNewDevice) {
+            pairButton.setOnClickListener(v -> {
                 mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR, mLaunchSourceId);
                 dismissDialogIfExists();
                 final Intent intent = new Intent(Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS);
@@ -404,12 +401,11 @@
                 mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0,
                         mDialogTransitionAnimator.createActivityTransitionController(dialog));
             });
-        } else {
-            mPairButton.setVisibility(GONE);
         }
     }
 
     private void setupRelatedToolsView(SystemUIDialog dialog) {
+
         final Context context = dialog.getContext();
         final List<ToolItem> toolItemList = new ArrayList<>();
         final String[] toolNameArray;
@@ -430,15 +426,20 @@
         } catch (Resources.NotFoundException e) {
             Log.i(TAG, "No hearing devices related tool config resource");
         }
+
+        final View toolsLayout = dialog.requireViewById(R.id.tools_layout);
+        toolsLayout.setVisibility(toolItemList.isEmpty() ? GONE : VISIBLE);
+
+        final LinearLayout toolsContainer = dialog.requireViewById(R.id.tools_container);
         for (int i = 0; i < toolItemList.size(); i++) {
-            View view = createHearingToolView(context, toolItemList.get(i));
-            mRelatedToolsContainer.addView(view);
+            View view = createHearingToolView(context, toolItemList.get(i), toolsContainer);
+            toolsContainer.addView(view);
             if (i != toolItemList.size() - 1) {
                 final int spaceSize = context.getResources().getDimensionPixelSize(
                         R.dimen.hearing_devices_layout_margin);
                 Space space = new Space(context);
                 space.setLayoutParams(new LinearLayout.LayoutParams(spaceSize, 0));
-                mRelatedToolsContainer.addView(space);
+                toolsContainer.addView(space);
             }
         }
     }
@@ -453,6 +454,7 @@
             for (int position = 0; position < size; position++) {
                 if (presetInfos.get(position).getIndex() == activePresetIndex) {
                     mPresetSpinner.setSelection(position, /* animate= */ false);
+                    mPresetInfoAdapter.setSelected(position);
                 }
             }
         }
@@ -493,9 +495,9 @@
     }
 
     @NonNull
-    private View createHearingToolView(Context context, ToolItem item) {
-        View view = LayoutInflater.from(context).inflate(R.layout.hearing_tool_item,
-                mRelatedToolsContainer, false);
+    private View createHearingToolView(Context context, ToolItem item, ViewGroup container) {
+        View view = LayoutInflater.from(context).inflate(R.layout.hearing_tool_item, container,
+                false);
         ImageView icon = view.requireViewById(R.id.tool_icon);
         TextView text = view.requireViewById(R.id.tool_name);
         view.setContentDescription(item.getToolName());
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
index 9367cb5..e47e4b2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
@@ -138,19 +138,18 @@
 
             Pair<Drawable, String> iconPair = item.getIconWithDescription();
             if (iconPair != null) {
-                Drawable drawable = iconPair.getFirst().mutate();
-                drawable.setTint(tintColor);
+                Drawable drawable = iconPair.getFirst();
                 mIconView.setImageDrawable(drawable);
                 mIconView.setContentDescription(iconPair.getSecond());
             }
 
             mNameView.setTextAppearance(
-                    item.isActive() ? R.style.BluetoothTileDialog_DeviceName_Active
-                            : R.style.BluetoothTileDialog_DeviceName);
+                    item.isActive() ? R.style.TextAppearance_BluetoothTileDialog_Active
+                            : R.style.TextAppearance_BluetoothTileDialog);
             mNameView.setText(item.getDeviceName());
             mSummaryView.setTextAppearance(
-                    item.isActive() ? R.style.BluetoothTileDialog_DeviceSummary_Active
-                            : R.style.BluetoothTileDialog_DeviceSummary);
+                    item.isActive() ? R.style.TextAppearance_BluetoothTileDialog_Active
+                            : R.style.TextAppearance_BluetoothTileDialog);
             mSummaryView.setText(item.getConnectionSummary());
 
             mGearIcon.getDrawable().mutate().setTint(tintColor);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesSpinnerAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesSpinnerAdapter.java
new file mode 100644
index 0000000..28d742c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesSpinnerAdapter.java
@@ -0,0 +1,85 @@
+/*
+ * 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.accessibility.hearingaid;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.res.R;
+
+/**
+ * An ArrayAdapter which was used by Spinner in hearing devices dialog.
+ */
+public class HearingDevicesSpinnerAdapter extends ArrayAdapter<String> {
+
+    private final Context mContext;
+    private int mSelectedPosition;
+
+    public HearingDevicesSpinnerAdapter(@NonNull Context context) {
+        super(context, R.layout.hearing_devices_spinner_view,
+                R.id.hearing_devices_spinner_text);
+        setDropDownViewResource(R.layout.hearing_devices_spinner_dropdown_view);
+        mContext = context;
+    }
+
+    @Override
+    public View getDropDownView(int position, @Nullable View convertView,
+            @NonNull ViewGroup parent) {
+
+        View view = super.getDropDownView(position, convertView, parent);
+
+        final boolean isSelected = position == mSelectedPosition;
+        view.setBackgroundResource(isSelected
+                ? R.drawable.hearing_devices_spinner_selected_background
+                : R.drawable.bluetooth_tile_dialog_bg_off);
+
+        View checkIcon = view.findViewById(R.id.hearing_devices_spinner_check_icon);
+        if (checkIcon != null) {
+            checkIcon.setVisibility(isSelected ? VISIBLE : GONE);
+        }
+
+        TextView text = view.findViewById(R.id.hearing_devices_spinner_text);
+        if (text != null) {
+            int tintColor = Utils.getColorAttr(mContext,
+                    isSelected ? com.android.internal.R.attr.materialColorOnPrimaryContainer
+                            : com.android.internal.R.attr.materialColorOnSurface).getDefaultColor();
+            text.setTextColor(tintColor);
+        }
+        return view;
+    }
+
+    /**
+     * Sets the selected position into this adapter. The selected item will have different UI in
+     * the dropdown view.
+     *
+     * @param position the selected position
+     */
+    public void setSelected(int position) {
+        mSelectedPosition = position;
+        notifyDataSetChanged();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index a9c5c69..d7a0fc9 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -100,7 +100,7 @@
             initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
             cachedContentHeight: Int,
             dialogCallback: BluetoothTileDialogCallback,
-            dimissListener: Runnable
+            dimissListener: Runnable,
         ): BluetoothTileDialogDelegate
     }
 
@@ -135,7 +135,7 @@
                 object : AccessibilityDelegate() {
                     override fun onInitializeAccessibilityNodeInfo(
                         host: View,
-                        info: AccessibilityNodeInfo
+                        info: AccessibilityNodeInfo,
                     ) {
                         super.onInitializeAccessibilityNodeInfo(host, info)
                         info.addAction(
@@ -144,7 +144,7 @@
                                 context.getString(
                                     R.string
                                         .quick_settings_bluetooth_audio_sharing_button_accessibility
-                                )
+                                ),
                             )
                         )
                     }
@@ -180,7 +180,7 @@
         dialog: SystemUIDialog,
         deviceItem: List<DeviceItem>,
         showSeeAll: Boolean,
-        showPairNewDevice: Boolean
+        showPairNewDevice: Boolean,
     ) {
         withContext(mainDispatcher) {
             val start = systemClock.elapsedRealtime()
@@ -207,7 +207,7 @@
     internal fun onBluetoothStateUpdated(
         dialog: SystemUIDialog,
         isEnabled: Boolean,
-        uiProperties: BluetoothTileDialogViewModel.UiProperties
+        uiProperties: BluetoothTileDialogViewModel.UiProperties,
     ) {
         getToggleView(dialog).apply {
             isChecked = isEnabled
@@ -221,7 +221,7 @@
     internal fun onBluetoothAutoOnUpdated(
         dialog: SystemUIDialog,
         isEnabled: Boolean,
-        @StringRes infoResId: Int
+        @StringRes infoResId: Int,
     ) {
         getAutoOnToggle(dialog).isChecked = isEnabled
         getAutoOnToggleInfoTextView(dialog).text = dialog.context.getString(infoResId)
@@ -231,7 +231,7 @@
         dialog: SystemUIDialog,
         visibility: Int,
         label: String?,
-        isActive: Boolean
+        isActive: Boolean,
     ) {
         getAudioSharingButtonView(dialog).apply {
             this.visibility = visibility
@@ -339,14 +339,14 @@
             object : DiffUtil.ItemCallback<DeviceItem>() {
                 override fun areItemsTheSame(
                     deviceItem1: DeviceItem,
-                    deviceItem2: DeviceItem
+                    deviceItem2: DeviceItem,
                 ): Boolean {
                     return deviceItem1.cachedBluetoothDevice == deviceItem2.cachedBluetoothDevice
                 }
 
                 override fun areContentsTheSame(
                     deviceItem1: DeviceItem,
-                    deviceItem2: DeviceItem
+                    deviceItem2: DeviceItem,
                 ): Boolean {
                     return deviceItem1.type == deviceItem2.type &&
                         deviceItem1.cachedBluetoothDevice == deviceItem2.cachedBluetoothDevice &&
@@ -394,7 +394,7 @@
 
             internal fun bind(
                 item: DeviceItem,
-                deviceItemOnClickCallback: BluetoothTileDialogCallback
+                deviceItemOnClickCallback: BluetoothTileDialogCallback,
             ) {
                 container.apply {
                     isEnabled = item.isEnabled
@@ -409,14 +409,14 @@
                         com.android.settingslib.Utils.getColorAttr(
                                 context,
                                 if (item.isActive) InternalR.attr.materialColorOnPrimaryContainer
-                                else InternalR.attr.materialColorOnSurface
+                                else InternalR.attr.materialColorOnSurface,
                             )
                             .defaultColor
 
                     // update icons
                     iconView.apply {
                         item.iconWithDescription?.let {
-                            setImageDrawable(it.first.apply { mutate()?.setTint(tintColor) })
+                            setImageDrawable(it.first)
                             contentDescription = it.second
                         }
                     }
@@ -427,25 +427,25 @@
 
                     // update text styles
                     nameView.setTextAppearance(
-                        if (item.isActive) R.style.BluetoothTileDialog_DeviceName_Active
-                        else R.style.BluetoothTileDialog_DeviceName
+                        if (item.isActive) R.style.TextAppearance_BluetoothTileDialog_Active
+                        else R.style.TextAppearance_BluetoothTileDialog
                     )
                     summaryView.setTextAppearance(
-                        if (item.isActive) R.style.BluetoothTileDialog_DeviceSummary_Active
-                        else R.style.BluetoothTileDialog_DeviceSummary
+                        if (item.isActive) R.style.TextAppearance_BluetoothTileDialog_Active
+                        else R.style.TextAppearance_BluetoothTileDialog
                     )
 
                     accessibilityDelegate =
                         object : AccessibilityDelegate() {
                             override fun onInitializeAccessibilityNodeInfo(
                                 host: View,
-                                info: AccessibilityNodeInfo
+                                info: AccessibilityNodeInfo,
                             ) {
                                 super.onInitializeAccessibilityNodeInfo(host, info)
                                 info.addAction(
                                     AccessibilityAction(
                                         AccessibilityAction.ACTION_CLICK.id,
-                                        item.actionAccessibilityLabel
+                                        item.actionAccessibilityLabel,
                                     )
                                 )
                             }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index 2921373..92f0580 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -57,7 +57,6 @@
     companion object {
         @JvmStatic
         fun createDeviceItem(
-            context: Context,
             cachedDevice: CachedBluetoothDevice,
             type: DeviceItemType,
             connectionSummary: String,
@@ -71,9 +70,7 @@
                 deviceName = cachedDevice.name,
                 connectionSummary = connectionSummary,
                 iconWithDescription =
-                    BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let {
-                        Pair(it.first, it.second)
-                    },
+                    cachedDevice.drawableWithDescription.let { Pair(it.first, it.second) },
                 background = background,
                 isEnabled = !cachedDevice.isBusy,
                 actionAccessibilityLabel = actionAccessibilityLabel,
@@ -96,7 +93,6 @@
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
         return createDeviceItem(
-            context,
             cachedDevice,
             DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
             cachedDevice.connectionSummary ?: "",
@@ -122,7 +118,6 @@
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
         return createDeviceItem(
-            context,
             cachedDevice,
             DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
             cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
@@ -153,7 +148,6 @@
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
         return createDeviceItem(
-            context,
             cachedDevice,
             DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
             context.getString(
@@ -191,7 +185,6 @@
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
         return createDeviceItem(
-            context,
             cachedDevice,
             DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
             cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
@@ -232,7 +225,6 @@
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
         return createDeviceItem(
-            context,
             cachedDevice,
             DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
             cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
@@ -262,7 +254,6 @@
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
         return createDeviceItem(
-            context,
             cachedDevice,
             DeviceItemType.SAVED_BLUETOOTH_DEVICE,
             cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
index 7cfad60..6423f8f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
@@ -30,6 +30,7 @@
     @Named(LOGGABLE_PREFIXES) private val loggablePrefixes: List<String>,
     private val statsLogProxy: StatsLogProxy,
 ) {
+
     /** Logs an add widget event for metrics. No-op if widget is not loggable. */
     fun logAddWidget(componentName: String, rank: Int?) {
         if (!componentName.isLoggable()) {
@@ -69,11 +70,21 @@
         )
     }
 
+    fun logResizeWidget(componentName: String, rank: Int, spanY: Int = 0) {
+        if (!componentName.isLoggable()) {
+            return
+        }
+
+        statsLogProxy.writeCommunalHubWidgetEventReported(
+            SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__RESIZE,
+            componentName,
+            rank = rank,
+            spanY = spanY,
+        )
+    }
+
     /** Logs loggable widgets and the total widget count as a [StatsEvent]. */
-    fun logWidgetsSnapshot(
-        statsEvents: MutableList<StatsEvent>,
-        componentNames: List<String>,
-    ) {
+    fun logWidgetsSnapshot(statsEvents: MutableList<StatsEvent>, componentNames: List<String>) {
         val loggableComponentNames = componentNames.filter { it.isLoggable() }.toTypedArray()
         statsEvents.add(
             statsLogProxy.buildCommunalHubSnapshotStatsEvent(
@@ -95,6 +106,7 @@
             action: Int,
             componentName: String,
             rank: Int,
+            spanY: Int = 0,
         )
 
         /** Builds a [SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT] stats event. */
@@ -112,12 +124,14 @@
         action: Int,
         componentName: String,
         rank: Int,
+        spanY: Int,
     ) {
         SysUiStatsLog.write(
             SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED,
             action,
             componentName,
             rank,
+            spanY,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index e25ea4c..4e0e112 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -178,7 +178,13 @@
      *   alongside the resize, in case resizing also requires re-ordering. This ensures the
      *   re-ordering is done in the same database transaction as the resize.
      */
-    open fun onResizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {}
+    open fun onResizeWidget(
+        appWidgetId: Int,
+        spanY: Int,
+        widgetIdToRankMap: Map<Int, Int>,
+        componentName: ComponentName,
+        rank: Int,
+    ) {}
 
     /** Called as the UI requests opening the widget editor with an optional preselected widget. */
     open fun onOpenWidgetEditor(shouldOpenWidgetPickerOnStart: Boolean = false) {}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 6508e4b5..ccff230 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -141,8 +141,19 @@
     override fun onReorderWidgets(widgetIdToRankMap: Map<Int, Int>) =
         communalInteractor.updateWidgetOrder(widgetIdToRankMap)
 
-    override fun onResizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {
+    override fun onResizeWidget(
+        appWidgetId: Int,
+        spanY: Int,
+        widgetIdToRankMap: Map<Int, Int>,
+        componentName: ComponentName,
+        rank: Int,
+    ) {
         communalInteractor.resizeWidget(appWidgetId, spanY, widgetIdToRankMap)
+        metricsLogger.logResizeWidget(
+            componentName = componentName.flattenToString(),
+            rank = rank,
+            spanY = spanY,
+        )
     }
 
     override fun onReorderWidgetStart() {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 4447dff..b7d3c92 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -100,7 +100,7 @@
 import com.android.systemui.qs.footer.dagger.FooterActionsModule;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recordissue.RecordIssueModule;
-import com.android.systemui.retail.dagger.RetailModeModule;
+import com.android.systemui.retail.RetailModeModule;
 import com.android.systemui.scene.shared.model.SceneContainerConfig;
 import com.android.systemui.scene.shared.model.SceneDataSource;
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator;
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
index 4a369e7..7758dcc 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
@@ -17,12 +17,14 @@
 package com.android.systemui.inputdevice.tutorial.domain.interactor
 
 import android.os.SystemProperties
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
 import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
 import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD
 import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD
 import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
+import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor.Companion.LAUNCH_DELAY
 import com.android.systemui.keyboard.data.repository.KeyboardRepository
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -35,6 +37,7 @@
 import kotlin.time.toKotlinDuration
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flow
@@ -96,6 +99,9 @@
     private suspend fun waitForDeviceConnection(deviceType: DeviceType) =
         isAnyDeviceConnected[deviceType]!!.filter { it }.first()
 
+    // Only for testing notifications. This should behave independently from scheduling
+    @VisibleForTesting val commandTutorials = MutableStateFlow(TutorialType.NONE)
+
     // Merging two flows ensures that tutorial is launched consecutively to avoid race condition
     val tutorials: Flow<TutorialType> =
         merge(touchpadScheduleFlow, keyboardScheduleFlow).map {
@@ -146,6 +152,15 @@
                         pw.println("Touchpad connect time = ${repo.firstConnectionTime(TOUCHPAD)}")
                         pw.println("         launch time = ${repo.launchTime(TOUCHPAD)}")
                     }
+                "notify" -> {
+                    if (args.size != 2) help(pw)
+                    when (args[1]) {
+                        "keyboard" -> commandTutorials.value = TutorialType.KEYBOARD
+                        "touchpad" -> commandTutorials.value = TutorialType.TOUCHPAD
+                        "both" -> commandTutorials.value = TutorialType.BOTH
+                        else -> help(pw)
+                    }
+                }
                 else -> help(pw)
             }
         }
@@ -155,6 +170,7 @@
             pw.println("Available commands:")
             pw.println("  clear")
             pw.println("  info")
+            pw.println("  notify [keyboard|touchpad|both]")
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
index 3b26f2f..9dae649 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
@@ -24,6 +24,7 @@
 import android.content.Intent
 import android.os.Bundle
 import androidx.core.app.NotificationCompat
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -41,7 +42,7 @@
 import com.android.systemui.settings.UserTracker
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.merge
 
 /** When the scheduler is due, show a notification to launch tutorial */
 @SysUISingleton
@@ -56,7 +57,11 @@
 ) {
     fun start() {
         backgroundScope.launch {
-            tutorialSchedulerInteractor.tutorials.collect { showNotification(it) }
+            merge(
+                    tutorialSchedulerInteractor.tutorials,
+                    tutorialSchedulerInteractor.commandTutorials,
+                )
+                .collect { showNotification(it) }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
index fa49415..fee08b3 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
@@ -27,6 +27,7 @@
 import androidx.lifecycle.Lifecycle.State.STARTED
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.lifecycleScope
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.compose.theme.PlatformTheme
 import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
 import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.TutorialContext
@@ -40,7 +41,6 @@
 import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.HOME_GESTURE
 import java.util.Optional
 import javax.inject.Inject
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Activity for out of the box experience for keyboard and touchpad. Note that it's possible that
@@ -90,6 +90,7 @@
         setContent {
             PlatformTheme { KeyboardTouchpadTutorialContainer(vm, touchpadTutorialScreensProvider) }
         }
+        // TODO(b/376692701): Update launchTime when the activity is launched by Companion App
         if (savedInstanceState == null) {
             metricsLogger.logPeripheralTutorialLaunched(
                 intent.getStringExtra(INTENT_TUTORIAL_ENTRY_POINT_KEY),
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt
new file mode 100644
index 0000000..8c393e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.keyboard.shortcut.data.model
+
+import android.graphics.drawable.Icon
+import android.hardware.input.InputGestureData
+import android.view.KeyboardShortcutGroup
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesUtils
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+
+/**
+ * Internal Keyboard Shortcut models to use with [ShortcutCategoriesUtils.fetchShortcutCategory]
+ * when converting API models to Shortcut Helper Model [ShortcutCategory]. These Internal Models
+ * bridge the Gap between [InputGestureData] from custom shortcuts API and [KeyboardShortcutGroup]
+ * from default shortcuts API allowing us to have a single Utility Class that converts API models to
+ * Shortcut Helper models
+ *
+ * @param label Equivalent to shortcut helper's subcategory label
+ * @param items Keyboard Shortcuts received from API
+ * @param packageName package name of current app shortcut if available.
+ */
+data class InternalKeyboardShortcutGroup(
+    val label: String,
+    val items: List<InternalKeyboardShortcutInfo>,
+    val packageName: String? = null,
+)
+
+/**
+ * @param label Shortcut label
+ * @param keycode Key to trigger shortcut
+ * @param modifiers Mask of shortcut modifiers
+ * @param baseCharacter Key to trigger shortcut if is a character
+ * @param icon Shortcut icon if available - often used for app launch shortcuts
+ * @param isCustomShortcut If Shortcut is user customized or system defined.
+ */
+data class InternalKeyboardShortcutInfo(
+    val label: String,
+    val keycode: Int,
+    val modifiers: Int,
+    val baseCharacter: Char = Char.MIN_VALUE,
+    val icon: Icon? = null,
+    val isCustomShortcut: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
new file mode 100644
index 0000000..7f8fbb5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.shortcut.data.repository
+
+import android.content.Context
+import android.content.Context.INPUT_SERVICE
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputGestureData.KeyTrigger
+import android.hardware.input.InputManager
+import android.hardware.input.InputSettings
+import android.hardware.input.KeyGestureEvent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class CustomShortcutCategoriesRepository
+@Inject
+constructor(
+    stateRepository: ShortcutHelperStateRepository,
+    private val userTracker: UserTracker,
+    @Background private val backgroundScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val shortcutCategoriesUtils: ShortcutCategoriesUtils,
+) : ShortcutCategoriesRepository {
+
+    private val userContext: Context
+        get() = userTracker.createCurrentUserContext(userTracker.userContext)
+
+    // Input manager created with user context to provide correct user id when requesting custom
+    // shortcut
+    private val inputManager: InputManager
+        get() = userContext.getSystemService(INPUT_SERVICE) as InputManager
+
+    private val activeInputDevice =
+        stateRepository.state.map {
+            if (it is Active) {
+                withContext(backgroundDispatcher) { inputManager.getInputDevice(it.deviceId) }
+            } else {
+                null
+            }
+        }
+
+    override val categories: Flow<List<ShortcutCategory>> =
+        activeInputDevice
+            .map { inputDevice ->
+                if (inputDevice == null) {
+                    emptyList()
+                } else {
+                    val customInputGesturesForUser: List<InputGestureData> =
+                        if (InputSettings.isCustomizableInputGesturesFeatureFlagEnabled()) {
+                            inputManager.getCustomInputGestures(/* filter= */ null)
+                        } else emptyList()
+                    val sources = toInternalGroupSources(customInputGesturesForUser)
+                    val supportedKeyCodes =
+                        shortcutCategoriesUtils.fetchSupportedKeyCodes(
+                            inputDevice.id,
+                            sources.map { it.groups },
+                        )
+
+                    val result =
+                        sources.mapNotNull { source ->
+                            shortcutCategoriesUtils.fetchShortcutCategory(
+                                type = source.type,
+                                groups = source.groups,
+                                inputDevice = inputDevice,
+                                supportedKeyCodes = supportedKeyCodes,
+                            )
+                        }
+                    result
+                }
+            }
+            .stateIn(
+                scope = backgroundScope,
+                initialValue = emptyList(),
+                started = SharingStarted.Lazily,
+            )
+
+    private fun toInternalGroupSources(
+        inputGestures: List<InputGestureData>
+    ): List<InternalGroupsSource> {
+        val ungroupedInternalGroupSources =
+            inputGestures.mapNotNull { gestureData ->
+                val keyTrigger = gestureData.trigger as KeyTrigger
+                val keyGestureType = gestureData.action.keyGestureType()
+                fetchGroupLabelByGestureType(keyGestureType)?.let { groupLabel ->
+                    toInternalKeyboardShortcutInfo(keyGestureType, keyTrigger)?.let {
+                        internalKeyboardShortcutInfo ->
+                        val group =
+                            InternalKeyboardShortcutGroup(
+                                label = groupLabel,
+                                items = listOf(internalKeyboardShortcutInfo),
+                            )
+
+                        fetchShortcutCategoryTypeByGestureType(keyGestureType)?.let {
+                            InternalGroupsSource(groups = listOf(group), type = it)
+                        }
+                    }
+                }
+            }
+
+        return ungroupedInternalGroupSources
+    }
+
+    private fun toInternalKeyboardShortcutInfo(
+        keyGestureType: Int,
+        keyTrigger: KeyTrigger,
+    ): InternalKeyboardShortcutInfo? {
+        fetchShortcutInfoLabelByGestureType(keyGestureType)?.let {
+            return InternalKeyboardShortcutInfo(
+                label = it,
+                keycode = keyTrigger.keycode,
+                modifiers = keyTrigger.modifierState,
+                isCustomShortcut = true,
+            )
+        }
+        return null
+    }
+
+    private fun fetchGroupLabelByGestureType(
+        @KeyGestureEvent.KeyGestureType keyGestureType: Int
+    ): String? {
+        return InputGestures.gestureToInternalKeyboardShortcutGroupLabelMap.getOrDefault(
+            keyGestureType,
+            null,
+        )
+    }
+
+    private fun fetchShortcutInfoLabelByGestureType(
+        @KeyGestureEvent.KeyGestureType keyGestureType: Int
+    ): String? {
+        return InputGestures.gestureToInternalKeyboardShortcutInfoLabelMap.getOrDefault(
+            keyGestureType,
+            null,
+        )
+    }
+
+    private fun fetchShortcutCategoryTypeByGestureType(
+        @KeyGestureEvent.KeyGestureType keyGestureType: Int
+    ): ShortcutCategoryType? {
+        return InputGestures.gestureToShortcutCategoryTypeMap.getOrDefault(keyGestureType, null)
+    }
+
+    private data class InternalGroupsSource(
+        val groups: List<InternalKeyboardShortcutGroup>,
+        val type: ShortcutCategoryType,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt
new file mode 100644
index 0000000..5bb7cdd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.keyboard.shortcut.data.repository
+
+import android.hardware.input.InputManager
+import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo
+import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
+import com.android.systemui.keyboard.shortcut.qualifiers.AppCategoriesShortcuts
+import com.android.systemui.keyboard.shortcut.qualifiers.CurrentAppShortcuts
+import com.android.systemui.keyboard.shortcut.qualifiers.InputShortcuts
+import com.android.systemui.keyboard.shortcut.qualifiers.MultitaskingShortcuts
+import com.android.systemui.keyboard.shortcut.qualifiers.SystemShortcuts
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class DefaultShortcutCategoriesRepository
+@Inject
+constructor(
+    @Background private val backgroundScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    @SystemShortcuts private val systemShortcutsSource: KeyboardShortcutGroupsSource,
+    @MultitaskingShortcuts private val multitaskingShortcutsSource: KeyboardShortcutGroupsSource,
+    @AppCategoriesShortcuts private val appCategoriesShortcutsSource: KeyboardShortcutGroupsSource,
+    @InputShortcuts private val inputShortcutsSource: KeyboardShortcutGroupsSource,
+    @CurrentAppShortcuts private val currentAppShortcutsSource: KeyboardShortcutGroupsSource,
+    private val inputManager: InputManager,
+    stateRepository: ShortcutHelperStateRepository,
+    shortcutCategoriesUtils: ShortcutCategoriesUtils,
+) : ShortcutCategoriesRepository {
+
+    private val sources =
+        listOf(
+            InternalGroupsSource(source = systemShortcutsSource, typeProvider = { System }),
+            InternalGroupsSource(
+                source = multitaskingShortcutsSource,
+                typeProvider = { MultiTasking },
+            ),
+            InternalGroupsSource(
+                source = appCategoriesShortcutsSource,
+                typeProvider = { AppCategories },
+            ),
+            InternalGroupsSource(
+                source = inputShortcutsSource,
+                typeProvider = { InputMethodEditor },
+            ),
+            InternalGroupsSource(
+                source = currentAppShortcutsSource,
+                typeProvider = { groups -> getCurrentAppShortcutCategoryType(groups) },
+            ),
+        )
+
+    private val activeInputDevice =
+        stateRepository.state.map {
+            if (it is Active) {
+                withContext(backgroundDispatcher) { inputManager.getInputDevice(it.deviceId) }
+            } else {
+                null
+            }
+        }
+
+    override val categories: Flow<List<ShortcutCategory>> =
+        activeInputDevice
+            .map { inputDevice ->
+                if (inputDevice == null) {
+                    return@map emptyList()
+                }
+                val groupsFromAllSources =
+                    sources.map {
+                        toInternalKeyboardShortcutGroups(it.source.shortcutGroups(inputDevice.id))
+                    }
+                val supportedKeyCodes =
+                    shortcutCategoriesUtils.fetchSupportedKeyCodes(
+                        inputDevice.id,
+                        groupsFromAllSources,
+                    )
+                return@map sources.mapIndexedNotNull { index, internalGroupsSource ->
+                    shortcutCategoriesUtils.fetchShortcutCategory(
+                        internalGroupsSource.typeProvider(groupsFromAllSources[index]),
+                        groupsFromAllSources[index],
+                        inputDevice,
+                        supportedKeyCodes,
+                    )
+                }
+            }
+            .stateIn(
+                scope = backgroundScope,
+                started = SharingStarted.Lazily,
+                initialValue = emptyList(),
+            )
+
+    private fun toInternalKeyboardShortcutGroups(
+        keyboardShortcutGroups: List<KeyboardShortcutGroup>
+    ): List<InternalKeyboardShortcutGroup> {
+        return keyboardShortcutGroups.map { group ->
+            InternalKeyboardShortcutGroup(
+                label = group.label.toString(),
+                items = group.items.map { toInternalKeyboardShortcutInfo(it) },
+                packageName = group.packageName?.toString(),
+            )
+        }
+    }
+
+    private fun toInternalKeyboardShortcutInfo(
+        keyboardShortcutInfo: KeyboardShortcutInfo
+    ): InternalKeyboardShortcutInfo {
+        return InternalKeyboardShortcutInfo(
+            label = keyboardShortcutInfo.label!!.toString(),
+            keycode = keyboardShortcutInfo.keycode,
+            modifiers = keyboardShortcutInfo.modifiers,
+            baseCharacter = keyboardShortcutInfo.baseCharacter,
+            icon = keyboardShortcutInfo.icon,
+        )
+    }
+
+    private fun getCurrentAppShortcutCategoryType(
+        shortcutGroups: List<InternalKeyboardShortcutGroup>
+    ): ShortcutCategoryType? {
+        val packageName = shortcutGroups.firstOrNull()?.packageName ?: return null
+        return CurrentApp(packageName)
+    }
+
+    private class InternalGroupsSource(
+        val source: KeyboardShortcutGroupsSource,
+        val typeProvider: (groups: List<InternalKeyboardShortcutGroup>) -> ShortcutCategoryType?,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt
new file mode 100644
index 0000000..28134db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.keyboard.shortcut.data.repository
+
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_BACK
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
+
+object InputGestures {
+    val gestureToShortcutCategoryTypeMap =
+        mapOf(
+            // System Category
+            KEY_GESTURE_TYPE_HOME to System,
+            KEY_GESTURE_TYPE_RECENT_APPS to System,
+            KEY_GESTURE_TYPE_BACK to System,
+            KEY_GESTURE_TYPE_TAKE_SCREENSHOT to System,
+            KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER to System,
+            KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL to System,
+            KEY_GESTURE_TYPE_LOCK_SCREEN to System,
+            KEY_GESTURE_TYPE_OPEN_NOTES to System,
+            KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS to System,
+            KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to System,
+            KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to System,
+            KEY_GESTURE_TYPE_ALL_APPS to System,
+
+            // Multitasking Category
+            KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to MultiTasking,
+            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to MultiTasking,
+            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to MultiTasking,
+            KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION to MultiTasking,
+            KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT to MultiTasking,
+            KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to MultiTasking,
+
+            // App Category
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to AppCategories,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to AppCategories,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to AppCategories,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to AppCategories,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to AppCategories,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to AppCategories,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to AppCategories,
+        )
+
+    // TODO move all string to to resources use the same resources as the original shortcuts
+    // - that way when the strings are translated there are no discrepancies
+    val gestureToInternalKeyboardShortcutGroupLabelMap =
+        mapOf(
+            // System Category
+            KEY_GESTURE_TYPE_HOME to "System controls",
+            KEY_GESTURE_TYPE_RECENT_APPS to "System controls",
+            KEY_GESTURE_TYPE_BACK to "System controls",
+            KEY_GESTURE_TYPE_TAKE_SCREENSHOT to "System controls",
+            KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER to "System controls",
+            KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL to "System controls",
+            KEY_GESTURE_TYPE_LOCK_SCREEN to "System controls",
+            KEY_GESTURE_TYPE_ALL_APPS to "System controls",
+            KEY_GESTURE_TYPE_OPEN_NOTES to "System apps",
+            KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS to "System apps",
+            KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to "System apps",
+            KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to "System apps",
+
+            // Multitasking Category
+            KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to "Recent apps",
+            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to "Split screen",
+            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to "Split screen",
+            KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION to "Split screen",
+            KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT to "Split screen",
+            KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to "Split screen",
+
+            // App Category
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to "Applications",
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to "Applications",
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to "Applications",
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to "Applications",
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to "Applications",
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to "Applications",
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to "Applications",
+        )
+
+    val gestureToInternalKeyboardShortcutInfoLabelMap =
+        mapOf(
+            // System Category
+            KEY_GESTURE_TYPE_HOME to "Go to home screen",
+            KEY_GESTURE_TYPE_RECENT_APPS to "View recent apps",
+            KEY_GESTURE_TYPE_BACK to "Go back",
+            KEY_GESTURE_TYPE_TAKE_SCREENSHOT to "Take screenshot",
+            KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER to "Show shortcuts",
+            KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL to "View notifications",
+            KEY_GESTURE_TYPE_LOCK_SCREEN to "Lock screen",
+            KEY_GESTURE_TYPE_ALL_APPS to "Open apps list",
+            KEY_GESTURE_TYPE_OPEN_NOTES to "Take a note",
+            KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS to "Open settings",
+            KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to "Open assistant",
+            KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to "Open assistant",
+
+            // Multitasking Category
+            KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to "Cycle forward through recent apps",
+            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to
+                "Use split screen with current app on the left",
+            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to
+                "Use split screen with current app on the right",
+            KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION to "Switch from split screen to full screen",
+            KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT to
+                "Switch to app on left or above while using split screen",
+            KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to
+                "Switch to app on right or below while using split screen",
+
+            // App Category
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to "Calculator",
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to "Calendar",
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to "Chrome",
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to "Contacts",
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to "Gmail",
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to "Maps",
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to "Messages",
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesRepository.kt
new file mode 100644
index 0000000..2e8cc00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesRepository.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.shortcut.data.repository
+
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import kotlinx.coroutines.flow.Flow
+
+interface ShortcutCategoriesRepository {
+    val categories: Flow<List<ShortcutCategory>>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
similarity index 60%
rename from packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
rename to packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
index 12dd581..899fd15 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
@@ -24,123 +24,34 @@
 import android.view.InputDevice
 import android.view.KeyCharacterMap
 import android.view.KeyEvent
-import android.view.KeyboardShortcutGroup
-import android.view.KeyboardShortcutInfo
 import com.android.systemui.Flags.shortcutHelperKeyGlyph
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
-import com.android.systemui.keyboard.shortcut.qualifiers.AppCategoriesShortcuts
-import com.android.systemui.keyboard.shortcut.qualifiers.CurrentAppShortcuts
-import com.android.systemui.keyboard.shortcut.qualifiers.InputShortcuts
-import com.android.systemui.keyboard.shortcut.qualifiers.MultitaskingShortcuts
-import com.android.systemui.keyboard.shortcut.qualifiers.SystemShortcuts
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo
 import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.withContext
 
-@SysUISingleton
-class ShortcutHelperCategoriesRepository
+class ShortcutCategoriesUtils
 @Inject
 constructor(
     private val context: Context,
-    @Background private val backgroundScope: CoroutineScope,
-    @Background private val backgroundDispatcher: CoroutineDispatcher,
-    @SystemShortcuts private val systemShortcutsSource: KeyboardShortcutGroupsSource,
-    @MultitaskingShortcuts private val multitaskingShortcutsSource: KeyboardShortcutGroupsSource,
-    @AppCategoriesShortcuts private val appCategoriesShortcutsSource: KeyboardShortcutGroupsSource,
-    @InputShortcuts private val inputShortcutsSource: KeyboardShortcutGroupsSource,
-    @CurrentAppShortcuts private val currentAppShortcutsSource: KeyboardShortcutGroupsSource,
+    @Background private val backgroundCoroutineContext: CoroutineContext,
     private val inputManager: InputManager,
-    stateRepository: ShortcutHelperStateRepository,
 ) {
-
-    private val sources =
-        listOf(
-            InternalGroupsSource(
-                source = systemShortcutsSource,
-                isTrusted = true,
-                typeProvider = { System },
-            ),
-            InternalGroupsSource(
-                source = multitaskingShortcutsSource,
-                isTrusted = true,
-                typeProvider = { MultiTasking },
-            ),
-            InternalGroupsSource(
-                source = appCategoriesShortcutsSource,
-                isTrusted = true,
-                typeProvider = { AppCategories },
-            ),
-            InternalGroupsSource(
-                source = inputShortcutsSource,
-                isTrusted = false,
-                typeProvider = { InputMethodEditor },
-            ),
-            InternalGroupsSource(
-                source = currentAppShortcutsSource,
-                isTrusted = false,
-                typeProvider = { groups -> getCurrentAppShortcutCategoryType(groups) },
-            ),
-        )
-
-    private val activeInputDevice =
-        stateRepository.state.map {
-            if (it is Active) {
-                withContext(backgroundDispatcher) { inputManager.getInputDevice(it.deviceId) }
-            } else {
-                null
-            }
-        }
-
-    val categories: Flow<List<ShortcutCategory>> =
-        activeInputDevice
-            .map { inputDevice ->
-                if (inputDevice == null) {
-                    return@map emptyList()
-                }
-                val groupsFromAllSources = sources.map { it.source.shortcutGroups(inputDevice.id) }
-                val supportedKeyCodes = fetchSupportedKeyCodes(inputDevice.id, groupsFromAllSources)
-                return@map sources.mapIndexedNotNull { index, internalGroupsSource ->
-                    fetchShortcutCategory(
-                        internalGroupsSource,
-                        groupsFromAllSources[index],
-                        inputDevice,
-                        supportedKeyCodes,
-                    )
-                }
-            }
-            .stateIn(
-                scope = backgroundScope,
-                started = SharingStarted.Lazily,
-                initialValue = emptyList(),
-            )
-
-    private fun fetchShortcutCategory(
-        internalGroupsSource: InternalGroupsSource,
-        groups: List<KeyboardShortcutGroup>,
+    fun fetchShortcutCategory(
+        type: ShortcutCategoryType?,
+        groups: List<InternalKeyboardShortcutGroup>,
         inputDevice: InputDevice,
         supportedKeyCodes: Set<Int>,
     ): ShortcutCategory? {
-        val type = internalGroupsSource.typeProvider(groups)
         return if (type == null) {
             null
         } else {
@@ -151,27 +62,17 @@
                 inputDevice.keyCharacterMap,
                 type,
                 groups,
-                internalGroupsSource.isTrusted,
+                type.isTrusted,
                 supportedKeyCodes,
             )
         }
     }
 
-    private fun getCurrentAppShortcutCategoryType(
-        shortcutGroups: List<KeyboardShortcutGroup>
-    ): ShortcutCategoryType? {
-        return if (shortcutGroups.isEmpty()) {
-            null
-        } else {
-            CurrentApp(packageName = shortcutGroups[0].packageName.toString())
-        }
-    }
-
     private fun toShortcutCategory(
         keyGlyphMap: KeyGlyphMap?,
         keyCharacterMap: KeyCharacterMap,
         type: ShortcutCategoryType,
-        shortcutGroups: List<KeyboardShortcutGroup>,
+        shortcutGroups: List<InternalKeyboardShortcutGroup>,
         keepIcons: Boolean,
         supportedKeyCodes: Set<Int>,
     ): ShortcutCategory? {
@@ -179,7 +80,7 @@
             shortcutGroups
                 .map { shortcutGroup ->
                     ShortcutSubCategory(
-                        shortcutGroup.label.toString(),
+                        shortcutGroup.label,
                         toShortcuts(
                             keyGlyphMap,
                             keyCharacterMap,
@@ -201,7 +102,7 @@
     private fun toShortcuts(
         keyGlyphMap: KeyGlyphMap?,
         keyCharacterMap: KeyCharacterMap,
-        infoList: List<KeyboardShortcutInfo>,
+        infoList: List<InternalKeyboardShortcutInfo>,
         keepIcons: Boolean,
         supportedKeyCodes: Set<Int>,
     ) =
@@ -216,13 +117,13 @@
     private fun toShortcut(
         keyGlyphMap: KeyGlyphMap?,
         keyCharacterMap: KeyCharacterMap,
-        shortcutInfo: KeyboardShortcutInfo,
+        shortcutInfo: InternalKeyboardShortcutInfo,
         keepIcon: Boolean,
     ): Shortcut? {
         val shortcutCommand =
             toShortcutCommand(keyGlyphMap, keyCharacterMap, shortcutInfo) ?: return null
         return Shortcut(
-            label = shortcutInfo.label!!.toString(),
+            label = shortcutInfo.label,
             icon = toShortcutIcon(keepIcon, shortcutInfo),
             commands = listOf(shortcutCommand),
         )
@@ -230,7 +131,7 @@
 
     private fun toShortcutIcon(
         keepIcon: Boolean,
-        shortcutInfo: KeyboardShortcutInfo,
+        shortcutInfo: InternalKeyboardShortcutInfo,
     ): ShortcutIcon? {
         if (!keepIcon) {
             return null
@@ -247,7 +148,7 @@
     private fun toShortcutCommand(
         keyGlyphMap: KeyGlyphMap?,
         keyCharacterMap: KeyCharacterMap,
-        info: KeyboardShortcutInfo,
+        info: InternalKeyboardShortcutInfo,
     ): ShortcutCommand? {
         val keys = mutableListOf<ShortcutKey>()
         var remainingModifiers = info.modifiers
@@ -272,7 +173,7 @@
             Log.wtf(TAG, "No keys for $info")
             return null
         }
-        return ShortcutCommand(keys)
+        return ShortcutCommand(keys = keys, isCustom = info.isCustomShortcut)
     }
 
     private fun toShortcutModifierKey(keyGlyphMap: KeyGlyphMap?, modifierMask: Int): ShortcutKey? {
@@ -325,11 +226,11 @@
         return null
     }
 
-    private suspend fun fetchSupportedKeyCodes(
+    suspend fun fetchSupportedKeyCodes(
         deviceId: Int,
-        groupsFromAllSources: List<List<KeyboardShortcutGroup>>,
+        groupsFromAllSources: List<List<InternalKeyboardShortcutGroup>>,
     ): Set<Int> =
-        withContext(backgroundDispatcher) {
+        withContext(backgroundCoroutineContext) {
             val allUsedKeyCodes =
                 groupsFromAllSources
                     .flatMap { groups -> groups.flatMap { group -> group.items } }
@@ -342,14 +243,8 @@
                 .toSet()
         }
 
-    private class InternalGroupsSource(
-        val source: KeyboardShortcutGroupsSource,
-        val isTrusted: Boolean,
-        val typeProvider: (groups: List<KeyboardShortcutGroup>) -> ShortcutCategoryType?,
-    )
-
     companion object {
-        private const val TAG = "SHCategoriesRepo"
+        private const val TAG = "ShortcutCategoriesUtils"
 
         private val SUPPORTED_MODIFIERS =
             listOf(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
index 6f19561..39fc27d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyboard.shortcut.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.repository.DefaultShortcutCategoriesRepository
 import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
@@ -28,9 +28,7 @@
 @SysUISingleton
 class ShortcutHelperCategoriesInteractor
 @Inject
-constructor(
-    categoriesRepository: ShortcutHelperCategoriesRepository,
-) {
+constructor(categoriesRepository: DefaultShortcutCategoriesRepository) {
 
     val shortcutCategories: Flow<List<ShortcutCategory>> =
         categoriesRepository.categories.map { categories ->
@@ -42,12 +40,12 @@
             shortcutCategory.subCategories.map {
                 ShortcutSubCategory(
                     label = it.label,
-                    shortcuts = groupShortcutsInSubcategory(it.shortcuts)
+                    shortcuts = groupShortcutsInSubcategory(it.shortcuts),
                 )
             }
         return ShortcutCategory(
             type = shortcutCategory.type,
-            subCategories = subCategoriesWithGroupedShortcuts
+            subCategories = subCategoriesWithGroupedShortcuts,
         )
     }
 
@@ -59,7 +57,7 @@
                 Shortcut(
                     label = commonLabel,
                     icon = groupedShortcuts.firstOrNull()?.icon,
-                    commands = groupedShortcuts.flatMap { it.commands }
+                    commands = groupedShortcuts.flatMap { it.commands },
                 )
             }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
index c89ef15..813a1fca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
@@ -17,24 +17,36 @@
 package com.android.systemui.keyboard.shortcut.shared.model
 
 sealed interface ShortcutCategoryType {
-    data object System : ShortcutCategoryType
+    val isTrusted: Boolean
 
-    data object MultiTasking : ShortcutCategoryType
+    data object System : ShortcutCategoryType {
+        override val isTrusted: Boolean = true
+    }
 
-    data object InputMethodEditor : ShortcutCategoryType
+    data object MultiTasking : ShortcutCategoryType {
+        override val isTrusted: Boolean = true
+    }
 
-    data object AppCategories : ShortcutCategoryType
+    data object InputMethodEditor : ShortcutCategoryType {
+        override val isTrusted: Boolean = false
+    }
 
-    data class CurrentApp(val packageName: String) : ShortcutCategoryType
+    data object AppCategories : ShortcutCategoryType {
+        override val isTrusted: Boolean = true
+    }
+
+    data class CurrentApp(val packageName: String) : ShortcutCategoryType {
+        override val isTrusted: Boolean = false
+    }
 }
 
 data class ShortcutCategory(
     val type: ShortcutCategoryType,
-    val subCategories: List<ShortcutSubCategory>
+    val subCategories: List<ShortcutSubCategory>,
 ) {
     constructor(
         type: ShortcutCategoryType,
-        vararg subCategories: ShortcutSubCategory
+        vararg subCategories: ShortcutSubCategory,
     ) : this(type, subCategories.asList())
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
index 28451ae..c7e6b43 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
@@ -18,10 +18,11 @@
 
 import androidx.annotation.DrawableRes
 
-data class ShortcutCommand(val keys: List<ShortcutKey>)
+data class ShortcutCommand(val keys: List<ShortcutKey>, val isCustom: Boolean = false)
 
 class ShortcutCommandBuilder {
     private val keys = mutableListOf<ShortcutKey>()
+    private var isCustom = false
 
     fun key(text: String) {
         keys += ShortcutKey.Text(text)
@@ -31,7 +32,11 @@
         keys += ShortcutKey.Icon.ResIdIcon(drawableResId)
     }
 
-    fun build() = ShortcutCommand(keys)
+    fun isCustom(isCustom: Boolean) {
+        this.isCustom = isCustom
+    }
+
+    fun build() = ShortcutCommand(keys, isCustom)
 }
 
 fun shortcutCommand(block: ShortcutCommandBuilder.() -> Unit) =
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
index 807c70b..10a201e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
@@ -27,6 +27,7 @@
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
@@ -36,6 +37,7 @@
 import com.android.systemui.keyboard.shortcut.ui.composable.getWidth
 import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import com.android.systemui.statusbar.phone.createBottomSheet
 import javax.inject.Inject
@@ -86,6 +88,7 @@
                     },
                 )
                 dialog.setOnDismissListener { shortcutHelperViewModel.onViewClosed() }
+                dialog.setTitle(stringResource(R.string.shortcut_helper_title))
             },
             maxWidth = ShortcutHelperBottomSheet.LargeScreenWidthLandscape,
         )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 7292dda..b30e1e9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -26,6 +26,7 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
@@ -44,7 +45,6 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 object KeyguardClockViewBinder {
     private val TAG = KeyguardClockViewBinder::class.simpleName!!
@@ -133,15 +133,26 @@
                     launch {
                         if (!MigrateClocksToBlueprint.isEnabled) return@launch
                         aodBurnInViewModel.movement.collect { burnInModel ->
-                            viewModel.currentClock.value?.let {
-                                it.largeClock.layout.applyAodBurnIn(
+                            viewModel.currentClock.value
+                                ?.largeClock
+                                ?.layout
+                                ?.applyAodBurnIn(
                                     AodClockBurnInModel(
                                         translationX = burnInModel.translationX.toFloat(),
                                         translationY = burnInModel.translationY.toFloat(),
                                         scale = burnInModel.scale,
                                     )
                                 )
-                            }
+                        }
+                    }
+
+                    launch {
+                        if (!MigrateClocksToBlueprint.isEnabled) return@launch
+                        viewModel.largeClockTextSize.collect { fontSizePx ->
+                            viewModel.currentClock.value
+                                ?.largeClock
+                                ?.events
+                                ?.onFontSettingChanged(fontSizePx = fontSizePx.toFloat())
                         }
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 82adced..3a7a640 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -187,6 +187,9 @@
     val largeClockTopMargin: Flow<Int> =
         configurationInteractor.onAnyConfigurationChange.map { getLargeClockTopMargin() }
 
+    val largeClockTextSize: Flow<Int> =
+        configurationInteractor.dimensionPixelSize(customR.dimen.large_clock_text_size)
+
     enum class ClockLayout {
         LARGE_CLOCK,
         SMALL_CLOCK,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 51ff598..ec6a17b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -70,6 +70,7 @@
 import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInRoot
+import androidx.compose.ui.layout.positionOnScreen
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.res.stringResource
@@ -124,6 +125,7 @@
 import com.android.systemui.qs.ui.composable.QuickSettingsTheme
 import com.android.systemui.res.R
 import com.android.systemui.util.LifecycleFragment
+import com.android.systemui.util.animation.UniqueObjectHostView
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.printSection
 import com.android.systemui.util.println
@@ -447,8 +449,7 @@
     }
 
     override fun setShouldUpdateSquishinessOnMedia(shouldUpdate: Boolean) {
-        super.setShouldUpdateSquishinessOnMedia(shouldUpdate)
-        // TODO (b/353253280)
+        viewModel.shouldUpdateSquishinessOnMedia = shouldUpdate
     }
 
     override fun setInSplitShade(isInSplitShade: Boolean) {
@@ -660,7 +661,20 @@
 
                     Column(
                         modifier =
-                            Modifier.offset {
+                            Modifier.onPlaced { coordinates ->
+                                    val positionOnScreen = coordinates.positionOnScreen()
+                                    val left = positionOnScreen.x
+                                    val right = left + coordinates.size.width
+                                    val top = positionOnScreen.y
+                                    val bottom = top + coordinates.size.height
+                                    viewModel.applyNewQsScrollerBounds(
+                                        left = left,
+                                        top = top,
+                                        right = right,
+                                        bottom = bottom,
+                                    )
+                                }
+                                .offset {
                                     IntOffset(
                                         x = 0,
                                         y = viewModel.qsScrollTranslationY.fastRoundToInt(),
@@ -704,7 +718,10 @@
                         val Media =
                             @Composable {
                                 if (viewModel.qsMediaVisible) {
-                                    MediaObject(mediaHost = viewModel.qsMediaHost)
+                                    MediaObject(
+                                        mediaHost = viewModel.qsMediaHost,
+                                        update = { translationY = viewModel.qsMediaTranslationY },
+                                    )
                                 }
                             }
                         Box(
@@ -987,7 +1004,11 @@
     }
 
 @Composable
-private fun MediaObject(mediaHost: MediaHost, modifier: Modifier = Modifier) {
+private fun MediaObject(
+    mediaHost: MediaHost,
+    modifier: Modifier = Modifier,
+    update: UniqueObjectHostView.() -> Unit = {},
+) {
     Box {
         AndroidView(
             modifier = modifier,
@@ -1000,6 +1021,7 @@
                         )
                 }
             },
+            update = { view -> view.update() },
             onReset = {},
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index e912a0c..9029563 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -26,6 +26,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.lifecycle.LifecycleCoroutineScope
+import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.keyguard.BouncerPanelExpansionCalculator
 import com.android.systemui.Dumpable
@@ -270,6 +271,23 @@
     val qsMediaInRow: Boolean
         get() = qsMediaInRowViewModel.shouldMediaShowInRow
 
+    var shouldUpdateSquishinessOnMedia by mutableStateOf(false)
+
+    val qsMediaTranslationY by derivedStateOf {
+        if (
+            qsExpansion > 0f &&
+                !isKeyguardState &&
+                !qqsMediaVisible &&
+                !qsMediaInRow &&
+                !isInSplitShade
+        ) {
+            val interpolation = Interpolators.ACCELERATE.getInterpolation(1f - qsExpansion)
+            -qsMediaHost.hostView.height * 1.3f * interpolation
+        } else {
+            0f
+        }
+    }
+
     val animateTilesExpansion: Boolean
         get() = inFirstPage && !mediaSuddenlyAppearingInLandscape
 
@@ -297,6 +315,18 @@
                 MediaHostState.EXPANDED
             }
 
+    private val shouldApplySquishinessToMedia by derivedStateOf {
+        shouldUpdateSquishinessOnMedia || (isInSplitShade && statusBarState == StatusBarState.SHADE)
+    }
+
+    private val mediaSquishiness by derivedStateOf {
+        if (shouldApplySquishinessToMedia) {
+            squishinessFraction
+        } else {
+            1f
+        }
+    }
+
     private var qsBounds by mutableStateOf(Rect())
 
     private val constrainedSquishinessFraction: Float
@@ -355,8 +385,6 @@
     private val isOverscrolling: Boolean
         get() = overScrollAmount != 0
 
-    private var shouldUpdateMediaSquishiness by mutableStateOf(false)
-
     private val forceQs by derivedStateOf {
         (isQsExpanded || isStackScrollerOverscrolling) &&
             (isKeyguardState && !showCollapsedOnKeyguard)
@@ -394,11 +422,26 @@
                 ),
         )
 
+    fun applyNewQsScrollerBounds(left: Float, top: Float, right: Float, bottom: Float) {
+        if (usingMedia) {
+            qsMediaHost.currentClipping.set(
+                left.toInt(),
+                top.toInt(),
+                right.toInt(),
+                bottom.toInt(),
+            )
+        }
+    }
+
     override suspend fun onActivated(): Nothing {
         initMediaHosts() // init regardless of using media (same as current QS).
         coroutineScope {
             launch { hydrateSquishinessInteractor() }
-            launch { hydrateQqsMediaExpansion() }
+            if (usingMedia) {
+                launch { hydrateQqsMediaExpansion() }
+                launch { hydrateMediaSquishiness() }
+                launch { hydrateMediaDisappearParameters() }
+            }
             launch { hydrator.activate() }
             launch { containerViewModel.activate() }
             launch { qqsMediaInRowViewModel.activate() }
@@ -429,6 +472,21 @@
         snapshotFlow { qqsMediaExpansion }.collect { qqsMediaHost.expansion = it }
     }
 
+    private suspend fun hydrateMediaSquishiness() {
+        snapshotFlow { mediaSquishiness }.collect { qsMediaHost.squishFraction = it }
+    }
+
+    private suspend fun hydrateMediaDisappearParameters() {
+        coroutineScope {
+            launch {
+                snapshotFlow { qqsMediaInRow }.collect { qqsMediaHost.applyDisappearParameters(it) }
+            }
+            launch {
+                snapshotFlow { qsMediaInRow }.collect { qsMediaHost.applyDisappearParameters(it) }
+            }
+        }
+    }
+
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.asIndenting().run {
             printSection("Quick Settings state") {
@@ -474,6 +532,9 @@
                 println("qsMediaInRow", qsMediaInRow)
                 println("collapsedLandscapeMedia", collapsedLandscapeMedia)
                 println("qqsMediaExpansion", qqsMediaExpansion)
+                println("shouldUpdateSquishinessOnMedia", shouldUpdateSquishinessOnMedia)
+                println("mediaSquishiness", mediaSquishiness)
+                println("qsMediaTranslationY", qsMediaTranslationY)
             }
         }
     }
@@ -510,3 +571,22 @@
         // lazily.
         .onStart { emit(mediaHost.visible) }
 }
+
+// Taken from QSPanelControllerBase
+private fun MediaHost.applyDisappearParameters(inRow: Boolean) {
+    disappearParameters.apply {
+        fadeStartPosition = 0.95f
+        disappearStart = 0f
+        if (inRow) {
+            disappearSize.set(0f, 0.4f)
+            gonePivot.set(1f, 0f)
+            contentTranslationFraction.set(0.25f, 1f)
+            disappearEnd = 0.6f
+        } else {
+            disappearSize.set(1f, 0f)
+            gonePivot.set(0f, 0f)
+            contentTranslationFraction.set(0f, 1f)
+            disappearEnd = 0.95f
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 4a51bf0..177a5be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.panels.ui.compose.infinitegrid
 
 import android.graphics.drawable.Animatable
+import android.graphics.drawable.Drawable
 import android.text.TextUtils
 import androidx.compose.animation.animateColorAsState
 import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
@@ -30,7 +31,9 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.material3.MaterialTheme
@@ -68,6 +71,8 @@
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.common.ui.compose.load
 import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.SideIconHeight
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.SideIconWidth
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel
 import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState
 import com.android.systemui.res.R
@@ -79,6 +84,7 @@
     label: String,
     secondaryLabel: String?,
     icon: Icon,
+    sideDrawable: Drawable?,
     colors: TileColors,
     squishiness: () -> Float,
     accessibilityUiState: AccessibilityUiState? = null,
@@ -135,6 +141,14 @@
             colors = colors,
             accessibilityUiState = accessibilityUiState,
         )
+
+        if (sideDrawable != null) {
+            Image(
+                painter = rememberDrawablePainter(sideDrawable),
+                contentDescription = null,
+                modifier = Modifier.width(SideIconWidth).height(SideIconHeight),
+            )
+        }
     }
 }
 
@@ -229,6 +243,8 @@
 object CommonTileDefaults {
     val IconSize = 32.dp
     val LargeTileIconSize = 28.dp
+    val SideIconWidth = 32.dp
+    val SideIconHeight = 20.dp
     val ToggleTargetSize = 56.dp
     val TileHeight = 72.dp
     val TilePadding = 8.dp
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index 9bbf290..fe59c4d3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -130,15 +130,7 @@
 
     // TODO(b/361789146): Draw the shapes instead of clipping
     val tileShape = TileDefaults.animateTileShape(uiState.state)
-    val animatedColor by
-        animateColorAsState(
-            if (iconOnly || !uiState.handlesSecondaryClick) {
-                colors.iconBackground
-            } else {
-                colors.background
-            },
-            label = "QSTileBackgroundColor",
-        )
+    val animatedColor by animateColorAsState(colors.background, label = "QSTileBackgroundColor")
 
     TileExpandable(
         color = { animatedColor },
@@ -199,6 +191,7 @@
                     label = uiState.label,
                     secondaryLabel = uiState.secondaryLabel,
                     icon = icon,
+                    sideDrawable = uiState.sideDrawable,
                     colors = colors,
                     iconShape = iconShape,
                     toggleClick = secondaryClick,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
index 56675e4..2fc7f0f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.panels.ui.viewmodel
 
 import android.content.res.Resources
+import android.graphics.drawable.Drawable
 import android.service.quicksettings.Tile
 import android.text.TextUtils
 import android.widget.Switch
@@ -36,6 +37,7 @@
     val handlesLongClick: Boolean,
     val handlesSecondaryClick: Boolean,
     val icon: Supplier<QSTile.Icon?>,
+    val sideDrawable: Drawable?,
     val accessibilityUiState: AccessibilityUiState,
 )
 
@@ -90,6 +92,7 @@
         handlesLongClick = handlesLongClick,
         handlesSecondaryClick = handlesSecondaryClick,
         icon = icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null },
+        sideDrawable = sideViewCustomDrawable,
         AccessibilityUiState(
             contentDescription?.toString() ?: "",
             stateDescription.toString(),
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt b/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt
index 6aaa27d..eca4051 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt
@@ -69,7 +69,11 @@
     @WorkerThread
     fun doUnBind() {
         if (shouldUnBind) {
-            userContextProvider.userContext.unbindService(this)
+            try {
+                userContextProvider.userContext.unbindService(this)
+            } catch (e: IllegalArgumentException) {
+                Log.e(TAG, "Can't disconnect because service wasn't connected anyways.", e)
+            }
             shouldUnBind = false
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 580a51a..daeaaa5 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -373,6 +373,7 @@
                                     "device was unlocked with alternate bouncer showing" +
                                         " and shade didn't need to be left open"
                             } else {
+                                replaceLockscreenSceneOnBackStack()
                                 null
                             }
                         }
@@ -391,16 +392,7 @@
                                 val prevScene = previousScene.value
                                 val targetScene = prevScene ?: Scenes.Gone
                                 if (targetScene != Scenes.Gone) {
-                                    sceneBackInteractor.updateBackStack { stack ->
-                                        val list = stack.asIterable().toMutableList()
-                                        check(list.last() == Scenes.Lockscreen) {
-                                            "The bottommost/last SceneKey of the back stack isn't" +
-                                                " the Lockscreen scene like expected. The back" +
-                                                " stack is $stack."
-                                        }
-                                        list[list.size - 1] = Scenes.Gone
-                                        sceneStackOf(*list.toTypedArray())
-                                    }
+                                    replaceLockscreenSceneOnBackStack()
                                 }
                                 targetScene to
                                     "device was unlocked with primary bouncer showing," +
@@ -435,6 +427,20 @@
         }
     }
 
+    /** If the [Scenes.Lockscreen] is on the backstack, replaces it with [Scenes.Gone]. */
+    private fun replaceLockscreenSceneOnBackStack() {
+        sceneBackInteractor.updateBackStack { stack ->
+            val list = stack.asIterable().toMutableList()
+            check(list.last() == Scenes.Lockscreen) {
+                "The bottommost/last SceneKey of the back stack isn't" +
+                    " the Lockscreen scene like expected. The back" +
+                    " stack is $stack."
+            }
+            list[list.size - 1] = Scenes.Gone
+            sceneStackOf(*list.toTypedArray())
+        }
+    }
+
     private fun handlePowerState() {
         applicationScope.launch {
             powerInteractor.detailedWakefulness.collect { wakefulness ->
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 24dba59..4d77e3e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -17,8 +17,6 @@
 package com.android.systemui.shade;
 
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
 
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
@@ -27,16 +25,13 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
-import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.os.Binder;
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.Trace;
 import android.util.Log;
 import android.view.Display;
-import android.view.Gravity;
 import android.view.IWindow;
 import android.view.IWindowSession;
 import android.view.View;
@@ -271,33 +266,7 @@
         // Now that the notification shade encompasses the sliding panel and its
         // translucent backdrop, the entire thing is made TRANSLUCENT and is
         // hardware-accelerated.
-        mLp = new LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                LayoutParams.TYPE_NOTIFICATION_SHADE,
-                LayoutParams.FLAG_NOT_FOCUSABLE
-                        | LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
-                        | LayoutParams.FLAG_SPLIT_TOUCH
-                        | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                        | LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
-                PixelFormat.TRANSLUCENT);
-        mLp.token = new Binder();
-        mLp.gravity = Gravity.TOP;
-        mLp.setFitInsetsTypes(0 /* types */);
-        mLp.setTitle("NotificationShade");
-        mLp.packageName = mContext.getPackageName();
-        mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        mLp.privateFlags |= PRIVATE_FLAG_OPTIMIZE_MEASURE;
-
-        if (SceneContainerFlag.isEnabled()) {
-            // This prevents the appearance and disappearance of the software keyboard (also known
-            // as the "IME") from scrolling/panning the window to make room for the keyboard.
-            //
-            // The scene container logic does its own adjustment and animation when the IME appears
-            // or disappears.
-            mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
-        }
-
+        mLp = ShadeWindowLayoutParams.INSTANCE.create(mContext);
         mWindowManager.addView(mWindowRootView, mLp);
 
         // We use BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE here, however, there is special logic in
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 63510b8..e15830e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -157,7 +157,6 @@
 
     @SysUISingleton
     @Provides
-    @ShadeDisplayAware
     fun provideShadePositionRepository(impl: ShadePositionRepositoryImpl): ShadePositionRepository {
         ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
         return impl
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLayoutParams.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLayoutParams.kt
new file mode 100644
index 0000000..6bb50f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLayoutParams.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.content.Context
+import android.graphics.PixelFormat
+import android.os.Binder
+import android.view.Gravity
+import android.view.ViewGroup
+import android.view.WindowManager.LayoutParams
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+
+object ShadeWindowLayoutParams {
+    /**
+     * Creates [LayoutParams] for the shade window.
+     *
+     * This is extracted to a single place as those layout params will be used by several places:
+     * - When sysui starts, and the shade is added the first time
+     * - When the shade moves to a different window (e.g. while an external display is connected)
+     */
+    fun create(context: Context): LayoutParams {
+        return LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                LayoutParams.TYPE_NOTIFICATION_SHADE,
+                LayoutParams.FLAG_NOT_FOCUSABLE or
+                    LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING or
+                    LayoutParams.FLAG_SPLIT_TOUCH or
+                    LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+                    LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+                // Now that the notification shade encompasses the sliding panel and its
+                // translucent backdrop, the entire thing is made TRANSLUCENT and is
+                // hardware-accelerated.
+                PixelFormat.TRANSLUCENT,
+            )
+            .apply {
+                token = Binder()
+                gravity = Gravity.TOP
+                fitInsetsTypes = 0
+                title = "NotificationShade"
+                packageName = context.packageName
+                layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+                privateFlags = privateFlags or LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE
+                if (SceneContainerFlag.isEnabled) {
+                    // This prevents the appearance and disappearance of the software keyboard (also
+                    // known as the "IME") from scrolling/panning the window to make room for the
+                    // keyboard.
+                    //
+                    // The scene container logic does its own adjustment and animation when the IME
+                    // appears or disappears.
+                    softInputMode = LayoutParams.SOFT_INPUT_ADJUST_NOTHING
+                }
+            }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt
new file mode 100644
index 0000000..37210b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.data.repository
+
+import android.view.Display
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeShadePositionRepository : ShadePositionRepository {
+    private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
+
+    override fun setDisplayId(displayId: Int) {
+        _displayId.value = displayId
+    }
+
+    override val displayId: StateFlow<Int>
+        get() = _displayId
+
+    override fun resetDisplayId() {
+        _displayId.value = Display.DEFAULT_DISPLAY
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
index 6f492cf..c23ff53 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
@@ -32,7 +32,7 @@
 
     /** Is the refactor enabled */
     @JvmStatic
-    inline val isEnabled
+    inline val isEnabled: Boolean
         get() = Flags.shadeWindowGoesAround()
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
index 564d52a..1cb4c44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
@@ -27,7 +27,10 @@
 interface SystemStatusAnimationScheduler :
     CallbackController<SystemStatusAnimationCallback>, Dumpable {
 
-    /** StateFlow holding the current [SystemEventAnimationState] at any time. */
+    /**
+     * The current state of the animation. This can be used from compose functions to coordinate
+     * their animations with the chip
+     */
     val animationState: StateFlow<SystemEventAnimationState>
 
     fun onStatusEvent(event: StatusEvent)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/data/repository/SystemStatusEventAnimationRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/data/repository/SystemStatusEventAnimationRepository.kt
new file mode 100644
index 0000000..971f5d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/data/repository/SystemStatusEventAnimationRepository.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+/** Repository to expose the [SystemStatusAnimationScheduler] state via flows */
+interface SystemStatusEventAnimationRepository {
+    val animationState: StateFlow<SystemEventAnimationState>
+}
+
+@SysUISingleton
+class SystemStatusEventAnimationRepositoryImpl
+@Inject
+constructor(scheduler: SystemStatusAnimationScheduler) : SystemStatusEventAnimationRepository {
+    override val animationState = scheduler.animationState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/domain/interactor/SystemStatusEventAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/domain/interactor/SystemStatusEventAnimationInteractor.kt
new file mode 100644
index 0000000..3e30642
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/domain/interactor/SystemStatusEventAnimationInteractor.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.events.domain.interactor
+
+import android.view.View
+import androidx.core.animation.Animator
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.events.data.repository.SystemStatusEventAnimationRepository
+import com.android.systemui.statusbar.phone.fragment.StatusBarSystemEventDefaultAnimator
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Interactor for dealing with system status event animations. This class can be used to monitor the
+ * current [animationState], and defines some common animation functions that an handle hiding
+ * system chrome in order to make space for the event chips
+ */
+@SysUISingleton
+class SystemStatusEventAnimationInteractor
+@Inject
+constructor(
+    repo: SystemStatusEventAnimationRepository,
+    configurationInteractor: ConfigurationInteractor,
+    @Application scope: CoroutineScope,
+) {
+    private val chipAnimateInTranslationX =
+        configurationInteractor
+            .dimensionPixelSize(R.dimen.ongoing_appops_chip_animation_in_status_bar_translation_x)
+            .stateIn(scope, SharingStarted.Eagerly, 0)
+
+    private val chipAnimateOutTranslationX =
+        configurationInteractor
+            .dimensionPixelSize(R.dimen.ongoing_appops_chip_animation_out_status_bar_translation_x)
+            .stateIn(scope, SharingStarted.Eagerly, 0)
+
+    val animationState = repo.animationState
+
+    private fun getDefaultStatusBarAnimationForChipEnter(
+        setX: (Float) -> Unit,
+        setAlpha: (Float) -> Unit,
+    ): Animator {
+        return StatusBarSystemEventDefaultAnimator.getDefaultStatusBarAnimationForChipEnter(
+            chipAnimateInTranslationX.value,
+            setX,
+            setAlpha,
+        )
+    }
+
+    private fun getDefaultStatusBarAnimationForChipExit(
+        setX: (Float) -> Unit,
+        setAlpha: (Float) -> Unit,
+    ): Animator {
+        return StatusBarSystemEventDefaultAnimator.getDefaultStatusBarAnimationForChipExit(
+            chipAnimateOutTranslationX.value,
+            setX,
+            setAlpha,
+        )
+    }
+
+    fun animateStatusBarContentForChipEnter(v: View) {
+        getDefaultStatusBarAnimationForChipEnter(setX = v::setTranslationX, setAlpha = v::setAlpha)
+            .start()
+    }
+
+    fun animateStatusBarContentForChipExit(v: View) {
+        v.translationX = chipAnimateOutTranslationX.value.toFloat()
+        getDefaultStatusBarAnimationForChipExit(setX = v::setTranslationX, setAlpha = v::setAlpha)
+            .start()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index e74ed8d..c487ff5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -49,6 +49,7 @@
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
+import android.util.Log;
 import android.view.ContentInfo;
 
 import androidx.annotation.NonNull;
@@ -65,6 +66,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.icon.IconPack;
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
 import com.android.systemui.statusbar.notification.row.NotificationGuts;
@@ -194,6 +196,10 @@
      */
     private boolean mIsDemoted = false;
 
+    // TODO(b/377565433): Move into NotificationContentModel during/after
+    //  NotificationRowContentBinderRefactor.
+    private PromotedNotificationContentModel mPromotedNotificationContentModel;
+
     /**
      * True if both
      *  1) app provided full screen intent but does not have the permission to send it
@@ -1061,6 +1067,32 @@
         this.mHeadsUpStatusBarTextPublic.setValue(headsUpStatusBarModel.getPublicText());
     }
 
+    /**
+     * Gets the content needed to render this notification as a promoted notification on various
+     * surfaces (like status bar chips and AOD).
+     */
+    public PromotedNotificationContentModel getPromotedNotificationContentModel() {
+        if (PromotedNotificationContentModel.featureFlagEnabled()) {
+            return mPromotedNotificationContentModel;
+        } else {
+            Log.wtf(TAG, "getting promoted content without feature flag enabled");
+            return null;
+        }
+    }
+
+    /**
+     * Sets the content needed to render this notification as a promoted notification on various
+     * surfaces (like status bar chips and AOD).
+     */
+    public void setPromotedNotificationContentModel(
+            @Nullable PromotedNotificationContentModel promotedNotificationContentModel) {
+        if (PromotedNotificationContentModel.featureFlagEnabled()) {
+            this.mPromotedNotificationContentModel = promotedNotificationContentModel;
+        } else {
+            Log.wtf(TAG, "setting promoted content without feature flag enabled");
+        }
+    }
+
     /** Information about a suggestion that is being edited. */
     public static class EditedSuggestionInfo {
 
@@ -1101,4 +1133,6 @@
     private static final long INITIALIZATION_DELAY = 400;
     private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
     private static final int COLOR_INVALID = 1;
+
+    private static final String TAG = "NotificationEntry";
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 23da90d..8edbc5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProvider
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
@@ -173,6 +174,7 @@
             isGroupSummary = sbn.notification.isGroupSummary,
             bucket = bucket,
             callType = sbn.toCallType(),
+            promotedContent = promotedNotificationContentModel,
         )
     }
 }
@@ -199,6 +201,7 @@
     isGroupSummary: Boolean,
     bucket: Int,
     callType: CallType,
+    promotedContent: PromotedNotificationContentModel?,
 ): ActiveNotificationModel {
     return individuals[key]?.takeIf {
         it.isCurrent(
@@ -223,6 +226,7 @@
             contentIntent = contentIntent,
             bucket = bucket,
             callType = callType,
+            promotedContent = promotedContent,
         )
     }
         ?: ActiveNotificationModel(
@@ -247,6 +251,7 @@
             contentIntent = contentIntent,
             bucket = bucket,
             callType = callType,
+            promotedContent = promotedContent,
         )
 }
 
@@ -272,6 +277,7 @@
     isGroupSummary: Boolean,
     bucket: Int,
     callType: CallType,
+    promotedContent: PromotedNotificationContentModel?,
 ): Boolean {
     return when {
         key != this.key -> false
@@ -295,6 +301,9 @@
         contentIntent != this.contentIntent -> false
         bucket != this.bucket -> false
         callType != this.callType -> false
+        // QQQ: Do we need to do the same `isCurrent` thing within the content model to avoid
+        // recreating the active notification model constantly?
+        promotedContent != this.promotedContent -> false
         else -> true
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
new file mode 100644
index 0000000..41ee3b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted.shared.model
+
+import android.annotation.DrawableRes
+import android.graphics.drawable.Icon
+import com.android.internal.widget.NotificationProgressModel
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
+
+/**
+ * The content needed to render a promoted notification to surfaces besides the notification stack,
+ * like the skeleton view on AOD or the status bar chip.
+ */
+data class PromotedNotificationContentModel(
+    val key: String,
+
+    // for all styles:
+    val skeletonSmallIcon: Icon?, // TODO(b/377568176): Make into an IconModel.
+    val appName: CharSequence?,
+    val subText: CharSequence?,
+    val time: When?,
+    val lastAudiblyAlertedMs: Long,
+    @DrawableRes val profileBadgeResId: Int?,
+    val title: CharSequence?,
+    val text: CharSequence?,
+    val skeletonLargeIcon: Icon?, // TODO(b/377568176): Make into an IconModel.
+    val style: Style,
+
+    // for CallStyle:
+    val personIcon: Icon?, // TODO(b/377568176): Make into an IconModel.
+    val personName: CharSequence?,
+    val verificationIcon: Icon?, // TODO(b/377568176): Make into an IconModel.
+    val verificationText: CharSequence?,
+
+    // for ProgressStyle:
+    val progress: NotificationProgressModel?,
+) {
+    class Builder(val key: String) {
+        var skeletonSmallIcon: Icon? = null
+        var appName: CharSequence? = null
+        var subText: CharSequence? = null
+        var time: When? = null
+        var lastAudiblyAlertedMs: Long = 0L
+        @DrawableRes var profileBadgeResId: Int? = null
+        var title: CharSequence? = null
+        var text: CharSequence? = null
+        var skeletonLargeIcon: Icon? = null
+        var style: Style = Style.Ineligible
+
+        // for CallStyle:
+        var personIcon: Icon? = null
+        var personName: CharSequence? = null
+        var verificationIcon: Icon? = null
+        var verificationText: CharSequence? = null
+
+        // for ProgressStyle:
+        var progress: NotificationProgressModel? = null
+
+        fun build() =
+            PromotedNotificationContentModel(
+                key = key,
+                skeletonSmallIcon = skeletonSmallIcon,
+                appName = appName,
+                subText = subText,
+                time = time,
+                lastAudiblyAlertedMs = lastAudiblyAlertedMs,
+                profileBadgeResId = profileBadgeResId,
+                title = title,
+                text = text,
+                skeletonLargeIcon = skeletonLargeIcon,
+                style = style,
+                personIcon = personIcon,
+                personName = personName,
+                verificationIcon = verificationIcon,
+                verificationText = verificationText,
+                progress = progress,
+            )
+    }
+
+    /** The timestamp associated with a notification, along with the mode used to display it. */
+    data class When(val time: Long, val mode: Mode) {
+        /** The mode used to display a notification's `when` value. */
+        enum class Mode {
+            Absolute,
+            CountDown,
+            CountUp,
+        }
+    }
+
+    /** The promotion-eligible style of a notification, or [Style.Ineligible] if not. */
+    enum class Style {
+        BigPicture,
+        BigText,
+        Call,
+        Progress,
+        Ineligible,
+    }
+
+    companion object {
+        @JvmStatic
+        fun featureFlagEnabled(): Boolean =
+            PromotedNotificationUi.isEnabled || StatusBarNotifChips.isEnabled
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index 19a92a2..a2b7155 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -17,7 +17,9 @@
 
 import android.app.PendingIntent
 import android.graphics.drawable.Icon
+import android.util.Log
 import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import com.android.systemui.statusbar.notification.stack.PriorityBucket
 
 /**
@@ -36,6 +38,7 @@
     val groupKey: String?,
     /** When this notification was posted. */
     val whenTime: Long,
+    // TODO(b/377566661): Make isPromoted just check if promotedContent != null.
     /** True if this notification should be promoted and false otherwise. */
     val isPromoted: Boolean,
     /** Is this entry in the ambient / minimized section (lowest priority)? */
@@ -78,7 +81,24 @@
     @PriorityBucket val bucket: Int,
     /** The call type set on the notification. */
     val callType: CallType,
-) : ActiveNotificationEntryModel()
+    /**
+     * The content needed to render this as a promoted notification on various surfaces, or null if
+     * this notification cannot be rendered as a promoted notification.
+     */
+    val promotedContent: PromotedNotificationContentModel?,
+) : ActiveNotificationEntryModel() {
+    init {
+        if (!PromotedNotificationContentModel.featureFlagEnabled()) {
+            if (promotedContent != null) {
+                Log.wtf(TAG, "passing non-null promoted content without feature flag enabled")
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "ActiveNotificationEntryModel"
+    }
+}
 
 /** Model for a group of notifications. */
 data class ActiveNotificationGroupModel(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index d991b1d..41db5f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -174,6 +174,14 @@
     private val dumpableName = TAG + nameSuffix
     private val commandName = StatusBarInsetsCommand.NAME + nameSuffix
 
+    init {
+        if (!StatusBarConnectedDisplays.isEnabled) {
+            // Call start(), since it is not called when the flag is disabled, to keep the old
+            // behavior as it was.
+            start()
+        }
+    }
+
     override fun start() {
         configurationController.addCallback(this)
         dumpManager.registerDumpable(dumpableName, this)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 0c511aea..aef26de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -520,6 +520,7 @@
             mListenForCanShowAlternateBouncer.cancel(null);
         }
         mListenForCanShowAlternateBouncer = null;
+
         // Collector that keeps the AlternateBouncerInteractor#canShowAlternateBouncer flow hot.
         mListenForCanShowAlternateBouncer = mJavaAdapter.alwaysCollectFlow(
                 mAlternateBouncerInteractor.getCanShowAlternateBouncer(),
@@ -568,6 +569,12 @@
     }
 
     private void consumeCanShowAlternateBouncer(boolean canShow) {
+        if (SceneContainerFlag.isEnabled()) {
+            // When the scene framework is enabled, the alternative bouncer is hidden from the scene
+            // framework logic so there's no need for this logic here.
+            return;
+        }
+
         // Hack: this is required to fix issues where
         // KeyguardBouncerRepository#alternateBouncerVisible state is incorrectly set and then never
         // reset. This is caused by usages of show()/forceShow() that only read this flow to set the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index c55a63c..23b4b65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -376,7 +376,11 @@
         mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
 
         mHomeStatusBarViewBinder.bind(
-                mStatusBar, mHomeStatusBarViewModel, mStatusBarVisibilityChangeListener);
+                mStatusBar,
+                mHomeStatusBarViewModel,
+                /* systemEventChipAnimateIn */ null,
+                /* systemEventChipAnimateOut */ null,
+                mStatusBarVisibilityChangeListener);
     }
 
     private String getDumpableName() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
index 1f9ea08..fd7bce0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
@@ -71,49 +71,87 @@
 
     override fun onSystemEventAnimationBegin(): Animator {
         isAnimationRunning = true
-        val moveOut =
-            ValueAnimator.ofFloat(0f, 1f).apply {
-                duration = 23.frames
-                interpolator = STATUS_BAR_X_MOVE_OUT
-                addUpdateListener {
-                    onTranslationXChanged(-(translationXIn * animatedValue as Float))
-                }
-            }
-        val alphaOut =
-            ValueAnimator.ofFloat(1f, 0f).apply {
-                duration = 8.frames
-                interpolator = null
-                addUpdateListener { onAlphaChanged(animatedValue as Float) }
-            }
-
-        val animSet = AnimatorSet()
-        animSet.playTogether(moveOut, alphaOut)
-        return animSet
+        return getDefaultStatusBarAnimationForChipEnter(
+            translationXIn,
+            onTranslationXChanged,
+            onAlphaChanged,
+        )
     }
 
     override fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator {
         onTranslationXChanged(translationXOut.toFloat())
-        val moveIn =
-            ValueAnimator.ofFloat(1f, 0f).apply {
-                duration = 23.frames
-                startDelay = 7.frames
-                interpolator = STATUS_BAR_X_MOVE_IN
-                addUpdateListener {
-                    onTranslationXChanged(translationXOut * animatedValue as Float)
-                }
-            }
-        val alphaIn =
-            ValueAnimator.ofFloat(0f, 1f).apply {
-                duration = 5.frames
-                startDelay = 11.frames
-                interpolator = null
-                addUpdateListener { onAlphaChanged(animatedValue as Float) }
-            }
+        val anim =
+            getDefaultStatusBarAnimationForChipExit(
+                translationXOut,
+                onTranslationXChanged,
+                onAlphaChanged,
+            )
+        anim.doOnEnd { isAnimationRunning = false }
+        anim.doOnCancel { isAnimationRunning = false }
 
-        val animatorSet = AnimatorSet()
-        animatorSet.playTogether(moveIn, alphaIn)
-        animatorSet.doOnEnd { isAnimationRunning = false }
-        animatorSet.doOnCancel { isAnimationRunning = false }
-        return animatorSet
+        return anim
+    }
+
+    /** Static definition of these animations so we can use them more easily from view binders */
+    companion object {
+        /**
+         * Chip: coming in. Animated view: going out.
+         *
+         * Implements the exact spec for animating any status bar elements OUT to make space for the
+         * chip IN animation.
+         */
+        fun getDefaultStatusBarAnimationForChipEnter(
+            targetTranslation: Int,
+            setX: (Float) -> Unit,
+            setAlpha: (Float) -> Unit,
+        ): Animator {
+            val moveOut =
+                ValueAnimator.ofFloat(0f, 1f).apply {
+                    duration = 23.frames
+                    interpolator = STATUS_BAR_X_MOVE_OUT
+                    addUpdateListener { setX(-(targetTranslation * animatedValue as Float)) }
+                }
+            val alphaOut =
+                ValueAnimator.ofFloat(1f, 0f).apply {
+                    duration = 8.frames
+                    interpolator = null
+                    addUpdateListener { setAlpha(animatedValue as Float) }
+                }
+
+            val animSet = AnimatorSet()
+            animSet.playTogether(moveOut, alphaOut)
+            return animSet
+        }
+
+        /**
+         * Chip: going out. Animated view: coming in.
+         *
+         * Implements the exact spec for animating any status bar elements IN as the chip is
+         * animating OUT
+         */
+        fun getDefaultStatusBarAnimationForChipExit(
+            targetTranslation: Int,
+            setX: (Float) -> Unit,
+            setAlpha: (Float) -> Unit,
+        ): Animator {
+            val moveIn =
+                ValueAnimator.ofFloat(1f, 0f).apply {
+                    duration = 23.frames
+                    startDelay = 7.frames
+                    interpolator = STATUS_BAR_X_MOVE_IN
+                    addUpdateListener { setX(targetTranslation * animatedValue as Float) }
+                }
+            val alphaIn =
+                ValueAnimator.ofFloat(0f, 1f).apply {
+                    duration = 5.frames
+                    startDelay = 11.frames
+                    interpolator = null
+                    addUpdateListener { setAlpha(animatedValue as Float) }
+                }
+
+            val animatorSet = AnimatorSet()
+            animatorSet.playTogether(moveIn, alphaIn)
+            return animatorSet
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 935b101..bfdc8bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -23,6 +23,8 @@
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.events.data.repository.SystemStatusEventAnimationRepository
+import com.android.systemui.statusbar.events.data.repository.SystemStatusEventAnimationRepositoryImpl
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
@@ -85,6 +87,11 @@
     abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
 
     @Binds
+    abstract fun systemStatusEventAnimationRepository(
+        impl: SystemStatusEventAnimationRepositoryImpl
+    ): SystemStatusEventAnimationRepository
+
+    @Binds
     abstract fun realDeviceBasedSatelliteRepository(
         impl: DeviceBasedSatelliteRepositoryImpl
     ): RealDeviceBasedSatelliteRepository
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index 2177e02..72df027 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -32,6 +32,10 @@
 import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.core.StatusBarRootModernization
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingIn
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingOut
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.RunningChipAnim
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
@@ -46,10 +50,16 @@
     /**
      * Binds the view to the view-model. [listener] will be notified whenever an event that may
      * change the status bar visibility occurs.
+     *
+     * Null chip animations are used when [StatusBarRootModernization] is off (i.e., when we are
+     * binding from the fragment). If non-null, they control the animation of the system icon area
+     * to support the chip animations.
      */
     fun bind(
         view: View,
         viewModel: HomeStatusBarViewModel,
+        systemEventChipAnimateIn: ((View) -> Unit)?,
+        systemEventChipAnimateOut: ((View) -> Unit)?,
         listener: StatusBarVisibilityChangeListener,
     )
 }
@@ -59,6 +69,8 @@
     override fun bind(
         view: View,
         viewModel: HomeStatusBarViewModel,
+        systemEventChipAnimateIn: ((View) -> Unit)?,
+        systemEventChipAnimateOut: ((View) -> Unit)?,
         listener: StatusBarVisibilityChangeListener,
     ) {
         view.repeatWhenAttached {
@@ -95,6 +107,7 @@
                                 when (primaryChipModel) {
                                     is OngoingActivityChipModel.Shown ->
                                         primaryChipView.show(shouldAnimateChange = true)
+
                                     is OngoingActivityChipModel.Hidden ->
                                         primaryChipView.hide(
                                             state = View.GONE,
@@ -109,6 +122,7 @@
                                             hasSecondaryOngoingActivity = false,
                                             shouldAnimate = true,
                                         )
+
                                     is OngoingActivityChipModel.Hidden ->
                                         listener.onOngoingActivityStatusChanged(
                                             hasPrimaryOngoingActivity = false,
@@ -175,10 +189,30 @@
                         view.requireViewById<View>(R.id.status_bar_end_side_content)
                     // TODO(b/364360986): Also handle operator name view.
                     launch {
-                        viewModel.isSystemInfoVisible.collect {
-                            systemInfoView.adjustVisibility(it)
-                            // TODO(b/364360986): The system info view has a custom alpha controller
-                            // in CollapsedStatusBarFragment.
+                        viewModel.systemInfoCombinedVis.collect { (baseVis, animState) ->
+                            // Broadly speaking, the baseVis controls the view.visibility, and
+                            // the animation state uses only alpha to achieve its effect. This
+                            // means that we can always modify the visibility, and if we're
+                            // animating we can use the animState to handle it. If we are not
+                            // animating, then we can use the baseVis default animation
+                            if (animState.isAnimatingChip()) {
+                                // Just apply the visibility of the view, but don't animate
+                                systemInfoView.visibility = baseVis.visibility
+                                // Now apply the animation state, with its animator
+                                when (animState) {
+                                    AnimatingIn -> {
+                                        systemEventChipAnimateIn?.invoke(systemInfoView)
+                                    }
+                                    AnimatingOut -> {
+                                        systemEventChipAnimateOut?.invoke(systemInfoView)
+                                    }
+                                    else -> {
+                                        // Nothing to do here
+                                    }
+                                }
+                            } else {
+                                systemInfoView.adjustVisibility(baseVis)
+                            }
                         }
                     }
                 }
@@ -186,6 +220,14 @@
         }
     }
 
+    private fun SystemEventAnimationState.isAnimatingChip() =
+        when (this) {
+            AnimatingIn,
+            AnimatingOut,
+            RunningChipAnim -> true
+            else -> false
+        }
+
     private fun OngoingActivityChipModel.toVisibilityModel(): VisibilityModel {
         return VisibilityModel(
             visibility = if (this is OngoingActivityChipModel.Shown) View.VISIBLE else View.GONE,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index a9c2f72..1faa9f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore
+import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
 import com.android.systemui.statusbar.phone.NotificationIconContainer
 import com.android.systemui.statusbar.phone.PhoneStatusBarView
@@ -59,6 +60,7 @@
     private val iconController: StatusBarIconController,
     private val ongoingCallController: OngoingCallController,
     private val darkIconDispatcherStore: DarkIconDispatcherStore,
+    private val eventAnimationInteractor: SystemStatusEventAnimationInteractor,
 ) {
     fun create(root: ViewGroup, andThen: (ViewGroup) -> Unit): ComposeView {
         val composeView = ComposeView(root.context)
@@ -73,6 +75,7 @@
                     iconController = iconController,
                     ongoingCallController = ongoingCallController,
                     darkIconDispatcher = darkIconDispatcherStore.forDisplay(root.context.displayId),
+                    eventAnimationInteractor = eventAnimationInteractor,
                     onViewCreated = andThen,
                 )
             }
@@ -102,6 +105,7 @@
     iconController: StatusBarIconController,
     ongoingCallController: OngoingCallController,
     darkIconDispatcher: DarkIconDispatcher,
+    eventAnimationInteractor: SystemStatusEventAnimationInteractor,
     onViewCreated: (ViewGroup) -> Unit,
 ) {
     // None of these methods are used when [StatusBarRootModernization] is on.
@@ -181,6 +185,8 @@
                         statusBarViewBinder.bind(
                             phoneStatusBarView,
                             statusBarViewModel,
+                            eventAnimationInteractor::animateStatusBarContentForChipEnter,
+                            eventAnimationInteractor::animateStatusBarContentForChipExit,
                             nopVisibilityChangeListener,
                         )
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index 4277a8b..6a9b43c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -35,6 +35,9 @@
 import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
+import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor
@@ -95,7 +98,12 @@
 
     val isClockVisible: Flow<VisibilityModel>
     val isNotificationIconContainerVisible: Flow<VisibilityModel>
-    val isSystemInfoVisible: Flow<VisibilityModel>
+    /**
+     * Pair of (system info visibility, event animation state). The animation state can be used to
+     * respond to the system event chip animations. In all cases, system info visibility correctly
+     * models the View.visibility for the system info area
+     */
+    val systemInfoCombinedVis: StateFlow<SystemInfoCombinedVisibilityModel>
 
     /**
      * Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where
@@ -114,6 +122,12 @@
         /** True if a visibility change should be animated. */
         val shouldAnimateChange: Boolean,
     )
+
+    /** The combined visibility + animation state for the system info status bar area */
+    data class SystemInfoCombinedVisibilityModel(
+        val baseVisibility: VisibilityModel,
+        val animationState: SystemEventAnimationState,
+    )
 }
 
 @SysUISingleton
@@ -129,6 +143,7 @@
     sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor,
     shadeInteractor: ShadeInteractor,
     ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
+    animations: SystemStatusEventAnimationInteractor,
     @Application coroutineScope: CoroutineScope,
 ) : HomeStatusBarViewModel {
     override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> =
@@ -228,7 +243,7 @@
                 visibilityViaDisableFlags.animate,
             )
         }
-    override val isSystemInfoVisible: Flow<VisibilityModel> =
+    private val isSystemInfoVisible =
         combine(
             shouldHomeStatusBarBeVisible,
             collapsedStatusBarInteractor.visibilityViaDisableFlags,
@@ -238,6 +253,22 @@
             VisibilityModel(showSystemInfo.toVisibilityInt(), visibilityViaDisableFlags.animate)
         }
 
+    override val systemInfoCombinedVis =
+        combine(isSystemInfoVisible, animations.animationState) { sysInfoVisible, animationState ->
+                HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel(
+                    sysInfoVisible,
+                    animationState,
+                )
+            }
+            .stateIn(
+                coroutineScope,
+                SharingStarted.WhileSubscribed(),
+                HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel(
+                    VisibilityModel(View.VISIBLE, false),
+                    Idle,
+                ),
+            )
+
     @View.Visibility
     private fun Boolean.toVisibilityInt(): Int {
         return if (this) View.VISIBLE else View.GONE
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java
deleted file mode 100644
index 0d36b48..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
-import android.text.format.DateFormat;
-import android.util.AttributeSet;
-import android.widget.LinearLayout;
-import android.widget.TextClock;
-
-import com.android.systemui.res.R;
-
-/**
- * Container for a clock which has two separate views for the clock itself and AM/PM indicator. This
- * is used to scale the clock independently of AM/PM.
- */
-public class SplitClockView extends LinearLayout {
-
-    private TextClock mTimeView;
-    private TextClock mAmPmView;
-
-    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (Intent.ACTION_TIME_CHANGED.equals(action)
-                    || Intent.ACTION_TIMEZONE_CHANGED.equals(action)
-                    || Intent.ACTION_LOCALE_CHANGED.equals(action)
-                    || Intent.ACTION_CONFIGURATION_CHANGED.equals(action)
-                    || Intent.ACTION_USER_SWITCHED.equals(action)) {
-                updatePatterns();
-            }
-        }
-    };
-
-    public SplitClockView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mTimeView = findViewById(R.id.time_view);
-        mAmPmView = findViewById(R.id.am_pm_view);
-        mTimeView.setShowCurrentUserTime(true);
-        mAmPmView.setShowCurrentUserTime(true);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_TIME_CHANGED);
-        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
-        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
-        filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
-        getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
-
-        updatePatterns();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        getContext().unregisterReceiver(mIntentReceiver);
-    }
-
-    private void updatePatterns() {
-        String formatString = DateFormat.getTimeFormatString(getContext(),
-                ActivityManager.getCurrentUser());
-        int index = getAmPmPartEndIndex(formatString);
-        String timeString;
-        String amPmString;
-        if (index == -1) {
-            timeString = formatString;
-            amPmString = "";
-        } else {
-            timeString = formatString.substring(0, index);
-            amPmString = formatString.substring(index);
-        }
-        mTimeView.setFormat12Hour(timeString);
-        mTimeView.setFormat24Hour(timeString);
-        mTimeView.setContentDescriptionFormat12Hour(formatString);
-        mTimeView.setContentDescriptionFormat24Hour(formatString);
-        mAmPmView.setFormat12Hour(amPmString);
-        mAmPmView.setFormat24Hour(amPmString);
-    }
-
-    /**
-     * @return the index where the AM/PM part starts at the end in {@code formatString} including
-     *         leading white spaces or {@code -1} if no AM/PM part is found or {@code formatString}
-     *         doesn't end with AM/PM part
-     */
-    private static int getAmPmPartEndIndex(String formatString) {
-        boolean hasAmPm = false;
-        int length = formatString.length();
-        for (int i = length - 1; i >= 0; i--) {
-            char c = formatString.charAt(i);
-            boolean isAmPm = c == 'a';
-            boolean isWhitespace = Character.isWhitespace(c);
-            if (isAmPm) {
-                hasAmPm = true;
-            }
-            if (isAmPm || isWhitespace) {
-                continue;
-            }
-            if (i == length - 1) {
-
-                // First character was not AM/PM and not whitespace, so it's not ending with AM/PM.
-                return -1;
-            } else {
-
-                // If we have AM/PM at all, return last index, or -1 to indicate that it's not
-                // ending with AM/PM.
-                return hasAmPm ? i + 1 : -1;
-            }
-        }
-
-        // Only AM/PM and whitespaces? The whole string is AM/PM. Else: Only whitespaces in the
-        // string.
-        return hasAmPm ? 0 : -1;
-    }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
index b79d39e..36d64a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
@@ -39,7 +39,7 @@
 import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController;
 
 /**
- * Status bar view.
+ * Status bar view
  * We now extend WindowRootView so that we can host Compose views
  */
 public class StatusBarWindowView extends FrameLayout {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index 3c06828..2a9b1b9 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Tracing
 import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.util.settings.SettingsSingleThreadBackground
 import dagger.Module
 import dagger.Provides
 import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
index 4fc9a7c..5c0cc81 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -18,9 +18,13 @@
 
 import android.app.Dialog
 import android.content.Context
+import android.graphics.PixelFormat
 import android.os.Bundle
 import android.view.MotionEvent
+import android.view.ViewGroup
+import android.view.WindowManager
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
 import com.android.systemui.volume.Events
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.ui.binder.VolumeDialogViewBinder
@@ -32,10 +36,34 @@
     @Application context: Context,
     private val viewBinder: VolumeDialogViewBinder,
     private val visibilityInteractor: VolumeDialogVisibilityInteractor,
-) : Dialog(context) {
+) : Dialog(context, R.style.Theme_SystemUI_Dialog_Volume) {
+
+    init {
+        with(window!!) {
+            addFlags(
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+                    WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+                    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+            )
+            addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+
+            setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
+            setWindowAnimations(-1)
+            setFormat(PixelFormat.TRANSLUCENT)
+
+            attributes =
+                attributes.apply {
+                    title = "VolumeDialog" // Not the same as Window#setTitle
+                }
+            setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
+        }
+        setCanceledOnTouchOutside(true)
+    }
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+        setContentView(R.layout.volume_dialog)
         viewBinder.bind(this)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index 78eabb2..d9a945c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -17,21 +17,22 @@
 package com.android.systemui.volume.dialog.ui.binder
 
 import android.app.Dialog
-import android.graphics.Color
-import android.graphics.PixelFormat
-import android.graphics.drawable.ColorDrawable
+import android.graphics.Rect
+import android.graphics.Region
 import android.view.Gravity
 import android.view.View
 import android.view.ViewGroup
-import android.view.Window
-import android.view.WindowManager
+import android.view.ViewTreeObserver
+import android.view.ViewTreeObserver.InternalInsetsInfo
+import android.widget.FrameLayout
+import androidx.annotation.GravityInt
 import com.android.internal.view.RotationPolicy
 import com.android.systemui.lifecycle.WindowLifecycleState
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.lifecycle.viewModel
 import com.android.systemui.res.R
+import com.android.systemui.util.children
 import com.android.systemui.volume.SystemUIInterpolators
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerViewBinder
 import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder
@@ -52,6 +53,8 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
 
 /** Binds the root view of the Volume Dialog. */
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -61,65 +64,41 @@
 constructor(
     private val volumeResources: VolumeDialogResources,
     private val gravityViewModel: VolumeDialogGravityViewModel,
-    private val viewModelFactory: VolumeDialogViewModel.Factory,
+    private val dialogViewModelFactory: VolumeDialogViewModel.Factory,
     private val jankListenerFactory: JankListenerFactory,
     private val tracer: VolumeTracer,
-    @VolumeDialog private val coroutineScope: CoroutineScope,
     private val volumeDialogRingerViewBinder: VolumeDialogRingerViewBinder,
     private val slidersViewBinder: VolumeDialogSlidersViewBinder,
     private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder,
 ) {
 
     fun bind(dialog: Dialog) {
-        setupDialog(dialog)
-        val view: View = dialog.requireViewById(R.id.volume_dialog_container)
-        view.alpha = 0f
-        view.repeatWhenAttached {
-            view.viewModel(
+        // Root view of the Volume Dialog.
+        val root: ViewGroup = dialog.requireViewById(R.id.volume_dialog_root)
+        // Volume Dialog container view that contains the dialog itself without the floating sliders
+        val container: View = root.requireViewById(R.id.volume_dialog_container)
+        container.alpha = 0f
+        container.repeatWhenAttached {
+            root.viewModel(
                 traceName = "VolumeDialogViewBinder",
                 minWindowLifecycleState = WindowLifecycleState.ATTACHED,
-                factory = { viewModelFactory.create() },
+                factory = { dialogViewModelFactory.create() },
             ) { viewModel ->
-                viewModel.dialogTitle.onEach { dialog.window?.setTitle(it) }.launchIn(this)
+                animateVisibility(container, dialog, viewModel.dialogVisibilityModel)
 
-                animateVisibility(view, dialog, viewModel.dialogVisibilityModel)
+                viewModel.dialogTitle.onEach { dialog.window?.setTitle(it) }.launchIn(this)
+                gravityViewModel.dialogGravity
+                    .onEach { container.setLayoutGravity(it) }
+                    .launchIn(this)
+
+                launch { root.viewTreeObserver.computeInternalInsetsListener(root) }
 
                 awaitCancellation()
             }
         }
-        volumeDialogRingerViewBinder.bind(view)
-        slidersViewBinder.bind(view)
-        settingsButtonViewBinder.bind(view)
-    }
-
-    /** Configures [Window] for the [Dialog]. */
-    private fun setupDialog(dialog: Dialog) {
-        with(dialog.window!!) {
-            clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
-            addFlags(
-                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
-                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
-                    WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
-                    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
-            )
-            addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
-
-            requestFeature(Window.FEATURE_NO_TITLE)
-            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
-            setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
-            setWindowAnimations(-1)
-            setFormat(PixelFormat.TRANSLUCENT)
-
-            attributes =
-                attributes.apply {
-                    title = "VolumeDialog" // Not the same as Window#setTitle
-                }
-            setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
-
-            gravityViewModel.dialogGravity.onEach { setGravity(it) }.launchIn(coroutineScope)
-        }
-        dialog.setContentView(R.layout.volume_dialog)
-        dialog.setCanceledOnTouchOutside(true)
+        volumeDialogRingerViewBinder.bind(root)
+        slidersViewBinder.bind(root)
+        settingsButtonViewBinder.bind(root)
     }
 
     private fun CoroutineScope.animateVisibility(
@@ -209,4 +188,33 @@
         }
         animator.suspendAnimate(jankListenerFactory.dismiss(this, duration))
     }
+
+    private suspend fun ViewTreeObserver.computeInternalInsetsListener(viewGroup: ViewGroup) =
+        suspendCancellableCoroutine<Unit> { continuation ->
+            val listener =
+                ViewTreeObserver.OnComputeInternalInsetsListener { inoutInfo ->
+                    viewGroup.fillTouchableBounds(inoutInfo)
+                }
+            addOnComputeInternalInsetsListener(listener)
+            continuation.invokeOnCancellation { removeOnComputeInternalInsetsListener(listener) }
+        }
+
+    private fun ViewGroup.fillTouchableBounds(internalInsetsInfo: InternalInsetsInfo) {
+        for (child in children) {
+            val boundsRect = Rect()
+            internalInsetsInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION)
+
+            child.getBoundsInWindow(boundsRect, false)
+            internalInsetsInfo.touchableRegion.op(boundsRect, Region.Op.UNION)
+        }
+        val boundsRect = Rect()
+        getBoundsInWindow(boundsRect, false)
+    }
+
+    private fun View.setLayoutGravity(@GravityInt newGravity: Int) {
+        val frameLayoutParams =
+            layoutParams as? FrameLayout.LayoutParams
+                ?: error("View must be a child of a FrameLayout")
+        layoutParams = frameLayoutParams.apply { gravity = newGravity }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index 2ff8cbc..5bf1513 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -78,8 +78,7 @@
     fun testAvailableMediaDeviceItemFactory_createFromCachedDevice() {
         `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
         `when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
-        `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
-            .thenReturn(Pair.create(drawable, ""))
+        `when`(cachedDevice.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
         val deviceItem = availableMediaDeviceItemFactory.create(context, cachedDevice)
 
         assertDeviceItem(deviceItem, DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
@@ -89,8 +88,7 @@
     fun testConnectedDeviceItemFactory_createFromCachedDevice() {
         `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
         `when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
-        `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
-            .thenReturn(Pair.create(drawable, ""))
+        `when`(cachedDevice.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
         val deviceItem = connectedDeviceItemFactory.create(context, cachedDevice)
 
         assertDeviceItem(deviceItem, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
@@ -100,8 +98,7 @@
     fun testSavedDeviceItemFactory_createFromCachedDevice() {
         `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
         `when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
-        `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
-            .thenReturn(Pair.create(drawable, ""))
+        `when`(cachedDevice.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
         val deviceItem = savedDeviceItemFactory.create(context, cachedDevice)
 
         assertDeviceItem(deviceItem, DeviceItemType.SAVED_BLUETOOTH_DEVICE)
@@ -111,8 +108,7 @@
     @Test
     fun testAvailableAudioSharingMediaDeviceItemFactory_createFromCachedDevice() {
         `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
-        `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
-            .thenReturn(Pair.create(drawable, ""))
+        `when`(cachedDevice.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
         val deviceItem =
             AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
                 .create(context, cachedDevice)
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 fc318d5..856333e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -49,6 +49,7 @@
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -2507,17 +2508,54 @@
 
     @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
     @Test
-    public void testEventLogging_bubbleBar_dragBubbleToDismiss() {
+    public void testEventLogging_bubbleBar_dragSelectedBubbleToDismiss() {
         mBubbleProperties.mIsBubbleBarEnabled = true;
         mPositioner.setIsLargeScreen(true);
         FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
         mBubbleController.registerBubbleStateListener(bubbleStateListener);
 
         mEntryListener.onEntryAdded(mRow);
-        mBubbleController.dragBubbleToDismiss(mRow.getKey(), 1L);
+        mEntryListener.onEntryAdded(mRow2);
+        mBubbleController.expandStackAndSelectBubbleFromLauncher(mRow2.getKey(), 0);
 
+        clearInvocations(mBubbleLogger);
+
+        // Dismiss selected bubble
+        mBubbleController.startBubbleDrag(mRow2.getKey());
+        mBubbleController.dragBubbleToDismiss(mRow2.getKey(), System.currentTimeMillis());
+
+        // Log bubble dismissed via drag and new bubble selected
+        verify(mBubbleLogger).log(eqBubbleWithKey(mRow2.getKey()),
+                eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_BUBBLE));
+        verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()),
+                eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED));
+
+        verifyNoMoreInteractions(mBubbleLogger);
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void testEventLogging_bubbleBar_dragOtherBubbleToDismiss() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        mEntryListener.onEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow2);
+        mBubbleController.expandStackAndSelectBubbleFromLauncher(mRow2.getKey(), 0);
+
+        clearInvocations(mBubbleLogger);
+
+        // Dismiss other bubble
+        mBubbleController.startBubbleDrag(mRow.getKey());
+        mBubbleController.dragBubbleToDismiss(mRow.getKey(), System.currentTimeMillis());
+
+        // Log bubble dismissed via drag, but no switch event
         verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()),
                 eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_BUBBLE));
+
+        verifyNoMoreInteractions(mBubbleLogger);
     }
 
     @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@@ -2643,6 +2681,32 @@
                 eq(BubbleLogger.Event.BUBBLE_BAR_EXPANDED));
     }
 
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void testEventLogging_bubbleBar_switchBubble() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        mEntryListener.onEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow2);
+        mBubbleController.expandStackAndSelectBubbleFromLauncher(mRow.getKey(), 0);
+
+        // First select is expand
+        verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()),
+                eq(BubbleLogger.Event.BUBBLE_BAR_EXPANDED));
+        verify(mBubbleLogger, never()).log(eqBubbleWithKey(mRow.getKey()),
+                eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED));
+
+        // Second select is switch
+        mBubbleController.expandStackAndSelectBubbleFromLauncher(mRow2.getKey(), 0);
+        verify(mBubbleLogger).log(eqBubbleWithKey(mRow2.getKey()),
+                eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED));
+        verify(mBubbleLogger, never()).log(eqBubbleWithKey(mRow2.getKey()),
+                eq(BubbleLogger.Event.BUBBLE_BAR_EXPANDED));
+    }
+
     /** 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/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index 8022e6e..f52f039 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -22,7 +22,9 @@
 import android.hardware.input.fakeInputManager
 import android.view.windowManager
 import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.repository.CustomShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.repository.DefaultShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesUtils
 import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
 import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperTestHelper
 import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource
@@ -40,6 +42,7 @@
 import com.android.systemui.keyguard.data.repository.fakeCommandQueue
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.model.sysUiState
@@ -73,10 +76,18 @@
 var Kosmos.shortcutHelperCurrentAppShortcutsSource: KeyboardShortcutGroupsSource by
     Kosmos.Fixture { CurrentAppShortcutsSource(windowManager) }
 
-val Kosmos.shortcutHelperCategoriesRepository by
+val Kosmos.shortcutCategoriesUtils by
     Kosmos.Fixture {
-        ShortcutHelperCategoriesRepository(
+        ShortcutCategoriesUtils(
             applicationContext,
+            backgroundCoroutineContext,
+            fakeInputManager.inputManager,
+        )
+    }
+
+val Kosmos.defaultShortcutCategoriesRepository by
+    Kosmos.Fixture {
+        DefaultShortcutCategoriesRepository(
             applicationCoroutineScope,
             testDispatcher,
             shortcutHelperSystemShortcutsSource,
@@ -86,6 +97,18 @@
             shortcutHelperCurrentAppShortcutsSource,
             fakeInputManager.inputManager,
             shortcutHelperStateRepository,
+            shortcutCategoriesUtils,
+        )
+    }
+
+val Kosmos.customShortcutCategoriesRepository by
+    Kosmos.Fixture {
+        CustomShortcutCategoriesRepository(
+            shortcutHelperStateRepository,
+            userTracker,
+            applicationCoroutineScope,
+            testDispatcher,
+            shortcutCategoriesUtils,
         )
     }
 
@@ -112,7 +135,7 @@
     }
 
 val Kosmos.shortcutHelperCategoriesInteractor by
-    Kosmos.Fixture { ShortcutHelperCategoriesInteractor(shortcutHelperCategoriesRepository) }
+    Kosmos.Fixture { ShortcutHelperCategoriesInteractor(defaultShortcutCategoriesRepository) }
 
 val Kosmos.shortcutHelperViewModel by
     Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/data/repository/SystemStatusEventAnimationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/data/repository/SystemStatusEventAnimationRepositoryKosmos.kt
new file mode 100644
index 0000000..92eeef9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/data/repository/SystemStatusEventAnimationRepositoryKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
+import kotlinx.coroutines.flow.MutableStateFlow
+
+val Kosmos.systemStatusEventAnimationRepository: FakeSystemStatusEventAnimationRepository by
+    Kosmos.Fixture { FakeSystemStatusEventAnimationRepository() }
+
+class FakeSystemStatusEventAnimationRepository : SystemStatusEventAnimationRepository {
+    override val animationState = MutableStateFlow(SystemEventAnimationState.Idle)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/domain/interactor/SystemStatusEventAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/domain/interactor/SystemStatusEventAnimationInteractorKosmos.kt
new file mode 100644
index 0000000..7513fea
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/domain/interactor/SystemStatusEventAnimationInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.events.domain.interactor
+
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.events.data.repository.systemStatusEventAnimationRepository
+
+val Kosmos.systemStatusEventAnimationInteractor by
+    Kosmos.Fixture {
+        SystemStatusEventAnimationInteractor(
+            repo = systemStatusEventAnimationRepository,
+            configurationInteractor = configurationInteractor,
+            scope = applicationCoroutineScope,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index 32c582f..2ec8016 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -19,6 +19,7 @@
 import android.app.PendingIntent
 import android.graphics.drawable.Icon
 import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
 import com.android.systemui.statusbar.notification.shared.CallType
 import com.android.systemui.statusbar.notification.stack.BUCKET_UNKNOWN
@@ -46,6 +47,7 @@
     contentIntent: PendingIntent? = null,
     bucket: Int = BUCKET_UNKNOWN,
     callType: CallType = CallType.None,
+    promotedContent: PromotedNotificationContentModel? = null,
 ) =
     ActiveNotificationModel(
         key = key,
@@ -69,4 +71,5 @@
         isGroupSummary = isGroupSummary,
         bucket = bucket,
         callType = callType,
+        promotedContent = promotedContent,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index 3a7ada2..03e4c89 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
+import com.android.systemui.statusbar.events.domain.interactor.systemStatusEventAnimationInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.phone.domain.interactor.lightsOutInteractor
 import com.android.systemui.statusbar.pipeline.shared.domain.interactor.collapsedStatusBarInteractor
@@ -40,6 +41,7 @@
             sceneContainerOcclusionInteractor,
             shadeInteractor,
             ongoingActivityChipsViewModel,
+            systemStatusEventAnimationInteractor,
             applicationCoroutineScope,
         )
     }
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh b/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
index 3726ca9..b389a67 100755
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
@@ -30,7 +30,7 @@
 EOF
 }
 
-source "${0%/*}"/../../common.sh
+source "${0%/*}"/../common.sh
 
 SCRIPT_NAME="${0##*/}"
 
@@ -61,7 +61,6 @@
 done
 shift $(($OPTIND - 1))
 
-
 # Build the dump files, which are the input of this test.
 run m  dump-jar tiny-framework-dump-test
 
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
index 44ae1d1..81e83b5 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
@@ -16,7 +16,6 @@
 
 package com.android.server.appfunctions;
 
-import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
@@ -24,15 +23,21 @@
 /** Executors for App function operations. */
 public final class AppFunctionExecutors {
 
+    static final int sConcurrency = Runtime.getRuntime().availableProcessors();
+
     /** Executor for operations that do not need to block. */
-    public static final Executor THREAD_POOL_EXECUTOR =
+    public static final ThreadPoolExecutor THREAD_POOL_EXECUTOR =
             new ThreadPoolExecutor(
-                    /* corePoolSize= */ Runtime.getRuntime().availableProcessors(),
-                    /* maxConcurrency= */ Runtime.getRuntime().availableProcessors(),
-                    /* keepAliveTime= */ 0L,
+                    /* corePoolSize= */ sConcurrency,
+                    /* maxConcurrency= */ sConcurrency,
+                    /* keepAliveTime= */ 1L,
                     /* unit= */ TimeUnit.SECONDS,
                     /* workQueue= */ new LinkedBlockingQueue<>(),
                     new NamedThreadFactory("AppFunctionExecutors"));
 
+    static {
+        THREAD_POOL_EXECUTOR.allowCoreThreadTimeOut(true);
+    }
+
     private AppFunctionExecutors() {}
 }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 0bd879b..827e3ef 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -607,7 +607,8 @@
                     // ... and see if these are hosts we've been awaiting.
                     // NOTE: We are backing up and restoring only the owner.
                     // TODO: http://b/22388012
-                    if (newPackageAdded && userId == mUserManager.getMainUser().getIdentifier()) {
+                    UserHandle mainUser = mUserManager.getMainUser();
+                    if (newPackageAdded && mainUser != null && userId == mainUser.getIdentifier()) {
                         final int uid = getUidForPackage(pkgName, userId);
                         if (uid >= 0 ) {
                             resolveHostUidLocked(pkgName, uid);
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 1dc3b73..bd46deb 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -22,3 +22,11 @@
     description: "Guards against Autofill-Credman Phase1 developer integration via new APIs"
     bug: "320730001"
 }
+
+flag {
+    name: "fill_dialog_improvements"
+    is_exported: true
+    namespace: "autofill"
+    description: "Improvements for Fill Dialog, including deprecation of pre-trigger API's"
+    bug: "336223371"
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index cb89f28..dfddc08 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -668,6 +668,8 @@
      */
     private static final boolean ENABLE_PROC_LOCK = true;
 
+    private static final int DEFAULT_INTENT_CREATOR_UID = -1;
+
     /**
      * The lock for process management.
      *
@@ -19308,22 +19310,36 @@
         if (!preventIntentRedirect()) return;
 
         if (intent == null) return;
+
+        String targetPackage = intent.getComponent() != null
+                ? intent.getComponent().getPackageName()
+                : intent.getPackage();
+        final boolean isCreatorSameAsTarget = creatorPackage != null && creatorPackage.equals(
+                targetPackage);
+        final boolean noExtraIntentKeys =
+                intent.getExtraIntentKeys() == null || intent.getExtraIntentKeys().isEmpty();
+        final int creatorUid = noExtraIntentKeys ? DEFAULT_INTENT_CREATOR_UID : Binder.getCallingUid();
+
         intent.forEachNestedCreatorToken(extraIntent -> {
-            IntentCreatorToken creatorToken = createIntentCreatorToken(extraIntent, creatorPackage);
+            if (isCreatorSameAsTarget) {
+                FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED, creatorUid, true);
+                return;
+            }
+            IntentCreatorToken creatorToken = createIntentCreatorToken(extraIntent, creatorUid,
+                    creatorPackage);
             if (creatorToken != null) {
                 extraIntent.setCreatorToken(creatorToken);
                 // TODO remove Slog.wtf once proven FrameworkStatsLog works. b/375396329
                 Slog.wtf(TAG, "A creator token is added to an intent. creatorPackage: "
                         + creatorPackage + "; intent: " + extraIntent);
-                FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED,
-                        creatorToken.getCreatorUid());
+                FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED, creatorUid, false);
             }
         });
     }
 
-    private IntentCreatorToken createIntentCreatorToken(Intent intent, String creatorPackage) {
+    private IntentCreatorToken createIntentCreatorToken(Intent intent, int creatorUid,
+            String creatorPackage) {
         if (IntentCreatorToken.isValid(intent)) return null;
-        int creatorUid = getCallingUid();
         IntentCreatorToken.Key key = new IntentCreatorToken.Key(creatorUid, creatorPackage, intent);
         IntentCreatorToken token;
         synchronized (sIntentCreatorTokenCache) {
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index 8a12858..446b367 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -57,7 +57,6 @@
 import android.app.ApplicationThreadConstants;
 import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
-import android.app.BroadcastStickyCache;
 import android.app.IApplicationThread;
 import android.app.compat.CompatChanges;
 import android.appwidget.AppWidgetManager;
@@ -702,7 +701,6 @@
             boolean serialized, boolean sticky, int userId) {
         mService.enforceNotIsolatedCaller("broadcastIntent");
 
-        int result;
         synchronized (mService) {
             intent = verifyBroadcastLocked(intent);
 
@@ -724,7 +722,7 @@
 
             final long origId = Binder.clearCallingIdentity();
             try {
-                result = broadcastIntentLocked(callerApp,
+                return broadcastIntentLocked(callerApp,
                         callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
                         intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
                         resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
@@ -735,10 +733,6 @@
                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
-        if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
-            BroadcastStickyCache.incrementVersion(intent.getAction());
-        }
-        return result;
     }
 
     // Not the binder call surface
@@ -749,7 +743,6 @@
             boolean serialized, boolean sticky, int userId,
             BackgroundStartPrivileges backgroundStartPrivileges,
             @Nullable int[] broadcastAllowList) {
-        int result;
         synchronized (mService) {
             intent = verifyBroadcastLocked(intent);
 
@@ -757,7 +750,7 @@
             String[] requiredPermissions = requiredPermission == null ? null
                     : new String[] {requiredPermission};
             try {
-                result = broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
+                return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
                         resultToApp, resultTo, resultCode, resultData, resultExtras,
                         requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
                         uid, realCallingUid, realCallingPid, userId,
@@ -767,10 +760,6 @@
                 Binder.restoreCallingIdentity(origId);
             }
         }
-        if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
-            BroadcastStickyCache.incrementVersion(intent.getAction());
-        }
-        return result;
     }
 
     @GuardedBy("mService")
@@ -1469,7 +1458,6 @@
                     list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
                             callingUid, callerAppProcessState, resolvedType));
                 }
-                BroadcastStickyCache.incrementVersion(intent.getAction());
             }
         }
 
@@ -1736,7 +1724,6 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
-        final ArrayList<String> changedStickyBroadcasts = new ArrayList<>();
         synchronized (mStickyBroadcasts) {
             ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
             if (stickies != null) {
@@ -1753,16 +1740,12 @@
                     if (list.size() <= 0) {
                         stickies.remove(intent.getAction());
                     }
-                    changedStickyBroadcasts.add(intent.getAction());
                 }
                 if (stickies.size() <= 0) {
                     mStickyBroadcasts.remove(userId);
                 }
             }
         }
-        for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
-            BroadcastStickyCache.incrementVersionIfExists(changedStickyBroadcasts.get(i));
-        }
     }
 
     void finishReceiver(IBinder caller, int resultCode, String resultData,
@@ -1925,9 +1908,7 @@
 
     private void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
         mService.mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
-    }
-
-    private List<ResolveInfo> collectReceiverComponents(
+    }private List<ResolveInfo> collectReceiverComponents(
             Intent intent, String resolvedType, int callingUid, int callingPid,
             int[] users, int[] broadcastAllowList) {
         // TODO: come back and remove this assumption to triage all broadcasts
@@ -2143,18 +2124,9 @@
     }
 
     void removeStickyBroadcasts(int userId) {
-        final ArrayList<String> changedStickyBroadcasts = new ArrayList<>();
         synchronized (mStickyBroadcasts) {
-            final ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
-                    mStickyBroadcasts.get(userId);
-            if (stickies != null) {
-                changedStickyBroadcasts.addAll(stickies.keySet());
-            }
             mStickyBroadcasts.remove(userId);
         }
-        for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
-            BroadcastStickyCache.incrementVersionIfExists(changedStickyBroadcasts.get(i));
-        }
     }
 
     @NeverCompile
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 416c110..da5b1fd 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -101,7 +101,7 @@
 import java.util.Set;
 import java.util.concurrent.Executor;
 
-public final class CachedAppOptimizer {
+public class CachedAppOptimizer {
 
     // Flags stored in the DeviceConfig API.
     @VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction";
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index c067662..b84bf6b9 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL_IMPLICIT;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_BFSL;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_CPU_TIME;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
@@ -155,6 +156,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -469,7 +471,6 @@
             }
             Process.setThreadPriority(tid, priority);
         }
-
     }
 
     // TODO(b/346822474): hook up global state usage.
@@ -499,7 +500,8 @@
     }
 
     OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids,
-            ServiceThread adjusterThread, GlobalState globalState, Injector injector) {
+            ServiceThread adjusterThread, GlobalState globalState,
+            CachedAppOptimizer cachedAppOptimizer, Injector injector) {
         mService = service;
         mGlobalState = globalState;
         mInjector = injector;
@@ -508,7 +510,7 @@
         mActiveUids = activeUids;
 
         mConstants = mService.mConstants;
-        mCachedAppOptimizer = new CachedAppOptimizer(mService);
+        mCachedAppOptimizer = cachedAppOptimizer;
         mCacheOomRanker = new CacheOomRanker(service);
 
         mLogger = new OomAdjusterDebugLogger(this, mService.mConstants);
@@ -2597,6 +2599,7 @@
         }
 
         capability |= getDefaultCapability(app, procState);
+        capability |= getCpuCapability(app, now);
 
         // Procstates below BFGS should never have this capability.
         if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
@@ -2739,8 +2742,12 @@
             if (app.mOptRecord.setShouldNotFreeze(true, dryRun,
                     app.mOptRecord.shouldNotFreezeReason()
                     | client.mOptRecord.shouldNotFreezeReason(), mAdjSeq)) {
-                // Bail out early, as we only care about the return value for a dryrun.
-                return true;
+                if (Flags.useCpuTimeCapability()) {
+                    // Do nothing, capability updated check will handle the dryrun output.
+                } else {
+                    // Bail out early, as we only care about the return value for a dryrun.
+                    return true;
+                }
             }
         }
 
@@ -2751,6 +2758,8 @@
         // we check the final procstate, and remove it if the procsate is below BFGS.
         capability |= getBfslCapabilityFromClient(client);
 
+        capability |= getCpuCapabilityFromClient(client);
+
         if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) {
             if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
                 capability |= cstate.getCurCapability();
@@ -2809,9 +2818,14 @@
                             app.mOptRecord.shouldNotFreezeReason()
                             | ProcessCachedOptimizerRecord
                             .SHOULD_NOT_FREEZE_REASON_BINDER_ALLOW_OOM_MANAGEMENT, mAdjSeq)) {
-                        // Bail out early, as we only care about the return value for a dryrun.
-                        return true;
+                        if (Flags.useCpuTimeCapability()) {
+                            // Do nothing, capability updated check will handle the dryrun output.
+                        } else {
+                            // Bail out early, as we only care about the return value for a dryrun.
+                            return true;
+                        }
                     }
+                    capability |= PROCESS_CAPABILITY_CPU_TIME;
                 }
                 // Not doing bind OOM management, so treat
                 // this guy more like a started service.
@@ -3053,9 +3067,14 @@
                         app.mOptRecord.shouldNotFreezeReason()
                         | ProcessCachedOptimizerRecord
                         .SHOULD_NOT_FREEZE_REASON_BIND_WAIVE_PRIORITY, mAdjSeq)) {
-                    // Bail out early, as we only care about the return value for a dryrun.
-                    return true;
+                    if (Flags.useCpuTimeCapability()) {
+                        // Do nothing, capability updated check will handle the dryrun output.
+                    } else {
+                        // Bail out early, as we only care about the return value for a dryrun.
+                        return true;
+                    }
                 }
+                capability |= PROCESS_CAPABILITY_CPU_TIME;
             }
         }
         if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
@@ -3108,9 +3127,24 @@
             capability &= ~PROCESS_CAPABILITY_BFSL;
         }
         if (!updated) {
-            updated = adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup
-                || (capability != prevCapability
-                        && (capability & prevCapability) == prevCapability);
+            if (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup) {
+                updated = true;
+            }
+
+            if (Flags.useCpuTimeCapability()) {
+                if ((capability != prevCapability)
+                        && ((capability & prevCapability) == prevCapability)) {
+                    updated = true;
+                }
+            } else {
+                // Ignore PROCESS_CAPABILITY_CPU_TIME in capability comparison
+                final int curFiltered = capability & ~PROCESS_CAPABILITY_CPU_TIME;
+                final int prevFiltered = prevCapability & ~PROCESS_CAPABILITY_CPU_TIME;
+                if ((curFiltered != prevFiltered)
+                        && ((curFiltered & prevFiltered) == prevFiltered)) {
+                    updated = true;
+                }
+            }
         }
 
         if (dryRun) {
@@ -3186,6 +3220,8 @@
         // we check the final procstate, and remove it if the procsate is below BFGS.
         capability |= getBfslCapabilityFromClient(client);
 
+        capability |= getCpuCapabilityFromClient(client);
+
         if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
             // If the other app is cached for any reason, for purposes here
             // we are going to consider it empty.
@@ -3196,8 +3232,12 @@
             if (app.mOptRecord.setShouldNotFreeze(true, dryRun,
                     app.mOptRecord.shouldNotFreezeReason()
                     | client.mOptRecord.shouldNotFreezeReason(), mAdjSeq)) {
-                // Bail out early, as we only care about the return value for a dryrun.
-                return true;
+                if (Flags.useCpuTimeCapability()) {
+                    // Do nothing, capability updated check will handle the dryrun output.
+                } else {
+                    // Bail out early, as we only care about the return value for a dryrun.
+                    return true;
+                }
             }
         }
 
@@ -3273,10 +3313,25 @@
             capability &= ~PROCESS_CAPABILITY_BFSL;
         }
 
-        if (dryRun && (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup
-                || (capability != prevCapability
-                        && (capability & prevCapability) == prevCapability))) {
-            return true;
+        if (dryRun) {
+            if (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup) {
+                return true;
+            }
+
+            if (Flags.useCpuTimeCapability()) {
+                if ((capability != prevCapability)
+                        && ((capability & prevCapability) == prevCapability)) {
+                    return true;
+                }
+            } else {
+                // Ignore PROCESS_CAPABILITY_CPU_TIME in capability comparison
+                final int curFiltered = capability & ~PROCESS_CAPABILITY_CPU_TIME;
+                final int prevFiltered = prevCapability & ~PROCESS_CAPABILITY_CPU_TIME;
+                if ((curFiltered != prevFiltered)
+                        && ((curFiltered & prevFiltered) == prevFiltered)) {
+                    return true;
+                }
+            }
         }
 
         if (adj < prevRawAdj) {
@@ -3328,6 +3383,29 @@
         return baseCapabilities | networkCapabilities;
     }
 
+    private static int getCpuCapability(ProcessRecord app, long nowUptime) {
+        final UidRecord uidRec = app.getUidRecord();
+        if (uidRec != null && uidRec.isCurAllowListed()) {
+            // Process has user visible activities.
+            return PROCESS_CAPABILITY_CPU_TIME;
+        }
+        if (UserHandle.isCore(app.uid)) {
+            // Make sure all system components are not frozen.
+            return PROCESS_CAPABILITY_CPU_TIME;
+        }
+        if (app.mState.getCachedHasVisibleActivities()) {
+            // Process has user visible activities.
+            return PROCESS_CAPABILITY_CPU_TIME;
+        }
+        if (app.mServices.hasUndemotedShortForegroundService(nowUptime)) {
+            // It running a short fgs, just give it cpu time.
+            return PROCESS_CAPABILITY_CPU_TIME;
+        }
+        // TODO(b/370817323): Populate this method with all of the reasons to keep a process
+        //  unfrozen.
+        return 0;
+    }
+
     /**
      * @return the BFSL capability from a client (of a service binding or provider).
      */
@@ -3376,6 +3454,15 @@
     }
 
     /**
+     * @return the CPU capability from a client (of a service binding or provider).
+     */
+    private static int getCpuCapabilityFromClient(ProcessRecord client) {
+        // Just grant CPU capability every time
+        // TODO(b/370817323): Populate with reasons to not propagate cpu capability across bindings.
+        return client.mState.getCurCapability() & PROCESS_CAPABILITY_CPU_TIME;
+    }
+
+    /**
      * Checks if for the given app and client, there's a cycle that should skip over the client
      * for now or use partial values to evaluate the effect of the client binding.
      * @param app
@@ -3955,6 +4042,39 @@
         mCacheOomRanker.dump(pw);
     }
 
+    /**
+     * Return whether or not a process should be frozen.
+     */
+    boolean getFreezePolicy(ProcessRecord proc) {
+        // Reasons to not freeze:
+        if (Flags.useCpuTimeCapability()) {
+            if ((proc.mState.getCurCapability() & PROCESS_CAPABILITY_CPU_TIME) != 0) {
+                /// App is important enough (see {@link #getCpuCapability}) or bound by something
+                /// important enough to not be frozen.
+                return false;
+            }
+        } else {
+            // The CPU capability handling covers all setShouldNotFreeze paths. Must check
+            // shouldNotFreeze, if the CPU capability is not being used.
+            if (proc.mOptRecord.shouldNotFreeze()) {
+                return false;
+            }
+        }
+
+        if (proc.mOptRecord.isFreezeExempt()) {
+            return false;
+        }
+
+        // Reasons to freeze:
+        if (proc.mState.getCurAdj() >= FREEZER_CUTOFF_ADJ) {
+            // Oomscore is in a high enough state, it is safe to freeze.
+            return true;
+        }
+
+        // Default, do not freeze a process.
+        return false;
+    }
+
     @GuardedBy({"mService", "mProcLock"})
     void updateAppFreezeStateLSP(ProcessRecord app, @OomAdjReason int oomAdjReason,
             boolean immediate, int oldOomAdj) {
@@ -3969,43 +4089,44 @@
                     (state.getCurAdj() >= FREEZER_CUTOFF_ADJ ^ oldOomAdj >= FREEZER_CUTOFF_ADJ)
                     || oldOomAdj == UNKNOWN_ADJ;
             final boolean shouldNotFreezeChanged = opt.shouldNotFreezeAdjSeq() == mAdjSeq;
-            if ((oomAdjChanged || shouldNotFreezeChanged)
+            final boolean hasCpuCapability =
+                    (PROCESS_CAPABILITY_CPU_TIME & app.mState.getCurCapability())
+                            == PROCESS_CAPABILITY_CPU_TIME;
+            final boolean usedToHaveCpuCapability =
+                    (PROCESS_CAPABILITY_CPU_TIME & app.mState.getSetCapability())
+                            == PROCESS_CAPABILITY_CPU_TIME;
+            final boolean cpuCapabilityChanged = hasCpuCapability != usedToHaveCpuCapability;
+            if ((oomAdjChanged || shouldNotFreezeChanged || cpuCapabilityChanged)
                     && Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                 Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                         CachedAppOptimizer.ATRACE_FREEZER_TRACK,
                         "updateAppFreezeStateLSP " + app.processName
+                        + " pid: " + app.getPid()
                         + " isFreezeExempt: " + opt.isFreezeExempt()
                         + " isFrozen: " + opt.isFrozen()
                         + " shouldNotFreeze: " + opt.shouldNotFreeze()
                         + " shouldNotFreezeReason: " + opt.shouldNotFreezeReason()
                         + " curAdj: " + state.getCurAdj()
                         + " oldOomAdj: " + oldOomAdj
-                        + " immediate: " + immediate);
+                        + " immediate: " + immediate
+                        + " cpuCapability: " + hasCpuCapability);
             }
         }
 
-        if (app.mOptRecord.isFreezeExempt()) {
-            return;
-        }
-
-        // if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
-        if (opt.isFrozen() && opt.shouldNotFreeze()) {
-            mCachedAppOptimizer.unfreezeAppLSP(app,
-                    CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
-            return;
-        }
-
-        // Use current adjustment when freezing, set adjustment when unfreezing.
-        if (state.getCurAdj() >= FREEZER_CUTOFF_ADJ && !opt.isFrozen()
-                && !opt.shouldNotFreeze()) {
-            if (!immediate) {
-                mCachedAppOptimizer.freezeAppAsyncLSP(app);
-            } else {
+        if (getFreezePolicy(app)) {
+            // This process should be frozen.
+            if (immediate && !opt.isFrozen()) {
+                // And it will be frozen immediately.
                 mCachedAppOptimizer.freezeAppAsyncAtEarliestLSP(app);
+            } else if (!opt.isFrozen() || !opt.isPendingFreeze()) {
+                mCachedAppOptimizer.freezeAppAsyncLSP(app);
             }
-        } else if (state.getSetAdj() < FREEZER_CUTOFF_ADJ) {
-            mCachedAppOptimizer.unfreezeAppLSP(app,
-                    CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
+        } else {
+            // This process should not be frozen.
+            if (opt.isFrozen() || opt.isPendingFreeze()) {
+                mCachedAppOptimizer.unfreezeAppLSP(app,
+                        CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
+            }
         }
     }
 
@@ -4029,7 +4150,8 @@
         final int size = processes.size();
         for (int i = 0; i < size; i++) {
             ProcessRecord proc = processes.get(i);
-            mCachedAppOptimizer.unfreezeTemporarily(proc, reason);
+            mCachedAppOptimizer.unfreezeTemporarily(proc,
+                    CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(reason));
         }
         processes.clear();
     }
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 8b66055..1b7e8f0 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -758,8 +758,9 @@
 
     OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
             ActiveUids activeUids, ServiceThread adjusterThread, GlobalState globalState,
-            Injector injector) {
-        super(service, processList, activeUids, adjusterThread, globalState, injector);
+            CachedAppOptimizer cachedAppOptimizer, Injector injector) {
+        super(service, processList, activeUids, adjusterThread, globalState, cachedAppOptimizer,
+                injector);
     }
 
     private final ProcessRecordNodes mProcessRecordProcStateNodes = new ProcessRecordNodes(
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 5cb8b95..3644974 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -256,18 +256,24 @@
         }
         // Now we need to look at all short-FGS within the process and see if all of them are
         // procstate-timed-out or not.
+        return !hasUndemotedShortForegroundService(nowUptime);
+    }
+
+    boolean hasUndemotedShortForegroundService(long nowUptime) {
         for (int i = mServices.size() - 1; i >= 0; i--) {
             final ServiceRecord sr = mServices.valueAt(i);
             if (!sr.isShortFgs() || !sr.hasShortFgsInfo()) {
                 continue;
             }
             if (sr.getShortFgsInfo().getProcStateDemoteTime() >= nowUptime) {
-                return false;
+                // This short fgs has not timed out yet.
+                return true;
             }
         }
-        return true;
+        return false;
     }
 
+
     int getReportedForegroundServiceTypes() {
         return mRepFgServiceTypes;
     }
diff --git a/services/core/java/com/android/server/am/ProcessStateController.java b/services/core/java/com/android/server/am/ProcessStateController.java
index 01468c6..5789922 100644
--- a/services/core/java/com/android/server/am/ProcessStateController.java
+++ b/services/core/java/com/android/server/am/ProcessStateController.java
@@ -29,6 +29,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.ServiceThread;
 
 /**
@@ -44,13 +45,14 @@
     private final GlobalState mGlobalState = new GlobalState();
 
     private ProcessStateController(ActivityManagerService ams, ProcessList processList,
-            ActiveUids activeUids, ServiceThread handlerThread, OomAdjuster.Injector oomAdjInjector,
+            ActiveUids activeUids, ServiceThread handlerThread,
+            CachedAppOptimizer cachedAppOptimizer, OomAdjuster.Injector oomAdjInjector,
             boolean useOomAdjusterModernImpl) {
         mOomAdjuster = useOomAdjusterModernImpl
                 ? new OomAdjusterModernImpl(ams, processList, activeUids, handlerThread,
-                mGlobalState, oomAdjInjector)
+                mGlobalState, cachedAppOptimizer, oomAdjInjector)
                 : new OomAdjuster(ams, processList, activeUids, handlerThread, mGlobalState,
-                        oomAdjInjector);
+                        cachedAppOptimizer, oomAdjInjector);
     }
 
     /**
@@ -594,6 +596,7 @@
         private final ActiveUids mActiveUids;
 
         private ServiceThread mHandlerThread = null;
+        private CachedAppOptimizer mCachedAppOptimizer = null;
         private OomAdjuster.Injector mOomAdjInjector = null;
         private boolean mUseOomAdjusterModernImpl = false;
 
@@ -610,24 +613,38 @@
             if (mHandlerThread == null) {
                 mHandlerThread = OomAdjuster.createAdjusterThread();
             }
+            if (mCachedAppOptimizer == null) {
+                mCachedAppOptimizer = new CachedAppOptimizer(mAms);
+            }
             if (mOomAdjInjector == null) {
                 mOomAdjInjector = new OomAdjuster.Injector();
             }
             return new ProcessStateController(mAms, mProcessList, mActiveUids, mHandlerThread,
-                    mOomAdjInjector, mUseOomAdjusterModernImpl);
+                    mCachedAppOptimizer, mOomAdjInjector, mUseOomAdjusterModernImpl);
         }
 
         /**
          * For Testing Purposes. Set what thread OomAdjuster will offload tasks on to.
          */
+        @VisibleForTesting
         public Builder setHandlerThread(ServiceThread handlerThread) {
             mHandlerThread = handlerThread;
             return this;
         }
 
         /**
+         * For Testing Purposes. Set the CachedAppOptimzer used by OomAdjuster.
+         */
+        @VisibleForTesting
+        public Builder setCachedAppOptimizer(CachedAppOptimizer cachedAppOptimizer) {
+            mCachedAppOptimizer = cachedAppOptimizer;
+            return this;
+        }
+
+        /**
          * For Testing Purposes. Set an injector for OomAdjuster.
          */
+        @VisibleForTesting
         public Builder setOomAdjusterInjector(OomAdjuster.Injector injector) {
             mOomAdjInjector = injector;
             return this;
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 3dd5ec9..ef5296e 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -221,6 +221,7 @@
         "preload_safety",
         "printing",
         "privacy_infra_policy",
+        "psap_ai",
         "ravenwood",
         "resource_manager",
         "responsible_apis",
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 8395685..711b163 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -253,3 +253,10 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "use_cpu_time_capability"
+    namespace: "backstage_power"
+    description: "Use PROCESS_CAPABILITY_CPU_TIME to control unfreeze state."
+    bug: "370817323"
+}
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index e8fa417..afdc0c0 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -119,9 +119,16 @@
                 == BiometricManager.Authenticators.IDENTITY_CHECK;
         boolean isMandatoryBiometricsAuthentication = false;
 
+        final int effectiveUserId;
+        if (Flags.effectiveUserBp()) {
+            effectiveUserId = userManager.getCredentialOwnerProfile(userId);
+        } else {
+            effectiveUserId = userId;
+        }
+
         if (dropCredentialFallback(promptInfo.getAuthenticators(),
                 settingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
-                        userId), trustManager)) {
+                        effectiveUserId), trustManager)) {
             isMandatoryBiometricsAuthentication = true;
             promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
             if (promptInfo.getNegativeButtonText() == null) {
@@ -145,13 +152,6 @@
         final List<BiometricSensor> eligibleSensors = new ArrayList<>();
         final List<Pair<BiometricSensor, Integer>> ineligibleSensors = new ArrayList<>();
 
-        final int effectiveUserId;
-        if (Flags.effectiveUserBp()) {
-            effectiveUserId = userManager.getCredentialOwnerProfile(userId);
-        } else {
-            effectiveUserId = userId;
-        }
-
         if (biometricRequested) {
             for (BiometricSensor sensor : sensors) {
 
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 97f4a5c..eeac260 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -32,6 +32,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.net.Uri;
 import android.os.Binder;
@@ -75,6 +76,7 @@
     private final ChangeReporter mChangeReporter;
     private final CompatConfig mCompatConfig;
     private final AndroidBuildClassifier mBuildClassifier;
+    private Boolean mIsWear;
 
     public PlatformCompat(Context context) {
         super(PermissionEnforcer.fromContext(context));
@@ -511,9 +513,16 @@
     }
 
     private ApplicationInfo fixTargetSdk(ApplicationInfo appInfo, int uid) {
+
+        // mIsWear doesn't need to be locked, ok if executes twice
+        if (mIsWear == null) {
+            mIsWear = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+        }
+
         // b/282922910 - we don't want apps sharing system uid and targeting
         // older target sdk to impact all system uid apps
-        if (Flags.systemUidTargetSystemSdk() && uid == Process.SYSTEM_UID) {
+        if (Flags.systemUidTargetSystemSdk() && !mIsWear &&
+                uid == Process.SYSTEM_UID) {
             appInfo.targetSdkVersion = Build.VERSION.SDK_INT;
         }
         return appInfo;
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index 0124e25..bb0b190 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -21,8 +21,6 @@
 import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE;
 
 import static com.android.hardware.input.Flags.enableNew25q2Keycodes;
-import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
-import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
 
 import android.annotation.BinderThread;
 import android.annotation.MainThread;
@@ -51,7 +49,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.IndentingPrintWriter;
@@ -215,7 +212,7 @@
     }
 
     private void initKeyCombinationRules() {
-        if (!useKeyGestureEventHandler() || !useKeyGestureEventHandlerMultiPressGestures()) {
+        if (!InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()) {
             return;
         }
         // TODO(b/358569822): Handle Power, Back key properly since key combination gesture is
@@ -441,7 +438,8 @@
 
     public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
         final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
-        if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
+        if (InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()
+                && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
             return mKeyCombinationManager.interceptKey(event, interactive);
         }
         return false;
@@ -457,15 +455,18 @@
         final long keyConsumed = -1;
         final long keyNotConsumed = 0;
 
-        if (mKeyCombinationManager.isKeyConsumed(event)) {
-            return keyConsumed;
-        }
+        if (InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()) {
+            if (mKeyCombinationManager.isKeyConsumed(event)) {
+                return keyConsumed;
+            }
 
-        if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
-            final long now = SystemClock.uptimeMillis();
-            final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout(keyCode);
-            if (now < interceptTimeout) {
-                return interceptTimeout - now;
+            if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
+                final long now = SystemClock.uptimeMillis();
+                final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout(
+                        keyCode);
+                if (now < interceptTimeout) {
+                    return interceptTimeout - now;
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index c3205af..0defd27 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -20,6 +20,7 @@
 import android.annotation.BinderThread;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Color;
 import android.hardware.input.IKeyboardBacklightListener;
 import android.hardware.input.IKeyboardBacklightState;
@@ -81,9 +82,6 @@
     private static final String UEVENT_KEYBOARD_BACKLIGHT_TAG = "kbd_backlight";
 
     @VisibleForTesting
-    static final long USER_INACTIVITY_THRESHOLD_MILLIS = Duration.ofSeconds(30).toMillis();
-
-    @VisibleForTesting
     static final int[] DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL =
             new int[DEFAULT_NUM_BRIGHTNESS_CHANGE_STEPS + 1];
 
@@ -112,6 +110,7 @@
     private AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener mAmbientListener;
 
     private int mAmbientBacklightValue = 0;
+    private final int mUserInactivityThresholdMs;
 
     static {
         // Fixed brightness levels to avoid issues when converting back and forth from the
@@ -139,6 +138,9 @@
         mAnimatorFactory = animatorFactory;
         mAmbientController = new AmbientKeyboardBacklightController(context, looper);
         mUEventManager = uEventManager;
+        Resources res = mContext.getResources();
+        mUserInactivityThresholdMs = res.getInteger(
+                com.android.internal.R.integer.config_keyboardBacklightTimeoutMs);
     }
 
     @Override
@@ -300,7 +302,7 @@
         }
         mHandler.removeMessages(MSG_NOTIFY_USER_INACTIVITY);
         mHandler.sendEmptyMessageAtTime(MSG_NOTIFY_USER_INACTIVITY,
-                SystemClock.uptimeMillis() + USER_INACTIVITY_THRESHOLD_MILLIS);
+                SystemClock.uptimeMillis() + mUserInactivityThresholdMs);
     }
 
     private void handleUserInactivity() {
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index bb4ae96..a132876b 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -46,7 +46,6 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
-import com.android.server.integrity.model.RuleMetadata;
 
 import java.io.File;
 import java.nio.charset.StandardCharsets;
diff --git a/services/core/java/com/android/server/integrity/model/BitInputStream.java b/services/core/java/com/android/server/integrity/model/BitInputStream.java
deleted file mode 100644
index e7cc81e..0000000
--- a/services/core/java/com/android/server/integrity/model/BitInputStream.java
+++ /dev/null
@@ -1,72 +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.model;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/** A wrapper class for reading a stream of bits.
- *
- * <p>Note: this class reads from underlying stream byte-by-byte. It is advised to apply buffering
- * to underlying streams.
- */
-public class BitInputStream {
-
-    private long mBitsRead;
-
-    private InputStream mInputStream;
-
-    private byte mCurrentByte;
-
-    public BitInputStream(InputStream inputStream) {
-        mInputStream = inputStream;
-    }
-
-    /**
-     * Read the next number of bits from the stream.
-     *
-     * @param numOfBits The number of bits to read.
-     * @return The value read from the stream.
-     */
-    public int getNext(int numOfBits) throws IOException {
-        int component = 0;
-        int count = 0;
-
-        while (count++ < numOfBits) {
-            if (mBitsRead % 8 == 0) {
-                mCurrentByte = getNextByte();
-            }
-            int offset = 7 - (int) (mBitsRead % 8);
-
-            component <<= 1;
-            component |= (mCurrentByte >>> offset) & 1;
-
-            mBitsRead++;
-        }
-
-        return component;
-    }
-
-    /** Check if there are bits left in the stream. */
-    public boolean hasNext() throws IOException {
-        return mInputStream.available() > 0;
-    }
-
-    private byte getNextByte() throws IOException {
-        return (byte) mInputStream.read();
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/model/BitOutputStream.java b/services/core/java/com/android/server/integrity/model/BitOutputStream.java
deleted file mode 100644
index 14b35fd..0000000
--- a/services/core/java/com/android/server/integrity/model/BitOutputStream.java
+++ /dev/null
@@ -1,105 +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.model;
-
-import static com.android.server.integrity.model.ComponentBitSize.BYTE_BITS;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.Arrays;
-
-/** A wrapper class for writing a stream of bits. */
-public class BitOutputStream {
-
-    private static final int BUFFER_SIZE = 4 * 1024;
-
-    private int mNextBitIndex;
-
-    private final OutputStream mOutputStream;
-    private final byte[] mBuffer;
-
-    public BitOutputStream(OutputStream outputStream) {
-        mBuffer = new byte[BUFFER_SIZE];
-        mNextBitIndex = 0;
-        mOutputStream = outputStream;
-    }
-
-    /**
-     * Set the next number of bits in the stream to value.
-     *
-     * @param numOfBits The number of bits used to represent the value.
-     * @param value The value to convert to bits.
-     */
-    public void setNext(int numOfBits, int value) throws IOException {
-        if (numOfBits <= 0) {
-            return;
-        }
-
-        // optional: we can do some clever size checking to "OR" an entire segment of bits instead
-        // of setting bits one by one, but it is probably not worth it.
-        int nextBitMask = 1 << (numOfBits - 1);
-        while (numOfBits-- > 0) {
-            setNext((value & nextBitMask) != 0);
-            nextBitMask >>>= 1;
-        }
-    }
-
-    /**
-     * Set the next bit in the stream to value.
-     *
-     * @param value The value to set the bit to
-     */
-    public void setNext(boolean value) throws IOException {
-        int byteToWrite = mNextBitIndex / BYTE_BITS;
-        if (byteToWrite == BUFFER_SIZE) {
-            mOutputStream.write(mBuffer);
-            reset();
-            byteToWrite = 0;
-        }
-        if (value) {
-            mBuffer[byteToWrite] |= 1 << (BYTE_BITS - 1 - (mNextBitIndex % BYTE_BITS));
-        }
-        mNextBitIndex++;
-    }
-
-    /** Set the next bit in the stream to true. */
-    public void setNext() throws IOException {
-        setNext(/* value= */ true);
-    }
-
-    /**
-     * Flush the data written to the underlying {@link java.io.OutputStream}. Any unfinished bytes
-     * will be padded with 0.
-     */
-    public void flush() throws IOException {
-        int endByte = mNextBitIndex / BYTE_BITS;
-        if (mNextBitIndex % BYTE_BITS != 0) {
-            // If next bit is not the first bit of a byte, then mNextBitIndex / BYTE_BITS would be
-            // the byte that includes already written bits. We need to increment it so this byte
-            // gets written.
-            endByte++;
-        }
-        mOutputStream.write(mBuffer, 0, endByte);
-        reset();
-    }
-
-    /** Reset this output stream to start state. */
-    private void reset() {
-        mNextBitIndex = 0;
-        Arrays.fill(mBuffer, (byte) 0);
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
deleted file mode 100644
index ceed054..0000000
--- a/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2020 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.model;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * An output stream that tracks the total number written bytes since construction and allows
- * querying this value any time during the execution.
- *
- * <p>This class is used for constructing the rule indexing.
- */
-public class ByteTrackedOutputStream extends OutputStream {
-
-    private int mWrittenBytesCount;
-    private final OutputStream mOutputStream;
-
-    public ByteTrackedOutputStream(OutputStream outputStream) {
-        mWrittenBytesCount = 0;
-        mOutputStream = outputStream;
-    }
-
-    @Override
-    public void write(int b) throws IOException {
-        mWrittenBytesCount++;
-        mOutputStream.write(b);
-    }
-
-    /**
-     * Writes the given bytes into the output stream provided in constructor and updates the total
-     * number of written bytes.
-     */
-    @Override
-    public void write(byte[] bytes) throws IOException {
-        write(bytes, 0, bytes.length);
-    }
-
-    @Override
-    public void write(byte[] b, int off, int len) throws IOException {
-        mWrittenBytesCount += len;
-        mOutputStream.write(b, off, len);
-    }
-
-    /** Returns the total number of bytes written into the output stream at the requested time. */
-    public int getWrittenBytesCount() {
-        return mWrittenBytesCount;
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java
deleted file mode 100644
index 94e6708..0000000
--- a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java
+++ /dev/null
@@ -1,45 +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.model;
-
-import android.content.integrity.Rule;
-
-/**
- * A helper class containing information about the binary representation of different {@link Rule}
- * components.
- */
-public final class ComponentBitSize {
-    public static final int FORMAT_VERSION_BITS = 8;
-
-    public static final int EFFECT_BITS = 3;
-    public static final int KEY_BITS = 4;
-    public static final int OPERATOR_BITS = 3;
-    public static final int CONNECTOR_BITS = 2;
-    public static final int SEPARATOR_BITS = 3;
-    public static final int VALUE_SIZE_BITS = 8;
-    public static final int IS_HASHED_BITS = 1;
-
-    public static final int ATOMIC_FORMULA_START = 0;
-    public static final int COMPOUND_FORMULA_START = 1;
-    public static final int COMPOUND_FORMULA_END = 2;
-    public static final int INSTALLER_ALLOWED_BY_MANIFEST_START = 3;
-
-    public static final int DEFAULT_FORMAT_VERSION = 1;
-    public static final int SIGNAL_BIT = 1;
-
-    public static final int BYTE_BITS = 8;
-}
diff --git a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
deleted file mode 100644
index 0c4052a..0000000
--- a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2020 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.model;
-
-/**  A helper class containing special indexing file constants. */
-public final class IndexingFileConstants {
-    // We empirically experimented with different block sizes and identified that 50 is in the
-    // optimal range of efficient computation.
-    public static final int INDEXING_BLOCK_SIZE = 50;
-
-    public static final String START_INDEXING_KEY = "START_KEY";
-    public static final String END_INDEXING_KEY = "END_KEY";
-}
diff --git a/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java b/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java
deleted file mode 100644
index b0647fc..0000000
--- a/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java
+++ /dev/null
@@ -1,93 +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.model;
-
-import android.annotation.Nullable;
-import android.content.integrity.Rule;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A class encapsulating the result from the evaluation engine after evaluating rules against app
- * install metadata.
- *
- * <p>It contains the outcome effect (whether to allow or block the install), and the rule causing
- * that effect.
- */
-public final class IntegrityCheckResult {
-
-    public enum Effect {
-        ALLOW,
-        DENY
-    }
-
-    private final Effect mEffect;
-    private final List<Rule> mRuleList;
-
-    private IntegrityCheckResult(Effect effect, @Nullable List<Rule> ruleList) {
-        this.mEffect = effect;
-        this.mRuleList = ruleList;
-    }
-
-    public Effect getEffect() {
-        return mEffect;
-    }
-
-    public List<Rule> getMatchedRules() {
-        return mRuleList;
-    }
-
-    /**
-     * Create an ALLOW evaluation outcome.
-     *
-     * @return An evaluation outcome with ALLOW effect and no rule.
-     */
-    public static IntegrityCheckResult allow() {
-        return new IntegrityCheckResult(Effect.ALLOW, Collections.emptyList());
-    }
-
-    /**
-     * Create an ALLOW evaluation outcome.
-     *
-     * @return An evaluation outcome with ALLOW effect and rule causing that effect.
-     */
-    public static IntegrityCheckResult allow(List<Rule> ruleList) {
-        return new IntegrityCheckResult(Effect.ALLOW, ruleList);
-    }
-
-    /**
-     * Create a DENY evaluation outcome.
-     *
-     * @param ruleList All valid rules that cause the DENY effect.
-     * @return An evaluation outcome with DENY effect and rule causing that effect.
-     */
-    public static IntegrityCheckResult deny(List<Rule> ruleList) {
-        return new IntegrityCheckResult(Effect.DENY, ruleList);
-    }
-
-    /** Returns true when the {@code mEffect} is caused by an app certificate mismatch. */
-    public boolean isCausedByAppCertRule() {
-        return mRuleList.stream().anyMatch(rule -> rule.getFormula().isAppCertificateFormula());
-    }
-
-    /** Returns true when the {@code mEffect} is caused by an installer rule. */
-    public boolean isCausedByInstallerRule() {
-        return mRuleList.stream().anyMatch(rule -> rule.getFormula().isInstallerFormula());
-    }
-
-}
diff --git a/services/core/java/com/android/server/integrity/model/RuleMetadata.java b/services/core/java/com/android/server/integrity/model/RuleMetadata.java
deleted file mode 100644
index 6b582ae..0000000
--- a/services/core/java/com/android/server/integrity/model/RuleMetadata.java
+++ /dev/null
@@ -1,41 +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.model;
-
-import android.annotation.Nullable;
-
-/** Data class containing relevant metadata associated with a rule set. */
-public class RuleMetadata {
-
-    private final String mRuleProvider;
-    private final String mVersion;
-
-    public RuleMetadata(String ruleProvider, String version) {
-        mRuleProvider = ruleProvider;
-        mVersion = version;
-    }
-
-    @Nullable
-    public String getRuleProvider() {
-        return mRuleProvider;
-    }
-
-    @Nullable
-    public String getVersion() {
-        return mVersion;
-    }
-}
diff --git a/services/core/java/com/android/server/location/contexthub/OWNERS b/services/core/java/com/android/server/location/contexthub/OWNERS
index c62e323..6536ca0 100644
--- a/services/core/java/com/android/server/location/contexthub/OWNERS
+++ b/services/core/java/com/android/server/location/contexthub/OWNERS
@@ -1,3 +1,4 @@
 bduddie@google.com
+arthuri@google.com
 matthewsedam@google.com
 stange@google.com
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityDbHelper.java b/services/core/java/com/android/server/media/quality/MediaQualityDbHelper.java
new file mode 100644
index 0000000..04f6216
--- /dev/null
+++ b/services/core/java/com/android/server/media/quality/MediaQualityDbHelper.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.quality;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.media.quality.MediaQualityContract.BaseParameters;
+
+public class MediaQualityDbHelper extends SQLiteOpenHelper {
+
+    private static final String TAG = "MediaQualityDbHelper";
+
+    static final int DATABASE_VERSION_1 = 1;
+    private static final String DATABASE_NAME = "media_quality.db";
+    public static final String PICTURE_QUALITY_TABLE_NAME = "picture_quality";
+    public static final String SOUND_QUALITY_TABLE_NAME = "sound_quality";
+    public static final String SETTINGS = "settings";
+
+    MediaQualityDbHelper(Context context) {
+        super(context, DATABASE_NAME, null, getDbVersion());
+    }
+
+    private static int getDbVersion() {
+        return DATABASE_VERSION_1;
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase db) {
+        db.execSQL(getTableCreateStatement(PICTURE_QUALITY_TABLE_NAME));
+        db.execSQL(getTableCreateStatement(SOUND_QUALITY_TABLE_NAME));
+    }
+
+    private String getTableCreateStatement(String tableName) {
+        return
+                "CREATE TABLE " + tableName + "("
+                        + BaseParameters.PARAMETER_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+                        + BaseParameters.PARAMETER_TYPE + " INTEGER,"
+                        + BaseParameters.PARAMETER_NAME + " STRING,"
+                        + BaseParameters.PARAMETER_PACKAGE + " STRING,"
+                        + BaseParameters.PARAMETER_INPUT_ID + " STRING,"
+                        + SETTINGS + " TEXT)";
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        // to do
+    }
+
+}
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index 21ae182..c5c8a5e 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -16,20 +16,31 @@
 
 package com.android.server.media.quality;
 
+import android.content.ContentValues;
 import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
 import android.media.quality.AmbientBacklightSettings;
 import android.media.quality.IAmbientBacklightCallback;
 import android.media.quality.IMediaQualityManager;
 import android.media.quality.IPictureProfileCallback;
 import android.media.quality.ISoundProfileCallback;
+import android.media.quality.MediaQualityContract.PictureQuality;
 import android.media.quality.ParamCapability;
 import android.media.quality.PictureProfile;
 import android.media.quality.SoundProfile;
+import android.os.Bundle;
+import android.util.Log;
 
 import com.android.server.SystemService;
 
+import org.json.JSONException;
+import org.json.JSONObject;
+
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * This service manage picture profile and sound profile for TV setting. Also communicates with the
@@ -40,10 +51,14 @@
     private static final boolean DEBUG = false;
     private static final String TAG = "MediaQualityService";
     private final Context mContext;
+    private final MediaQualityDbHelper mMediaQualityDbHelper;
 
     public MediaQualityService(Context context) {
         super(context);
         mContext = context;
+        mMediaQualityDbHelper = new MediaQualityDbHelper(mContext);
+        mMediaQualityDbHelper.setWriteAheadLoggingEnabled(true);
+        mMediaQualityDbHelper.setIdleConnectionTimeout(30);
     }
 
     @Override
@@ -53,11 +68,23 @@
 
     // TODO: Add additional APIs. b/373951081
     private final class BinderService extends IMediaQualityManager.Stub {
+
         @Override
         public PictureProfile createPictureProfile(PictureProfile pp) {
-            // TODO: implement
-            return pp;
+            SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+
+            ContentValues values = new ContentValues();
+            values.put(PictureQuality.PARAMETER_TYPE, pp.getProfileType());
+            values.put(PictureQuality.PARAMETER_NAME, pp.getName());
+            values.put(PictureQuality.PARAMETER_PACKAGE, pp.getPackageName());
+            values.put(PictureQuality.PARAMETER_INPUT_ID, pp.getInputId());
+            values.put(mMediaQualityDbHelper.SETTINGS, bundleToJson(pp.getParameters()));
+
+            // id is auto-generated by SQLite upon successful insertion of row
+            long id = db.insert(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, null, values);
+            return new PictureProfile.Builder(pp).setProfileId(Long.toString(id)).build();
         }
+
         @Override
         public void updatePictureProfile(String id, PictureProfile pp) {
             // TODO: implement
@@ -66,21 +93,159 @@
         public void removePictureProfile(String id) {
             // TODO: implement
         }
+
         @Override
         public PictureProfile getPictureProfile(int type, String name) {
-            return null;
+            SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase();
+
+            String selection = PictureQuality.PARAMETER_TYPE + " = ? AND "
+                    + PictureQuality.PARAMETER_NAME + " = ?";
+            String[] selectionArguments = {Integer.toString(type), name};
+
+            try (
+                    Cursor cursor = db.query(
+                            mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
+                            getAllPictureProfileColumns(),
+                            selection,
+                            selectionArguments,
+                            /*groupBy=*/ null,
+                            /*having=*/ null,
+                            /*orderBy=*/ null)
+            ) {
+                int count = cursor.getCount();
+                if (count == 0) {
+                    return null;
+                }
+                if (count > 1) {
+                    Log.wtf(TAG, String.format(Locale.US, "%d entries found for type=%d and name=%s"
+                                    + " in %s. Should only ever be 0 or 1.", count, type, name,
+                                    mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME));
+                    return null;
+                }
+                cursor.moveToFirst();
+                return getPictureProfileFromCursor(cursor);
+            }
         }
+
+        private String bundleToJson(Bundle bundle) {
+            JSONObject jsonObject = new JSONObject();
+            if (bundle == null) {
+                return jsonObject.toString();
+            }
+            for (String key : bundle.keySet()) {
+                try {
+                    jsonObject.put(key, bundle.getString(key));
+                } catch (JSONException e) {
+                    Log.e(TAG, "Unable to serialize ", e);
+                }
+            }
+            return jsonObject.toString();
+        }
+
+        private Bundle jsonToBundle(String jsonString) {
+            JSONObject jsonObject = null;
+            Bundle bundle = new Bundle();
+
+            try {
+                jsonObject = new JSONObject(jsonString);
+
+                Iterator<String> keys = jsonObject.keys();
+                while (keys.hasNext()) {
+                    String key = keys.next();
+                    Object value = jsonObject.get(key);
+
+                    if (value instanceof String) {
+                        bundle.putString(key, (String) value);
+                    } else if (value instanceof Integer) {
+                        bundle.putInt(key, (Integer) value);
+                    } else if (value instanceof Boolean) {
+                        bundle.putBoolean(key, (Boolean) value);
+                    } else if (value instanceof Double) {
+                        bundle.putDouble(key, (Double) value);
+                    } else if (value instanceof Long) {
+                        bundle.putLong(key, (Long) value);
+                    }
+                }
+            } catch (JSONException e) {
+                throw new RuntimeException(e);
+            }
+
+            return bundle;
+        }
+
+        private String[] getAllPictureProfileColumns() {
+            return new String[]{
+                    PictureQuality.PARAMETER_ID,
+                    PictureQuality.PARAMETER_TYPE,
+                    PictureQuality.PARAMETER_NAME,
+                    PictureQuality.PARAMETER_INPUT_ID,
+                    PictureQuality.PARAMETER_PACKAGE,
+                    mMediaQualityDbHelper.SETTINGS
+            };
+        }
+
+        private PictureProfile getPictureProfileFromCursor(Cursor cursor) {
+            String returnId = cursor.getString(
+                    cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_ID));
+            int type = cursor.getInt(
+                    cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_TYPE));
+            String name = cursor.getString(
+                    cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_NAME));
+            String inputId = cursor.getString(
+                    cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_INPUT_ID));
+            String packageName = cursor.getString(
+                    cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_PACKAGE));
+            String settings = cursor.getString(
+                    cursor.getColumnIndexOrThrow(mMediaQualityDbHelper.SETTINGS));
+            return new PictureProfile(returnId, type, name, inputId,
+                    packageName, jsonToBundle(settings));
+        }
+
         @Override
         public List<PictureProfile> getPictureProfilesByPackage(String packageName) {
-            return new ArrayList<>();
+            String selection = PictureQuality.PARAMETER_PACKAGE + " = ?";
+            String[] selectionArguments = {packageName};
+            return getPictureProfilesBasedOnConditions(getAllPictureProfileColumns(), selection,
+                    selectionArguments);
         }
+
         @Override
         public List<PictureProfile> getAvailablePictureProfiles() {
             return new ArrayList<>();
         }
+
         @Override
         public List<String> getPictureProfilePackageNames() {
-            return new ArrayList<>();
+            String [] column = {PictureQuality.PARAMETER_NAME};
+            List<PictureProfile> pictureProfiles = getPictureProfilesBasedOnConditions(column,
+                    null, null);
+            List<String> packageNames = new ArrayList<>();
+            for (PictureProfile pictureProfile: pictureProfiles) {
+                packageNames.add(pictureProfile.getName());
+            }
+            return packageNames;
+        }
+
+        private List<PictureProfile> getPictureProfilesBasedOnConditions(String[] columns,
+                String selection, String[] selectionArguments) {
+            SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase();
+
+            try (
+                    Cursor cursor = db.query(
+                            mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
+                            columns,
+                            selection,
+                            selectionArguments,
+                            /*groupBy=*/ null,
+                            /*having=*/ null,
+                            /*orderBy=*/ null)
+            ) {
+                List<PictureProfile> pictureProfiles = new ArrayList<>();
+                while (cursor.moveToNext()) {
+                    pictureProfiles.add(getPictureProfileFromCursor(cursor));
+                }
+                return pictureProfiles;
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index d5d4070..52ddb80 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -22,8 +22,11 @@
 import android.app.backup.BackupRestoreEventLogger;
 import android.service.notification.DeviceEffectsApplier;
 
+import com.android.internal.annotations.Keep;
+
 import java.util.Set;
 
+@Keep
 public interface NotificationManagerInternal {
     NotificationChannel getNotificationChannel(String pkg, int uid, String channelId);
     NotificationChannelGroup getNotificationChannelGroup(String pkg, int uid, String channelId);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4d0c7ec..207764b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -211,6 +211,7 @@
 import android.app.RemoteServiceException.BadUserInitiatedJobNotificationException;
 import android.app.StatsManager;
 import android.app.UriGrantsManager;
+import android.app.ZenBypassingApp;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.backup.BackupManager;
 import android.app.backup.BackupRestoreEventLogger;
@@ -238,7 +239,6 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.LauncherApps;
 import android.content.pm.ModuleInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
@@ -3089,7 +3089,7 @@
             migrateDefaultNAS();
             maybeShowInitialReviewPermissionsNotification();
 
-            if (android.app.Flags.modesApi()) {
+            if (android.app.Flags.modesApi() && !mZenModeHelper.hasDeviceEffectsApplier()) {
                 // Cannot be done earlier, as some services aren't ready until this point.
                 mZenModeHelper.setDeviceEffectsApplier(
                         new DefaultDeviceEffectsApplier(getContext()));
@@ -4043,7 +4043,7 @@
                         "canNotifyAsPackage for uid " + uid);
             }
 
-            return areNotificationsEnabledForPackageInt(pkg, uid);
+            return areNotificationsEnabledForPackageInt(uid);
         }
 
         /**
@@ -4864,30 +4864,20 @@
         }
 
         @Override
-        public List<String> getPackagesBypassingDnd(int userId,
-                boolean includeConversationChannels) {
+        public ParceledListSlice<ZenBypassingApp> getPackagesBypassingDnd(int userId)
+                throws RemoteException {
             checkCallerIsSystem();
 
-            final ArraySet<String> packageNames = new ArraySet<>();
-
-            List<PackageInfo> pkgs = mPackageManagerClient.getInstalledPackagesAsUser(0, userId);
-            for (PackageInfo pi : pkgs) {
-                String pkg = pi.packageName;
-                // If any NotificationChannel for this package is bypassing, the
-                // package is considered bypassing.
-                for (NotificationChannel channel : getNotificationChannelsBypassingDnd(pkg,
-                        pi.applicationInfo.uid).getList()) {
-                    // Skips non-demoted conversation channels.
-                    if (!includeConversationChannels
-                            && !TextUtils.isEmpty(channel.getConversationId())
-                            && !channel.isDemoted()) {
-                        continue;
-                    }
-                    packageNames.add(pkg);
+            UserHandle user = UserHandle.of(userId);
+            ArrayList<ZenBypassingApp> bypassing =
+                    mPreferencesHelper.getPackagesBypassingDnd(userId);
+            for (int i = bypassing.size() - 1; i >= 0; i--) {
+                String pkg = bypassing.get(i).getPkg();
+                if (!areNotificationsEnabledForPackage(pkg, getUidForPackageAndUser(pkg, user))) {
+                    bypassing.remove(i);
                 }
             }
-
-            return new ArrayList<String>(packageNames);
+            return new ParceledListSlice<>(bypassing);
         }
 
         @Override
@@ -7763,7 +7753,7 @@
 
         @Override
         public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
-            return areNotificationsEnabledForPackageInt(pkg, uid);
+            return areNotificationsEnabledForPackageInt(uid);
         }
 
         @Override
@@ -8742,7 +8732,7 @@
         }
 
         // blocked apps
-        boolean isBlocked = !areNotificationsEnabledForPackageInt(pkg, uid);
+        boolean isBlocked = !areNotificationsEnabledForPackageInt(uid);
         synchronized (mNotificationLock) {
             isBlocked |= isRecordBlockedLocked(r);
         }
@@ -8792,7 +8782,7 @@
         }
     }
 
-    private boolean areNotificationsEnabledForPackageInt(String pkg, int uid) {
+    private boolean areNotificationsEnabledForPackageInt(int uid) {
         return mPermissionHelper.hasPermission(uid);
     }
 
@@ -9328,7 +9318,7 @@
          * notifying all listeners to a background thread; false otherwise.
          */
         private boolean postNotification() {
-            boolean appBanned = !areNotificationsEnabledForPackageInt(pkg, uid);
+            boolean appBanned = !areNotificationsEnabledForPackageInt(uid);
             boolean isCallNotification = isCallNotification(pkg, uid);
             boolean posted = false;
             synchronized (NotificationManagerService.this.mNotificationLock) {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 9f0b4b0..e6f784c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -57,6 +57,7 @@
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
+import android.app.ZenBypassingApp;
 import android.content.AttributionSource;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -1950,6 +1951,35 @@
     }
 
     /**
+     * Gets all apps that can bypass DND, and a boolean indicating whether all (true) or some
+     * (false) of its notification channels can currently bypass.
+     */
+    public @NonNull ArrayList<ZenBypassingApp> getPackagesBypassingDnd(@UserIdInt int userId) {
+        ArrayList<ZenBypassingApp> bypassing = new ArrayList<>();
+        synchronized (mLock) {
+            for (PackagePreferences p : mPackagePreferences.values()) {
+                if (p.userId != userId) {
+                    continue;
+                }
+                int totalChannelCount = p.channels.size();
+                int bypassingCount = 0;
+                if  (totalChannelCount == 0) {
+                    continue;
+                }
+                for (NotificationChannel channel : p.channels.values()) {
+                    if (channelIsLiveLocked(p, channel) && channel.canBypassDnd()) {
+                        bypassingCount++;
+                    }
+                }
+                if (bypassingCount > 0) {
+                    bypassing.add(new ZenBypassingApp(p.pkg, totalChannelCount == bypassingCount));
+                }
+            }
+        }
+        return bypassing;
+    }
+
+    /**
      * True for pre-O apps that only have the default channel, or pre O apps that have no
      * channels yet. This method will create the default channel for pre-O apps that don't have it.
      * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index cfeacdf..ca4f83f 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -24,6 +24,8 @@
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED;
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_UNKNOWN;
 import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
+import static android.app.backup.NotificationLoggingConstants.ERROR_XML_PARSING;
 import static android.service.notification.Condition.SOURCE_UNKNOWN;
 import static android.service.notification.Condition.SOURCE_USER_ACTION;
 import static android.service.notification.Condition.STATE_FALSE;
@@ -44,8 +46,6 @@
 
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 import static com.android.internal.util.Preconditions.checkArgument;
-import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
-import static android.app.backup.NotificationLoggingConstants.ERROR_XML_PARSING;
 
 import static java.util.Objects.requireNonNull;
 
@@ -303,6 +303,15 @@
     }
 
     /**
+     * @return whether a {@link DeviceEffectsApplier} has already been set or not
+     */
+    boolean hasDeviceEffectsApplier() {
+        synchronized (mConfigLock) {
+            return mDeviceEffectsApplier != null;
+        }
+    }
+
+    /**
      * Set the {@link DeviceEffectsApplier} used to apply the consolidated effects.
      *
      * <p>Previously calculated effects (as loaded from the user's {@link ZenModeConfig}) will be
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 76ea0b9..4690e02 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -880,7 +880,8 @@
                 PackageInstaller.STATUS_PENDING_USER_ACTION);
         broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent);
         broadcastIntent.putExtra(Intent.EXTRA_USER, user);
-        sendIntent(statusReceiver, packageName, /* message= */ "", broadcastIntent);
+        mPm.mHandler.post(
+            () -> sendIntent(statusReceiver, packageName, /* message= */ "", broadcastIntent));
     }
 
     private void verifyUninstallPermissions() {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 5518bfa..1052c94 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1022,7 +1022,7 @@
                 // Close.
                 file.finishWrite(outs);
             } catch (IOException e) {
-                Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
+                Slog.w(TAG, "Failed to write to file " + file.getBaseFile(), e);
                 file.failWrite(outs);
             }
         }
@@ -1055,7 +1055,7 @@
                     final String tag = parser.getName();
                     if (depth == 1) {
                         if (!TAG_ROOT.equals(tag)) {
-                            Slog.e(TAG, "Invalid root tag: " + tag);
+                            Slog.v(TAG, "Invalid root tag: " + tag);
                             return;
                         }
                         continue;
@@ -1066,7 +1066,7 @@
                             mRawLastResetTime.set(parseLongAttribute(parser, ATTR_VALUE));
                             break;
                         default:
-                            Slog.e(TAG, "Invalid tag: " + tag);
+                            Slog.v(TAG, "Invalid tag: " + tag);
                             break;
                     }
                 }
@@ -1113,7 +1113,7 @@
                 // Remove all dangling bitmap files.
                 cleanupDanglingBitmapDirectoriesLocked(userId);
             } catch (XmlPullParserException | IOException e) {
-                Slog.e(TAG, "Failed to write to file " + file, e);
+                Slog.w(TAG, "Failed to write to file " + file, e);
                 file.failWrite(os);
             }
         }
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index acf62dc..09feb18 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -36,6 +36,7 @@
 import android.content.pm.PermissionInfo;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
+import android.health.connect.HealthPermissions;
 import android.media.RingtoneManager;
 import android.media.midi.MidiManager;
 import android.net.Uri;
@@ -48,6 +49,7 @@
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.permission.PermissionManager;
+import android.permission.flags.Flags;
 import android.print.PrintManager;
 import android.provider.CalendarContract;
 import android.provider.ContactsContract;
@@ -64,6 +66,7 @@
 import android.util.Xml;
 
 import com.android.internal.R;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
@@ -213,8 +216,13 @@
 
     private static final Set<String> SENSORS_PERMISSIONS = new ArraySet<>();
     static {
-        SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
-        SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
+        if (Flags.replaceBodySensorPermissionEnabled()) {
+            SENSORS_PERMISSIONS.add(HealthPermissions.READ_HEART_RATE);
+            SENSORS_PERMISSIONS.add(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND);
+        } else {
+            SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
+            SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
+        }
     }
 
     private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
@@ -1632,6 +1640,14 @@
                     continue;
                 }
 
+                // If the trunkstable feature flag is disabled for this
+                // exception, skip the tag.
+                if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(
+                        /* pkg= */ null, parser, /* allowNoNamespace= */ true)) {
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+                }
+
                 final boolean fixed =
                         parser.getAttributeBoolean(null, ATTR_FIXED, false);
                 final boolean whitelisted =
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8d039f1..dda5bcf 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -88,7 +88,6 @@
 import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
 import static com.android.hardware.input.Flags.modifierShortcutDump;
 import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
-import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
 import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
 import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
@@ -2496,7 +2495,7 @@
 
     private void initKeyCombinationRules() {
         mKeyCombinationManager = new KeyCombinationManager(mHandler);
-        if (useKeyGestureEventHandler() && useKeyGestureEventHandlerMultiPressGestures()) {
+        if (InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()) {
             return;
         }
         final boolean screenshotChordEnabled = mContext.getResources().getBoolean(
@@ -3442,7 +3441,7 @@
                             + keyguardOn() + " canceled=" + event.isCanceled());
         }
 
-        if (!useKeyGestureEventHandler()) {
+        if (!InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()) {
             if (mKeyCombinationManager.isKeyConsumed(event)) {
                 return keyConsumed;
             }
@@ -5720,7 +5719,8 @@
     }
 
     private void handleKeyGesture(KeyEvent event, boolean interactive, boolean defaultDisplayOn) {
-        if (mKeyCombinationManager.interceptKey(event, interactive)) {
+        if (!InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()
+                && mKeyCombinationManager.interceptKey(event, interactive)) {
             // handled by combo keys manager.
             mSingleKeyGestureDetector.reset();
             return;
diff --git a/services/core/java/com/android/server/stats/pull/psi/OWNERS b/services/core/java/com/android/server/stats/pull/psi/OWNERS
new file mode 100644
index 0000000..f72fd7c
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/psi/OWNERS
@@ -0,0 +1,9 @@
+jackrichardson@google.com
+dbrotikovskaya@google.com
+ivokay@google.com
+gagapov@google.com
+yigitfiliz@google.com
+rswang@google.com
+evaleriano@google.com
+igorstepanov@google.com
+iyou@google.com
diff --git a/services/core/java/com/android/server/stats/pull/psi/PsiData.java b/services/core/java/com/android/server/stats/pull/psi/PsiData.java
new file mode 100644
index 0000000..d1cbf74
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/psi/PsiData.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull.psi;
+
+/**
+ * Wraps PSI (Pressure Stall Information) corresponding to a system resource. See more details about
+ * PSI, see https://docs.kernel.org/accounting/psi.html#psi-pressure-stall-information.
+ */
+public class PsiData {
+    public enum ResourceType {
+        CPU,
+        MEMORY,
+        IO
+    }
+
+    static class AppsStallInfo {
+
+        /** Past 10s average % of wasted CPU cycles when apps tasks are stalled on mResourceType.*/
+        private final float mAvg10SecPercentage;
+
+        /** Past 60s average % of wasted CPU cycles when apps tasks are stalled on mResourceType.*/
+        private final float mAvg60SecPercentage;
+
+        /** Past 300s average % of wasted CPU cycles when apps tasks are stalled on mResourceType.*/
+        private final float mAvg300SecPercentage;
+
+        /** Total number of microseconds that apps tasks are stalled on mResourceType.*/
+        private final long mTotalUsec;
+
+        AppsStallInfo(
+                float avg10SecPercentage, float avg60SecPercentage,
+                float avg300SecPercentage, long totalUsec) {
+            mAvg10SecPercentage = avg10SecPercentage;
+            mAvg60SecPercentage = avg60SecPercentage;
+            mAvg300SecPercentage = avg300SecPercentage;
+            mTotalUsec = totalUsec;
+        }
+    }
+
+    /** The system resource type of this {@code PsiData}. */
+    private final ResourceType mResourceType;
+
+    /** Info on some tasks are stalled on mResourceType. */
+    private final AppsStallInfo mSomeAppsStallInfo;
+
+    /**
+     * Info on all non-idle tasks are stalled on mResourceType. For the CPU ResourceType,
+     * all fields will always be 0 as it's undefined.
+     */
+    private final AppsStallInfo mFullAppsStallInfo;
+
+    PsiData(
+            ResourceType resourceType,
+            AppsStallInfo someAppsStallInfo,
+            AppsStallInfo fullAppsStallInfo) {
+        mResourceType = resourceType;
+        mSomeAppsStallInfo = someAppsStallInfo;
+        mFullAppsStallInfo = fullAppsStallInfo;
+    }
+
+    public ResourceType getResourceType() {
+        return mResourceType;
+    }
+
+    public float getSomeAvg10SecPercentage() {
+        return mSomeAppsStallInfo.mAvg10SecPercentage; }
+
+    public float getSomeAvg60SecPercentage() {
+        return mSomeAppsStallInfo.mAvg60SecPercentage; }
+
+    public float getSomeAvg300SecPercentage() {
+        return mSomeAppsStallInfo.mAvg300SecPercentage; }
+
+    public long getSomeTotalUsec() {
+        return mSomeAppsStallInfo.mTotalUsec;
+    }
+
+    public float getFullAvg10SecPercentage() {
+        return mFullAppsStallInfo.mAvg10SecPercentage;
+    }
+
+    public float getFullAvg60SecPercentage() {
+        return mFullAppsStallInfo.mAvg60SecPercentage;
+    }
+
+    public float getFullAvg300SecPercentage() {
+        return mFullAppsStallInfo.mAvg300SecPercentage; }
+
+    public long getFullTotalUsec() {
+        return mFullAppsStallInfo.mTotalUsec;
+    }
+}
diff --git a/services/core/java/com/android/server/stats/pull/psi/PsiExtractor.java b/services/core/java/com/android/server/stats/pull/psi/PsiExtractor.java
new file mode 100644
index 0000000..5d0d7e1
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/psi/PsiExtractor.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull.psi;
+
+import static java.util.stream.Collectors.joining;
+
+import android.annotation.Nullable;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.text.MessageFormat;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class PsiExtractor {
+    private static final String TAG = "PsiExtractor";
+
+    // Paths for PSI files are guarded by SELinux policy. PCS needs to be explicitly
+    // allowlisted to access these files.
+    private static final String PSI_MEMORY_PATH = "/proc/pressure/memory";
+    private static final String PSI_IO_PATH = "/proc/pressure/io";
+    private static final String PSI_CPU_PATH = "/proc/pressure/cpu";
+
+    // The patterns matching a line of PSI output such as
+    // "some avg10=0.12 avg60=0.34 avg300=0.56 total=123456" or
+    // "full avg10=0.12 avg60=0.34 avg300=0.56 total=123456" to extract the stalling percentage
+    // values for "some" and "full" line of PSI output respectively.
+    private static final String PSI_PATTERN_TEMPLATE =
+            ".*{0} avg10=(\\d+.\\d+) avg60=(\\d+.\\d+) avg300=(\\d+.\\d+) total=(\\d+).*";
+    private static final String SOME = "some";
+    private static final String FULL = "full";
+    private final PsiReader mPsiReader;
+
+    public PsiExtractor() {
+        mPsiReader = new PsiReader();
+    }
+    public PsiExtractor(PsiReader psiReader) {
+        mPsiReader = psiReader;
+    }
+
+    /**
+    * Parses /pressure/proc/{resourceType} kernel file to extract the Pressure Stall Information
+    * (PSI), more information: can be found at https://docs.kernel.org/accounting/psi.html.
+    *
+    * @param resourceType (Memory/CPU/IO) to get the PSI for.
+    */
+    @Nullable
+    public PsiData getPsiData(PsiData.ResourceType resourceType) {
+        String psiFileData;
+        if (resourceType == PsiData.ResourceType.MEMORY) {
+            psiFileData = mPsiReader.read(PSI_MEMORY_PATH);
+        } else if (resourceType == PsiData.ResourceType.IO) {
+            psiFileData = mPsiReader.read(PSI_IO_PATH);
+        } else if (resourceType == PsiData.ResourceType.CPU) {
+            psiFileData = mPsiReader.read(PSI_CPU_PATH);
+        } else {
+            Log.w(TAG, "PsiExtractor failure: cannot read kernel source file, returning null");
+            return null;
+        }
+        return parsePsiData(psiFileData, resourceType);
+    }
+
+    @Nullable
+    private static PsiData.AppsStallInfo parsePsiString(
+            String psiFileData, String appType, PsiData.ResourceType resourceType) {
+        // There is an extra case of file content: the CPU full is undefined and isn't reported for
+        // earlier versions. It should be always propagated as 0, but for the current logic purposes
+        // we will report atom only if at least one value (some/full) is presented. Thus, hardcoding
+        // the "full" line as 0 only when the "some" line is presented.
+        if (appType == FULL && resourceType == PsiData.ResourceType.CPU) {
+            if (psiFileData.contains(SOME) && !psiFileData.contains(FULL)) {
+                return new PsiData.AppsStallInfo((float) 0.0, (float) 0.0, (float) 0.0, 0);
+            }
+        }
+
+        Pattern psiStringPattern = Pattern.compile(
+                MessageFormat.format(PSI_PATTERN_TEMPLATE, appType));
+        Matcher psiLineMatcher = psiStringPattern.matcher(psiFileData);
+
+        // Parsing the line starts with "some" in the expected output.
+        // The line for "some" should always be present in PSI output. The output must be somehow
+        // malformed if the line cannot be matched.
+        if (!psiLineMatcher.find()) {
+            Log.w(TAG,
+                    "Returning null: the line \"" +  appType + "\" is not in expected pattern.");
+            return null;
+        }
+        try {
+            return new PsiData.AppsStallInfo(
+                    Float.parseFloat(psiLineMatcher.group(1)),
+                    Float.parseFloat(psiLineMatcher.group(2)),
+                    Float.parseFloat(psiLineMatcher.group(3)),
+                    Long.parseLong(psiLineMatcher.group(4)));
+        } catch (NumberFormatException e) {
+            Log.w(TAG,
+                    "Returning null: some value in line \"" +  appType
+                            + "\" cannot be parsed as numeric.");
+            return null;
+        }
+    }
+
+    @Nullable
+    private static PsiData parsePsiData(
+                                         String psiFileData, PsiData.ResourceType resourceType) {
+        PsiData.AppsStallInfo someAppsStallInfo = parsePsiString(psiFileData, SOME, resourceType);
+        PsiData.AppsStallInfo fullAppsStallInfo = parsePsiString(psiFileData, FULL, resourceType);
+
+        if (someAppsStallInfo == null && fullAppsStallInfo == null) {
+            Log.w(TAG, "Returning empty PSI: some or full line are failed to parse");
+            return null;
+        } else if (someAppsStallInfo == null) {
+            Log.d(TAG, "Replacing some info with empty PSI record for the resource type "
+                    + resourceType);
+            someAppsStallInfo = new PsiData.AppsStallInfo(
+                    (float) -1.0, (float) -1.0, (float) -1.0, -1);
+        } else if (fullAppsStallInfo == null) {
+            Log.d(TAG, "Replacing full info with empty PSI record for the resource type "
+                    + resourceType);
+            fullAppsStallInfo = new PsiData.AppsStallInfo(
+                    (float) -1.0, (float) -1.0, (float) -1.0, -1);
+        }
+        return new PsiData(resourceType, someAppsStallInfo, fullAppsStallInfo);
+    }
+
+    /** Dependency class */
+    public static class PsiReader {
+        /**
+        * Reads file from provided path and returns its content if the file found, null otherwise.
+        *
+        * @param filePath file path to read.
+        */
+        @Nullable
+        public String read(String filePath) {
+            try (BufferedReader br =
+                         new BufferedReader(new InputStreamReader(
+                                 new FileInputStream(filePath)))) {
+                return br.lines().collect(joining(System.lineSeparator()));
+            } catch (IOException e) {
+                Log.w(TAG, "Cannot read file " +  filePath);
+                return null;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b27a2a5..c6e6e76 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2538,6 +2538,19 @@
                 }
             }
             if (!activityAllDrawn && !isActivityHome) {
+                // Only check the special case of a fragment host task because the starting window
+                // may not be visible if the client organizer delays the transition ready.
+                if (task.mTaskFragmentHostProcessName != null) {
+                    // It may be launched from a task trampoline that already has a starting window.
+                    // Return NONE because 2 consecutive splashes may not look smooth in visual.
+                    final Task prevTask = task.getParent().getTaskBelow(task);
+                    if (prevTask != null) {
+                        final ActivityRecord prevTaskTop = prevTask.getTopMostActivity();
+                        if (prevTaskTop != null && prevTaskTop.hasStartingWindow()) {
+                            return STARTING_WINDOW_TYPE_NONE;
+                        }
+                    }
+                }
                 return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
             }
         }
@@ -8896,6 +8909,7 @@
                 mAppCompatController.getAppCompatSizeCompatModePolicy();
 
         if (scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()
+                && mAppCompatDisplayInsets != null
                 && !mAppCompatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) {
             // App prefers to keep its original size.
             // If the size compat is from previous fixed orientation letterboxing, we may want to
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index fa2c716..e8eae4f 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -70,15 +70,8 @@
         mAppCompatAspectRatioState.reset();
     }
 
-    float getDesiredAspectRatio(@NonNull Configuration newParentConfig,
+    private float getDesiredAspectRatio(@NonNull Configuration newParentConfig,
             @NonNull Rect parentBounds) {
-        // If in camera compat mode, aspect ratio from the camera compat policy has priority over
-        // default letterbox aspect ratio.
-        if (AppCompatCameraPolicy.shouldCameraCompatControlAspectRatio(
-                mActivityRecord)) {
-            return AppCompatCameraPolicy.getCameraCompatAspectRatio(mActivityRecord);
-        }
-
         final float letterboxAspectRatioOverride =
                 mAppCompatOverrides.getAppCompatAspectRatioOverrides()
                         .getFixedOrientationLetterboxAspectRatio(newParentConfig);
@@ -120,7 +113,16 @@
         if (mTransparentPolicy.isRunning()) {
             return mTransparentPolicy.getInheritedMinAspectRatio();
         }
+
         final ActivityInfo info = mActivityRecord.info;
+
+        // If in camera compat mode, aspect ratio from the camera compat policy has priority over
+        // the default aspect ratio.
+        if (AppCompatCameraPolicy.shouldCameraCompatControlAspectRatio(mActivityRecord)) {
+            return Math.max(AppCompatCameraPolicy.getCameraCompatMinAspectRatio(mActivityRecord),
+                    info.getMinAspectRatio());
+        }
+
         final AppCompatAspectRatioOverrides aspectRatioOverrides =
                 mAppCompatOverrides.getAppCompatAspectRatioOverrides();
         if (aspectRatioOverrides.shouldApplyUserMinAspectRatioOverride()) {
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
index 8c5689c1..8be66cc 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
@@ -234,7 +234,7 @@
     }
 
     // TODO(b/369070416): have policies implement the same interface.
-    static float getCameraCompatAspectRatio(@NonNull ActivityRecord activity) {
+    static float getCameraCompatMinAspectRatio(@NonNull ActivityRecord activity) {
         final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
         if (cameraPolicy == null) {
             return 1.0f;
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 2664d8c..6091b83 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -771,20 +771,21 @@
                 mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
             }
 
-            boolean hasChange = false;
-            if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
+            final boolean startedGoingAway = (!lastKeyguardGoingAway && mKeyguardGoingAway);
+            final boolean occludedChanged = (lastOccluded != mOccluded);
+
+            if (startedGoingAway) {
                 writeEventLog("dismissIfInsecure");
                 controller.handleDismissInsecureKeyguard(display);
                 controller.scheduleGoingAwayTimeout(mDisplayId);
-                hasChange = true;
-            } else if (lastOccluded != mOccluded) {
+            }
+            if (occludedChanged && (reduceKeyguardTransitions() || !startedGoingAway)) {
                 controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
-                hasChange = true;
             }
 
             // Collect the participants for shell transition, so that transition won't happen too
             // early since the transition was set ready.
-            if (hasChange && top != null && (mOccluded || mKeyguardGoingAway)) {
+            if (top != null && (startedGoingAway || (occludedChanged && mOccluded))) {
                 display.mTransitionController.collect(top);
             }
         }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1bb4c41..0f66b93 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -706,6 +706,10 @@
                 win.setRequestedVisibleTypes(requestedVisibleTypes);
                 win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win,
                         imeStatsToken);
+                final Task task = win.getTask();
+                if (task != null) {
+                    task.dispatchTaskInfoChangedIfNeeded(/* forced= */ true);
+                }
             } else {
                 EmbeddedWindowController.EmbeddedWindow embeddedWindow = null;
                 if (android.view.inputmethod.Flags.refactorInsetsController()) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 352dc52..dbc3b76c2 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3436,7 +3436,8 @@
         info.isSleeping = shouldSleepActivities();
         info.isTopActivityTransparent = top != null && !top.fillsParent();
         info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
-        final WindowState windowState = top != null ? top.findMainWindow() : null;
+        final WindowState windowState = top != null
+                ? top.findMainWindow(/* includeStartingApp= */ false) : null;
         info.requestedVisibleTypes = (windowState != null && Flags.enableFullyImmersiveInDesktop())
                 ? windowState.getRequestedVisibleTypes() : WindowInsets.Type.defaultVisible();
         AppCompatUtils.fillAppCompatTaskInfo(this, info, top);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 079170a..81af78e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2989,15 +2989,7 @@
         return (mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0;
     }
 
-    @Override
-    void resolveOverrideConfiguration(Configuration newParentConfig) {
-        super.resolveOverrideConfiguration(newParentConfig);
-        if (mActivityRecord != null) {
-            // Let the activity decide whether to apply the size override.
-            return;
-        }
-        final Configuration resolvedConfig = getResolvedOverrideConfiguration();
-        resolvedConfig.seq = newParentConfig.seq;
+    void applySizeOverride(Configuration newParentConfig, Configuration resolvedConfig) {
         applySizeOverrideIfNeeded(
                 getDisplayContent(),
                 mSession.mProcess.mInfo,
@@ -3380,8 +3372,10 @@
             if (cleanupOnResume) {
                 requestUpdateWallpaperIfNeeded();
             }
-            mDestroying = false;
-            destroyedSomething = true;
+            if (!mHasSurface) {
+                mDestroying = false;
+                destroyedSomething = true;
+            }
 
             // Since mDestroying will affect ActivityRecord#allDrawn, we need to perform another
             // traversal in case we are waiting on this window to start the transition.
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 44e237a..004f406 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -672,6 +672,15 @@
             getResolvedOverrideConfiguration().updateFrom(
                     mFixedRotationTransformState.mRotatedOverrideConfiguration);
         }
+        if (asActivityRecord() == null) {
+            // Let ActivityRecord override the config if there is one. Otherwise, override here.
+            // Resolve WindowToken's configuration by the latest window.
+            final WindowState win = getTopChild();
+            if (win != null) {
+                final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+                win.applySizeOverride(newParentConfig, resolvedConfig);
+            }
+        }
     }
 
     @Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 9841058..c19c58e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -99,10 +99,11 @@
 final class DevicePolicyEngine {
     static final String TAG = "DevicePolicyEngine";
 
-    // TODO(b/281701062): reference role name from role manager once its exposed.
     static final String DEVICE_LOCK_CONTROLLER_ROLE =
             "android.app.role.SYSTEM_FINANCED_DEVICE_CONTROLLER";
 
+    static final String SYSTEM_SUPERVISION_ROLE = "android.app.role.SYSTEM_SUPERVISION";
+
     private static final String CELLULAR_2G_USER_RESTRICTION_ID =
             DevicePolicyIdentifiers.getIdentifierForUserRestriction(
                     UserManager.DISALLOW_CELLULAR_2G);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c653038..8ad8786 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9004,26 +9004,13 @@
         if (!mHasFeature) {
             return;
         }
-
-        CallerIdentity caller;
-        if (Flags.setAutoTimeEnabledCoexistence()) {
-            caller = getCallerIdentity(who, callerPackageName);
-        } else {
-            caller = getCallerIdentity(who);
-        }
-
-        if (Flags.setAutoTimeEnabledCoexistence()) {
-            // The effect of this policy is device-wide.
-            enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
-        } else {
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
-                    || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
-                    caller));
-        }
+        CallerIdentity caller = getCallerIdentity(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+                caller));
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, enabled ? 1 : 0));
-
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_AUTO_TIME)
                 .setAdmin(caller.getPackageName())
@@ -9039,26 +9026,77 @@
         if (!mHasFeature) {
             return false;
         }
-        CallerIdentity caller;
-        if (Flags.setAutoTimeEnabledCoexistence()) {
-            caller = getCallerIdentity(who, callerPackageName);
-        } else {
-            caller = getCallerIdentity(who);
-        }
+        CallerIdentity caller = getCallerIdentity(who);
 
-        if (Flags.setAutoTimeEnabledCoexistence()) {
-            enforceCanQuery(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
-        } else {
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
-                    || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
-                    caller));
-        }
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
 
         return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
     }
 
     /**
+     * Set whether auto time is enabled on the device.
+     */
+    @Override
+    public void setAutoTimePolicy(String callerPackageName, int policy) {
+        if (!mHasFeature) {
+            return;
+        }
+
+        final Set<Integer> allowedValues =
+                Set.of(
+                        DevicePolicyManager.AUTO_TIME_ENABLED,
+                        DevicePolicyManager.AUTO_TIME_DISABLED,
+                        DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY);
+        Preconditions.checkArgument(
+                allowedValues.contains(policy), "Provided mode is not one of the allowed values.");
+
+        CallerIdentity caller = getCallerIdentity(callerPackageName);
+        // The effect of this policy is device-wide.
+        EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+                /* who */ null,
+                SET_TIME,
+                caller.getPackageName(),
+                UserHandle.USER_ALL
+        );
+        if (policy == DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY) {
+            mDevicePolicyEngine.removeGlobalPolicy(PolicyDefinition.AUTO_TIME, enforcingAdmin);
+        } else {
+            mDevicePolicyEngine.setGlobalPolicy(
+                    PolicyDefinition.AUTO_TIME,
+                    enforcingAdmin,
+                    new IntegerPolicyValue(policy));
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.SET_AUTO_TIME)
+                    .setAdmin(caller.getPackageName())
+                    .setBoolean(policy == DevicePolicyManager.AUTO_TIME_ENABLED)
+                    .write();
+        }
+    }
+
+    /**
+     * Returns whether auto time is used on the device or not.
+     */
+    @Override
+    public int getAutoTimePolicy(String callerPackageName) {
+        if (!mHasFeature) {
+            return DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY;
+        }
+        CallerIdentity caller = getCallerIdentity(callerPackageName);
+        // The effect of this policy is device-wide.
+        EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+                /* who */ null,
+                SET_TIME,
+                caller.getPackageName(),
+                UserHandle.USER_ALL
+        );
+        Integer state = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+                PolicyDefinition.AUTO_TIME, enforcingAdmin);
+        return state != null ? state : DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY;
+    }
+
+    /**
      * Set whether auto time zone is enabled on the device.
      */
     @Override
@@ -9068,35 +9106,14 @@
             return;
         }
 
-        CallerIdentity caller;
-        if (Flags.setAutoTimeZoneEnabledCoexistence()) {
-            caller = getCallerIdentity(who, callerPackageName);
-        } else {
-            caller = getCallerIdentity(who);
-        }
+        CallerIdentity caller = getCallerIdentity(who);
 
-        if (Flags.setAutoTimeZoneEnabledCoexistence()) {
-            // The effect of this policy is device-wide.
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                    who,
-                    SET_TIME_ZONE,
-                    caller.getPackageName(),
-                    UserHandle.USER_ALL
-            );
-            mDevicePolicyEngine.setGlobalPolicy(
-                    PolicyDefinition.AUTO_TIMEZONE,
-                    // TODO(b/260573124): add correct enforcing admin when permission changes are
-                    //  merged.
-                    enforcingAdmin,
-                    new BooleanPolicyValue(enabled));
-        } else {
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
-                    || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
-                    caller));
-            mInjector.binderWithCleanCallingIdentity(() ->
-                    mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
-        }
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+                caller));
+        mInjector.binderWithCleanCallingIdentity(() ->
+                mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_AUTO_TIME_ZONE)
@@ -9114,26 +9131,70 @@
             return false;
         }
 
-        CallerIdentity caller;
-        if (Flags.setAutoTimeZoneEnabledCoexistence()) {
-            caller = getCallerIdentity(who, callerPackageName);
-        } else {
-            caller = getCallerIdentity(who);
-        }
-
-        if (Flags.setAutoTimeZoneEnabledCoexistence()) {
-            // The effect of this policy is device-wide.
-            enforceCanQuery(SET_TIME_ZONE, caller.getPackageName(), UserHandle.USER_ALL);
-        } else {
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
-                    || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
-                    caller));
-        }
-
+        CallerIdentity caller = getCallerIdentity(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+                caller));
         return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
     }
 
+    /**
+     * Set auto time zone state.
+     */
+    public void setAutoTimeZonePolicy(String callerPackageName, int policy) {
+        if (!mHasFeature) {
+            return;
+        }
+
+        CallerIdentity caller = getCallerIdentity(callerPackageName);
+        // The effect of this policy is device-wide.
+        EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+                /* who */ null,
+                SET_TIME_ZONE,
+                caller.getPackageName(),
+                UserHandle.USER_ALL
+        );
+
+        if (policy != DevicePolicyManager.AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY) {
+            mDevicePolicyEngine.setGlobalPolicy(
+                    PolicyDefinition.AUTO_TIME_ZONE,
+                    enforcingAdmin,
+                    new IntegerPolicyValue(policy));
+
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.SET_AUTO_TIME_ZONE)
+                    .setAdmin(caller.getPackageName())
+                    .setBoolean(policy == DevicePolicyManager.AUTO_TIME_ZONE_ENABLED)
+                    .write();
+        } else {
+            mDevicePolicyEngine.removeGlobalPolicy(
+                    PolicyDefinition.AUTO_TIME_ZONE,
+                    enforcingAdmin);
+        }
+    }
+
+    /**
+     * Returns whether auto time zone is used on the device or not.
+     */
+    @Override
+    public int getAutoTimeZonePolicy(String callerPackageName) {
+        if (!mHasFeature) {
+            return DevicePolicyManager.AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY;
+        }
+        CallerIdentity caller = getCallerIdentity(callerPackageName);
+        // The effect of this policy is device-wide.
+        EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+                /* who */ null,
+                SET_TIME_ZONE,
+                caller.getPackageName(),
+                UserHandle.USER_ALL
+        );
+        Integer state = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+                PolicyDefinition.AUTO_TIME_ZONE, enforcingAdmin);
+        return state != null ? state : DevicePolicyManager.AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY;
+    }
+
     // TODO (b/137101239): remove this method in follow-up CL
     // since it's only used for split system user.
     @Override
@@ -21030,6 +21091,27 @@
     }
 
     @Override
+    public boolean removeManagedProfile(int userId) {
+        Preconditions.checkCallAuthorization(
+                hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
+        if (!isManagedProfile(userId)){
+            throw new IllegalArgumentException("Cannot remove user as it is not a managed profile");
+        }
+
+        boolean success = false;
+        final long identity = Binder.clearCallingIdentity();
+        try{
+            success = mUserManager.removeUserEvenWhenDisallowed(userId);
+        } catch (Exception e) {
+            Slogf.e(LOG_TAG, "Remove managed profile failed due to: ", e);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return success;
+    }
+
+    @Override
     public UserHandle createAndProvisionManagedProfile(
             @NonNull ManagedProfileProvisioningParams provisioningParams,
             @NonNull String callerPackage) {
@@ -23765,9 +23847,6 @@
                     Slogf.i(LOG_TAG,
                             "Started device policies migration to the device policy engine.");
                     // TODO(b/359188869): Move this to the current migration method.
-                    if (Flags.setAutoTimeZoneEnabledCoexistence()) {
-                        migrateAutoTimezonePolicy();
-                    }
                     if (Flags.setPermissionGrantStateCoexistence()) {
                         migratePermissionGrantStatePolicies();
                     }
@@ -23816,11 +23895,6 @@
         // Additional migration steps should repeat the pattern above with a new backupId.
     }
 
-    private void migrateAutoTimezonePolicy() {
-        Slogf.i(LOG_TAG, "Skipping Migration of AUTO_TIMEZONE policy to device policy engine,"
-                + "as no way to identify if the value was set by the admin or the user.");
-    }
-
     private void migratePermissionGrantStatePolicies() {
         Slogf.i(LOG_TAG, "Migrating PERMISSION_GRANT policy to device policy engine.");
         for (UserInfo userInfo : mUserManager.getUsers()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index f1711f5..24b16b7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -17,6 +17,7 @@
 package com.android.server.devicepolicy;
 
 import static com.android.server.devicepolicy.DevicePolicyEngine.DEVICE_LOCK_CONTROLLER_ROLE;
+import static com.android.server.devicepolicy.DevicePolicyEngine.SYSTEM_SUPERVISION_ROLE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -94,14 +95,18 @@
     private static final MostRestrictive<Boolean> TRUE_MORE_RESTRICTIVE = new MostRestrictive<>(
             List.of(new BooleanPolicyValue(true), new BooleanPolicyValue(false)));
 
-    static PolicyDefinition<Boolean> AUTO_TIMEZONE = new PolicyDefinition<>(
+    static PolicyDefinition<Integer> AUTO_TIME_ZONE = new PolicyDefinition<>(
             new NoArgsPolicyKey(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY),
-            // auto timezone is disabled by default, hence enabling it is more restrictive.
-            TRUE_MORE_RESTRICTIVE,
+            // Auto time zone is enabled by default. Enabled state has higher priority given it
+            // means the time will be more precise and other applications can rely on that for
+            // their purposes.
+            new TopPriority<>(List.of(
+                    EnforcingAdmin.getRoleAuthorityOf(SYSTEM_SUPERVISION_ROLE),
+                    EnforcingAdmin.getRoleAuthorityOf(DEVICE_LOCK_CONTROLLER_ROLE),
+                    EnforcingAdmin.DPC_AUTHORITY)),
             POLICY_FLAG_GLOBAL_ONLY_POLICY,
-            (Boolean value, Context context, Integer userId, PolicyKey policyKey) ->
-                    PolicyEnforcerCallbacks.setAutoTimezoneEnabled(value, context),
-            new BooleanPolicySerializer());
+            PolicyEnforcerCallbacks::setAutoTimeZonePolicy,
+            new IntegerPolicySerializer());
 
     static final PolicyDefinition<Integer> GENERIC_PERMISSION_GRANT =
             new PolicyDefinition<>(
@@ -344,12 +349,22 @@
                     PolicyEnforcerCallbacks::setMtePolicy,
                     new IntegerPolicySerializer());
 
+    static PolicyDefinition<Integer> AUTO_TIME = new PolicyDefinition<>(
+            new NoArgsPolicyKey(DevicePolicyIdentifiers.AUTO_TIME_POLICY),
+            new TopPriority<>(List.of(
+                    EnforcingAdmin.getRoleAuthorityOf(SYSTEM_SUPERVISION_ROLE),
+                    EnforcingAdmin.getRoleAuthorityOf(DEVICE_LOCK_CONTROLLER_ROLE),
+                    EnforcingAdmin.DPC_AUTHORITY)),
+            POLICY_FLAG_GLOBAL_ONLY_POLICY,
+            PolicyEnforcerCallbacks::setAutoTimePolicy,
+            new IntegerPolicySerializer());
+
     private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
     private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
 
     // TODO(b/277218360): Revisit policies that should be marked as global-only.
     static {
-        POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE);
+        POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY, AUTO_TIME_ZONE);
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY,
                 GENERIC_PERMISSION_GRANT);
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.SECURITY_LOGGING_POLICY,
@@ -392,6 +407,7 @@
                 PACKAGES_SUSPENDED);
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY,
                 MEMORY_TAGGING);
+        POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIME_POLICY, AUTO_TIME);
 
         // User Restriction Policies
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index fdc0ec1..8f80004 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -81,20 +81,24 @@
         return AndroidFuture.completedFuture(true);
     }
 
-    static CompletableFuture<Boolean> setAutoTimezoneEnabled(@Nullable Boolean enabled,
-            @NonNull Context context) {
+    static CompletableFuture<Boolean> setAutoTimeZonePolicy(
+            @Nullable Integer policy, @NonNull Context context, int userId,
+            @NonNull PolicyKey policyKey) {
         if (!Flags.setAutoTimeZoneEnabledCoexistence()) {
-            Slogf.w(LOG_TAG, "Trying to enforce setAutoTimezoneEnabled while flag is off.");
+            Slogf.w(LOG_TAG, "Trying to enforce setAutoTimeZonePolicy while flag is off.");
             return AndroidFuture.completedFuture(true);
         }
         return Binder.withCleanCallingIdentity(() -> {
             Objects.requireNonNull(context);
-
-            int value = enabled != null && enabled ? 1 : 0;
-            return AndroidFuture.completedFuture(
-                    Settings.Global.putInt(
-                            context.getContentResolver(), Settings.Global.AUTO_TIME_ZONE,
-                    value));
+            if (policy != null &&
+                    policy == DevicePolicyManager.AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY) {
+                return AndroidFuture.completedFuture(false);
+            }
+            int enabled = policy != null &&
+                    policy == DevicePolicyManager.AUTO_TIME_ZONE_ENABLED ? 1 : 0;
+            return AndroidFuture.completedFuture(Settings.Global.putInt(
+                    context.getContentResolver(), Settings.Global.AUTO_TIME_ZONE,
+                    enabled));
         });
     }
 
@@ -208,6 +212,25 @@
         return AndroidFuture.completedFuture(true);
     }
 
+    public static CompletableFuture<Boolean> setAutoTimePolicy(
+            Integer policy, Context context, Integer userId, PolicyKey policyKey) {
+        if (!Flags.setAutoTimeEnabledCoexistence()) {
+            Slogf.w(LOG_TAG, "Trying to enforce setAutoTimePolicy while flag is off.");
+            return AndroidFuture.completedFuture(true);
+        }
+        return Binder.withCleanCallingIdentity(() -> {
+            Objects.requireNonNull(context);
+            if (policy != null
+                    && policy == DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY) {
+                return AndroidFuture.completedFuture(false);
+            }
+            int enabled = policy != null && policy == DevicePolicyManager.AUTO_TIME_ENABLED ? 1 : 0;
+            return AndroidFuture.completedFuture(
+                    Settings.Global.putInt(
+                            context.getContentResolver(), Settings.Global.AUTO_TIME,  enabled));
+        });
+    }
+
     private static class BlockingCallback {
         private final CountDownLatch mLatch = new CountDownLatch(1);
         private final AtomicReference<Boolean> mValue = new AtomicReference<>();
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java b/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java
index fead05b..5df9dd5 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java
@@ -35,6 +35,14 @@
     public abstract boolean isSupervisionEnabledForUser(@UserIdInt int userId);
 
     /**
+     * Set whether supervision is enabled for the specified user.
+     *
+     * @param userId The user to set the supervision state for
+     * @param enabled Whether or not the user should be supervised
+     */
+    public abstract void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled);
+
+    /**
      * Sets whether the supervision lock screen should be shown for the specified user
      *
      * @param userId The user set the superivision state for
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index 4c515c1..67e2547 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -19,7 +19,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.app.supervision.ISupervisionManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.Bundle;
@@ -28,19 +30,19 @@
 import android.os.ShellCallback;
 import android.util.SparseArray;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.pm.UserManagerInternal;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
-/**
- * Service for handling system supervision.
- */
+/** Service for handling system supervision. */
 public class SupervisionService extends ISupervisionManager.Stub {
     private static final String LOG_TAG = "SupervisionService";
 
@@ -52,14 +54,25 @@
     @GuardedBy("getLockObject()")
     private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>();
 
+    private final DevicePolicyManagerInternal mDpmInternal;
     private final UserManagerInternal mUserManagerInternal;
 
     public SupervisionService(Context context) {
         mContext = context.createAttributionContext(LOG_TAG);
+        mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
         mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
     }
 
+    void syncStateWithDevicePolicyManager(TargetUser user) {
+        if (user.isPreCreated()) return;
+
+        // Ensure that supervision is enabled when supervision app is the profile owner.
+        if (android.app.admin.flags.Flags.enableSupervisionServiceSync() && isProfileOwner(user)) {
+            setSupervisionEnabledForUser(user.getUserIdentifier(), true);
+        }
+    }
+
     @Override
     public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
         synchronized (getLockObject()) {
@@ -74,14 +87,15 @@
             @Nullable FileDescriptor err,
             @NonNull String[] args,
             @Nullable ShellCallback callback,
-            @NonNull ResultReceiver resultReceiver) throws RemoteException {
+            @NonNull ResultReceiver resultReceiver)
+            throws RemoteException {
         new SupervisionServiceShellCommand(this)
                 .exec(this, in, out, err, args, callback, resultReceiver);
     }
 
     @Override
-    protected void dump(@NonNull FileDescriptor fd,
-            @NonNull PrintWriter printWriter, @Nullable String[] args) {
+    protected void dump(
+            @NonNull FileDescriptor fd, @NonNull PrintWriter printWriter, @Nullable String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, printWriter)) return;
 
         try (var pw = new IndentingPrintWriter(printWriter, "  ")) {
@@ -120,6 +134,17 @@
         }
     }
 
+    /** Returns whether the supervision app has profile owner status. */
+    private boolean isProfileOwner(TargetUser user) {
+        ComponentName profileOwner = mDpmInternal.getProfileOwnerAsUser(user.getUserIdentifier());
+        if (profileOwner == null) {
+            return false;
+        }
+
+        String configPackage = mContext.getResources().getString(R.string.config_systemSupervision);
+        return profileOwner.getPackageName().equals(configPackage);
+    }
+
     public static class Lifecycle extends SystemService {
         private final SupervisionService mSupervisionService;
 
@@ -133,13 +158,24 @@
             publishLocalService(SupervisionManagerInternal.class, mSupervisionService.mInternal);
             publishBinderService(Context.SUPERVISION_SERVICE, mSupervisionService);
         }
+
+        @Override
+        public void onUserStarting(@NonNull TargetUser user) {
+            mSupervisionService.syncStateWithDevicePolicyManager(user);
+        }
     }
 
-    final SupervisionManagerInternal mInternal = new SupervisionManagerInternal() {
+    final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl();
+
+    private final class SupervisionManagerInternalImpl extends SupervisionManagerInternal {
+        @Override
         public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
-            synchronized (getLockObject()) {
-                return getUserDataLocked(userId).supervisionEnabled;
-            }
+            return SupervisionService.this.isSupervisionEnabledForUser(userId);
+        }
+
+        @Override
+        public void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) {
+            SupervisionService.this.setSupervisionEnabledForUser(userId, enabled);
         }
 
         @Override
@@ -151,7 +187,7 @@
                 data.supervisionLockScreenOptions = options;
             }
         }
-    };
+    }
 
     private final class UserLifecycleListener implements UserManagerInternal.UserLifecycleListener {
         @Override
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 69714f3..3fdb53f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -582,6 +582,34 @@
             PackageImpl::setSystemExt,
             true
         ),
+        getSetByValue(
+            AndroidPackage::getAlternateLauncherIconResIds,
+            PackageImpl::setAlternateLauncherIconResIds,
+            intArrayOf(3, 5, 7),
+            compare = { first, second ->
+                equalBy(
+                    first, second,
+                    { it.size },
+                    { it[0] },
+                    { it[1] },
+                    { it[2] }
+                )
+            }
+        ),
+        getSetByValue(
+            AndroidPackage::getAlternateLauncherLabelResIds,
+            PackageImpl::setAlternateLauncherLabelResIds,
+            intArrayOf(3, 5, 7),
+            compare = { first, second ->
+                equalBy(
+                    first, second,
+                    { it.size },
+                    { it[0] },
+                    { it[1] },
+                    { it[2] }
+                )
+            }
+        ),
     )
 
     override fun initialObject() = PackageImpl.forParsing(
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 4a131558..f82a860 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -109,6 +109,7 @@
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArrayMap;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
 import com.android.server.LocalServices;
@@ -175,6 +176,7 @@
     private ActiveUids mActiveUids;
     private PackageManagerInternal mPackageManagerInternal;
     private ActivityManagerService mService;
+    private TestCachedAppOptimizer mTestCachedAppOptimizer;
     private OomAdjusterInjector mInjector = new OomAdjusterInjector();
 
     private int mUiTierSize;
@@ -242,9 +244,11 @@
         doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(),
                 anyBoolean());
         mActiveUids = new ActiveUids(mService, false);
+        mTestCachedAppOptimizer = new TestCachedAppOptimizer(mService);
         mProcessStateController = new ProcessStateController.Builder(mService,
                 mService.mProcessList, mActiveUids)
                 .useModernOomAdjuster(mService.mConstants.ENABLE_NEW_OOMADJ)
+                .setCachedAppOptimizer(mTestCachedAppOptimizer)
                 .setOomAdjusterInjector(mInjector)
                 .build();
         mService.mProcessStateController = mProcessStateController;
@@ -3110,13 +3114,13 @@
         mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, true);
 
         assertEquals(true, app.getUidRecord().isSetAllowListed());
-        assertEquals(true, app.mOptRecord.shouldNotFreeze());
-        assertEquals(true, app2.mOptRecord.shouldNotFreeze());
+        assertFreezeState(app, false);
+        assertFreezeState(app2, false);
 
         mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, false);
         assertEquals(false, app.getUidRecord().isSetAllowListed());
-        assertEquals(false, app.mOptRecord.shouldNotFreeze());
-        assertEquals(false, app2.mOptRecord.shouldNotFreeze());
+        assertFreezeState(app, true);
+        assertFreezeState(app2, true);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -3138,25 +3142,25 @@
 
         assertEquals(true, app.getUidRecord().isSetAllowListed());
         assertEquals(true, app2.getUidRecord().isSetAllowListed());
-        assertEquals(true, app.mOptRecord.shouldNotFreeze());
-        assertEquals(true, app2.mOptRecord.shouldNotFreeze());
-        assertEquals(true, app3.mOptRecord.shouldNotFreeze());
+        assertFreezeState(app, false);
+        assertFreezeState(app2, false);
+        assertFreezeState(app3, false);
 
         // Remove app1 from allowlist.
         mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, false);
         assertEquals(false, app.getUidRecord().isSetAllowListed());
         assertEquals(true, app2.getUidRecord().isSetAllowListed());
-        assertEquals(false, app.mOptRecord.shouldNotFreeze());
-        assertEquals(true, app2.mOptRecord.shouldNotFreeze());
-        assertEquals(true, app3.mOptRecord.shouldNotFreeze());
+        assertFreezeState(app, true);
+        assertFreezeState(app2, false);
+        assertFreezeState(app3, false);
 
         // Now remove app2 from allowlist.
         mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP2_UID, false);
         assertEquals(false, app.getUidRecord().isSetAllowListed());
         assertEquals(false, app2.getUidRecord().isSetAllowListed());
-        assertEquals(false, app.mOptRecord.shouldNotFreeze());
-        assertEquals(false, app2.mOptRecord.shouldNotFreeze());
-        assertEquals(false, app3.mOptRecord.shouldNotFreeze());
+        assertFreezeState(app, true);
+        assertFreezeState(app2, true);
+        assertFreezeState(app3, true);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -3370,6 +3374,14 @@
         assertEquals(expectedCached, state.isCached());
     }
 
+    @SuppressWarnings("GuardedBy")
+    private void assertFreezeState(ProcessRecord app, boolean expectedFreezeState) {
+        boolean actualFreezeState = mTestCachedAppOptimizer.mLastSetFreezeState.get(app.getPid(),
+                false);
+        assertEquals("Unexcepted freeze state for " + app.processName, expectedFreezeState,
+                actualFreezeState);
+    }
+
     private class ProcessRecordBuilder {
         @SuppressWarnings("UnusedVariable")
         int mPid;
@@ -3513,6 +3525,39 @@
             return app;
         }
     }
+    private static final class TestProcessDependencies
+            implements CachedAppOptimizer.ProcessDependencies {
+        @Override
+        public long[] getRss(int pid) {
+            return new long[]{/*totalRSS*/ 0, /*fileRSS*/ 0, /*anonRSS*/ 0, /*swap*/ 0};
+        }
+
+        @Override
+        public void performCompaction(CachedAppOptimizer.CompactProfile action, int pid) {}
+    }
+
+    private static class TestCachedAppOptimizer extends CachedAppOptimizer {
+        private SparseBooleanArray mLastSetFreezeState = new SparseBooleanArray();
+
+        TestCachedAppOptimizer(ActivityManagerService ams) {
+            super(ams, null, new TestProcessDependencies());
+        }
+
+        @Override
+        public boolean useFreezer() {
+            return true;
+        }
+
+        @Override
+        public void freezeAppAsyncLSP(ProcessRecord app) {
+            mLastSetFreezeState.put(app.getPid(), true);
+        }
+
+        @Override
+        public void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason) {
+            mLastSetFreezeState.put(app.getPid(), false);
+        }
+    }
 
     static class OomAdjusterInjector extends OomAdjuster.Injector {
         // Jump ahead in time by this offset amount.
@@ -3524,7 +3569,6 @@
             mLastSetOomAdj.clear();
         }
 
-
         void jumpUptimeAheadTo(long uptimeMillis) {
             final long jumpMs = uptimeMillis - getUptimeMillis();
             if (jumpMs <= 0) return;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index c418151..96c6cbc 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -123,7 +123,7 @@
             Assert.assertEquals("mic mute reporting wrong value",
                     muted, mAudioService.isMicrophoneMuted());
             // verify the intent for mic mute changed is supposed to be fired
-            Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
+            mTestLooper.dispatchAll();
             verify(mSpySystemServer, times(1))
                     .sendMicrophoneMuteChangedIntent();
             reset(mSpySystemServer);
@@ -148,7 +148,7 @@
             Assert.assertEquals("mic mute reporting wrong value",
                     !muted, mAudioService.isMicrophoneMuted());
             // verify the intent for mic mute changed is supposed to be fired
-            Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
+            mTestLooper.dispatchAll();
             verify(mSpySystemServer, times(1))
                     .sendMicrophoneMuteChangedIntent();
             reset(mSpySystemServer);
@@ -159,8 +159,7 @@
     public void testRingNotifAlias() throws Exception {
         Log.i(TAG, "running testRingNotifAlias");
         Assert.assertNotNull(mAudioService);
-        // TODO add initialization message that can be caught here instead of sleeping
-        Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS); // wait for full AudioService initialization
+        mTestLooper.dispatchAll(); // wait for full AudioService initialization
 
         // test with aliasing RING and NOTIFICATION
         mAudioService.setNotifAliasRingForTest(true);
@@ -171,7 +170,7 @@
         mAudioService.setStreamVolume(AudioSystem.STREAM_NOTIFICATION,
                 ringVol, 0, "bla");
         mAudioService.setStreamVolume(AudioSystem.STREAM_RING, ringMaxVol, 0, "bla");
-        Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
+        mTestLooper.dispatchAll();
         Assert.assertEquals(ringMaxVol,
                 mAudioService.getStreamVolume(AudioSystem.STREAM_NOTIFICATION));
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index cf628ad..b81bf3c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -332,6 +332,28 @@
         assertThat(preAuthInfo.callingUserId).isEqualTo(USER_ID);
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_EFFECTIVE_USER_BP)
+    public void testCredentialOwnerIdAsUserId_forMandatoryBiometrics() throws Exception {
+        when(mUserManager.getCredentialOwnerProfile(USER_ID)).thenReturn(OWNER_ID);
+        when(mSettingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
+                OWNER_ID)).thenReturn(true);
+        when(mSettingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
+                USER_ID)).thenReturn(false);
+        when(mTrustManager.isInSignificantPlace()).thenReturn(false);
+
+        final BiometricSensor sensor = getFaceSensor();
+        final PromptInfo promptInfo = new PromptInfo();
+        promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK);
+        promptInfo.setNegativeButtonText(TEST_PACKAGE_NAME);
+        final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+                mSettingObserver, List.of(sensor), USER_ID , promptInfo, TEST_PACKAGE_NAME,
+                false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+                mUserManager);
+
+        assertThat(preAuthInfo.getIsMandatoryBiometricsAuthentication()).isTrue();
+    }
+
     private BiometricSensor getFingerprintSensor() {
         BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FINGERPRINT,
                 TYPE_FINGERPRINT, BiometricManager.Authenticators.BIOMETRIC_STRONG,
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 93aa10b..fd22118 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -68,7 +68,6 @@
 
 import com.android.internal.R;
 import com.android.server.compat.PlatformCompat;
-import com.android.server.integrity.model.IntegrityCheckResult;
 import com.android.server.testutils.TestUtils;
 
 import org.junit.After;
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
deleted file mode 100644
index 57274bf..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2020 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.model;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.ByteArrayOutputStream;
-
-@RunWith(JUnit4.class)
-public class ByteTrackedOutputStreamTest {
-
-    @Test
-    public void testConstructorStartsWithZeroBytesWritten() {
-        ByteTrackedOutputStream byteTrackedOutputStream =
-                new ByteTrackedOutputStream(new ByteArrayOutputStream());
-
-        assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(0);
-    }
-
-    @Test
-    public void testSuccessfulWriteAndValidateWrittenBytesCount_directFromByteArray()
-            throws Exception {
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        ByteTrackedOutputStream byteTrackedOutputStream = new ByteTrackedOutputStream(outputStream);
-
-        byte[] outputContent = "This is going to be outputed for tests.".getBytes();
-        byteTrackedOutputStream.write(outputContent);
-
-        assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(outputContent.length);
-        assertThat(outputStream.toByteArray().length).isEqualTo(outputContent.length);
-    }
-
-    @Test
-    public void testSuccessfulWriteAndValidateWrittenBytesCount_fromBitStream() throws Exception {
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        ByteTrackedOutputStream byteTrackedOutputStream = new ByteTrackedOutputStream(outputStream);
-
-        BitOutputStream bitOutputStream = new BitOutputStream(byteTrackedOutputStream);
-        bitOutputStream.setNext(/* numOfBits= */5, /* value= */1);
-        bitOutputStream.flush();
-
-        // Even though we wrote 5 bits, this will complete to 1 byte.
-        assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(1);
-
-        // Add a bit less than 2 bytes (10 bits).
-        bitOutputStream.setNext(/* numOfBits= */10, /* value= */1);
-        bitOutputStream.flush();
-        assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(3);
-
-        assertThat(outputStream.toByteArray().length).isEqualTo(3);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/IntegrityCheckResultTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/IntegrityCheckResultTest.java
deleted file mode 100644
index d31ed68..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/model/IntegrityCheckResultTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2020 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.model;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.Rule;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Arrays;
-import java.util.Collections;
-
-@RunWith(JUnit4.class)
-public class IntegrityCheckResultTest {
-
-    @Test
-    public void createAllowResult() {
-        IntegrityCheckResult allowResult = IntegrityCheckResult.allow();
-
-        assertThat(allowResult.getEffect()).isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-        assertThat(allowResult.getMatchedRules()).isEmpty();
-    }
-
-    @Test
-    public void createAllowResultWithRule() {
-        String packageName = "com.test.deny";
-        Rule forceAllowRule =
-                new Rule(
-                        new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME,
-                                packageName),
-                        Rule.FORCE_ALLOW);
-
-        IntegrityCheckResult allowResult =
-                IntegrityCheckResult.allow(Collections.singletonList(forceAllowRule));
-
-        assertThat(allowResult.getEffect()).isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-        assertThat(allowResult.getMatchedRules()).containsExactly(forceAllowRule);
-    }
-
-    @Test
-    public void createDenyResultWithRule() {
-        String packageName = "com.test.deny";
-        Rule failedRule =
-                new Rule(
-                        new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME,
-                                packageName),
-                        Rule.DENY);
-
-        IntegrityCheckResult denyResult =
-                IntegrityCheckResult.deny(Collections.singletonList(failedRule));
-
-        assertThat(denyResult.getEffect()).isEqualTo(IntegrityCheckResult.Effect.DENY);
-        assertThat(denyResult.getMatchedRules()).containsExactly(failedRule);
-    }
-
-    @Test
-    public void isDenyCausedByAppCertificate() {
-        String packageName = "com.test.deny";
-        String appCert = "app-cert";
-        Rule failedRule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.AND,
-                                Arrays.asList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME, packageName),
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.APP_CERTIFICATE, appCert))),
-                        Rule.DENY);
-        Rule otherFailedRule =
-                new Rule(
-                        new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE,
-                                AtomicFormula.EQ, 12),
-                        Rule.DENY);
-
-        IntegrityCheckResult denyResult =
-                IntegrityCheckResult.deny(Arrays.asList(failedRule, otherFailedRule));
-
-        assertThat(denyResult.isCausedByAppCertRule()).isTrue();
-        assertThat(denyResult.isCausedByInstallerRule()).isFalse();
-    }
-
-    @Test
-    public void isDenyCausedByInstaller() {
-        String packageName = "com.test.deny";
-        String appCert = "app-cert";
-        Rule failedRule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.AND,
-                                Arrays.asList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME, packageName),
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.INSTALLER_CERTIFICATE, appCert))),
-                        Rule.DENY);
-        Rule otherFailedRule =
-                new Rule(
-                        new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE,
-                                AtomicFormula.EQ, 12),
-                        Rule.DENY);
-
-        IntegrityCheckResult denyResult =
-                IntegrityCheckResult.deny(Arrays.asList(failedRule, otherFailedRule));
-
-        assertThat(denyResult.isCausedByAppCertRule()).isFalse();
-        assertThat(denyResult.isCausedByInstallerRule()).isTrue();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/stats/pull/psi/OWNERS b/services/tests/servicestests/src/com/android/server/stats/pull/psi/OWNERS
new file mode 100644
index 0000000..e068a84
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/pull/psi/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/stats/pull/psi/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/stats/pull/psi/PsiExtractorTest.java b/services/tests/servicestests/src/com/android/server/stats/pull/psi/PsiExtractorTest.java
new file mode 100644
index 0000000..b563c08
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/pull/psi/PsiExtractorTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull.psi;
+
+import static org.testng.AssertJUnit.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+
+public class PsiExtractorTest {
+    @Mock
+    private PsiExtractor.PsiReader mPsiReader;
+    private PsiExtractor mPsiExtractor;
+    // PSI file content with both some and full lines.
+    private static final String PSI_FILE_CONTENT_BOTH_LINES =
+            "some avg10=0.12 avg60=0.34 avg300=0.56 total=12345678\n"
+                    + "full avg10=0.21 avg60=0.43 avg300=0.65 total=87654321";
+    // PSI file content with only some line.
+    private static final String PSI_FILE_CONTENT_ONLY_SOME_LINE =
+            "some avg10=0.12 avg60=0.34 avg300=0.56 total=12345678";
+
+    // PSI file content with only full line.
+    private static final String PSI_FILE_CONTENT_ONLY_FULL_LINE =
+            "\nfull avg10=0.21 avg60=0.43 avg300=0.65 total=87654321";
+
+    // PSI file content that is malformed with "avg60" missing from the both lines.
+    private static final String BOTH_AVG60_MISSING_PSI_FILE_CONTENT =
+            "some avg10=0.12 avg300=0.56 total=12345678\n"
+                    + "full avg10=0.21 avg300=0.65 total=87654321";
+
+    // PSI file content that is malformed with non number "avg10" from the both lines.
+    private static final String NON_NUM_AVG10_PSI_FILE_CONTENT =
+            "some avg10=1.a2 avg300=0.56 total=12345678\n"
+                    + "full avg10=0.2s1 avg60=0.43 avg300=0.65 total=87654321";
+
+    // PSI file content that is malformed with non number "avg300" from the both lines.
+    private static final String NON_NUM_AVG300_PSI_FILE_CONTENT =
+            "some avg10=0.2 avg60=0.43 avg300=0.5ss6 total=12345678\n"
+                    + "full avg10=0.21 avg60=0.43 avg300=0.6b5 total=87654321";
+
+    // PSI file content that is malformed with non number "total"  from the both lines.
+    private static final String BOTH_TOTAL_MISSING_PSI_FILE_CONTENT =
+            "some avg10=0.2 avg60=0.43 avg300=0.56\n"
+                    + "full avg10=0.21 avg60=0.43 avg300=0.65";
+
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mPsiExtractor = new PsiExtractor(mPsiReader);
+    }
+
+    @Test
+    public void getPsiData_bothLinesPresentedAndValidMemory() {
+        Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+                PSI_FILE_CONTENT_BOTH_LINES);
+        PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+        assertEquals(psiData.getResourceType(), PsiData.ResourceType.MEMORY);
+        assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+        assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+        assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+        assertEquals(psiData.getSomeTotalUsec(), 12345678);
+        assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+        assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+        assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+        assertEquals(psiData.getFullTotalUsec(), 87654321);
+    }
+
+    @Test
+    public void getPsiData_bothLinesPresentedAndValidCpu() {
+        Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+                PSI_FILE_CONTENT_BOTH_LINES);
+        PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+        assertEquals(psiData.getResourceType(), PsiData.ResourceType.CPU);
+        assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+        assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+        assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+        assertEquals(psiData.getSomeTotalUsec(), 12345678);
+        assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+        assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+        assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+        assertEquals(psiData.getFullTotalUsec(), 87654321);
+    }
+
+    @Test
+    public void getPsiData_bothLinesPresentedAndValidIO() {
+        Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+                PSI_FILE_CONTENT_BOTH_LINES);
+        PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+        assertEquals(psiData.getResourceType(), PsiData.ResourceType.IO);
+        assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+        assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+        assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+        assertEquals(psiData.getSomeTotalUsec(), 12345678);
+        assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+        assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+        assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+        assertEquals(psiData.getFullTotalUsec(), 87654321);
+    }
+
+    @Test
+    public void getPsiData_onlySomePresentedAndValidMemory() {
+        Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+                PSI_FILE_CONTENT_ONLY_SOME_LINE);
+        PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+        assertEquals(psiData.getResourceType(), PsiData.ResourceType.MEMORY);
+        assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+        assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+        assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+        assertEquals(psiData.getSomeTotalUsec(), 12345678);
+        assertEquals(psiData.getFullAvg10SecPercentage(), (float) -1.0);
+        assertEquals(psiData.getFullAvg60SecPercentage(), (float) -1.0);
+        assertEquals(psiData.getFullAvg300SecPercentage(), (float) -1.0);
+        assertEquals(psiData.getFullTotalUsec(), -1);
+    }
+
+    @Test
+    public void getPsiData_onlySomePresentedAndValidCpu() {
+        Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+                PSI_FILE_CONTENT_ONLY_SOME_LINE);
+        PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+        assertEquals(psiData.getResourceType(), PsiData.ResourceType.CPU);
+        assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+        assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+        assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+        assertEquals(psiData.getSomeTotalUsec(), 12345678);
+        assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.0);
+        assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.0);
+        assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.0);
+        assertEquals(psiData.getFullTotalUsec(), 0);
+    }
+
+    @Test
+    public void getPsiData_onlySomePresentedAndValidIO() {
+        Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+                PSI_FILE_CONTENT_ONLY_SOME_LINE);
+        PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+        assertEquals(psiData.getResourceType(), PsiData.ResourceType.IO);
+        assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+        assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+        assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+        assertEquals(psiData.getSomeTotalUsec(), 12345678);
+        assertEquals(psiData.getFullAvg10SecPercentage(), (float) -1.0);
+        assertEquals(psiData.getFullAvg60SecPercentage(), (float) -1.0);
+        assertEquals(psiData.getFullAvg300SecPercentage(), (float) -1.0);
+        assertEquals(psiData.getFullTotalUsec(), -1);
+    }
+
+    @Test
+    public void getPsiData_onlyFullPresentedAndValidMemory() {
+        Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+                PSI_FILE_CONTENT_ONLY_FULL_LINE);
+        PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+        assertEquals(psiData.getResourceType(), PsiData.ResourceType.MEMORY);
+        assertEquals(psiData.getSomeAvg10SecPercentage(), (float) -1.0);
+        assertEquals(psiData.getSomeAvg60SecPercentage(), (float) -1.0);
+        assertEquals(psiData.getSomeAvg300SecPercentage(), (float) -1.0);
+        assertEquals(psiData.getSomeTotalUsec(), -1);
+        assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+        assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+        assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+        assertEquals(psiData.getFullTotalUsec(), 87654321);
+    }
+
+    @Test
+    public void getPsiData_onlyFullPresentedAndValidCpu() {
+        Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+                PSI_FILE_CONTENT_ONLY_FULL_LINE);
+        PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+        assertEquals(psiData.getResourceType(), PsiData.ResourceType.CPU);
+        assertEquals(psiData.getSomeAvg10SecPercentage(), (float) -1.0);
+        assertEquals(psiData.getSomeAvg60SecPercentage(), (float) -1.0);
+        assertEquals(psiData.getSomeAvg300SecPercentage(), (float) -1.0);
+        assertEquals(psiData.getSomeTotalUsec(), -1);
+        assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+        assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+        assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+        assertEquals(psiData.getFullTotalUsec(), 87654321);
+    }
+
+    @Test
+    public void getPsiData_onlyFullPresentedAndValidIO() {
+        Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+                PSI_FILE_CONTENT_ONLY_FULL_LINE);
+        PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+        assertEquals(psiData.getResourceType(), PsiData.ResourceType.IO);
+        assertEquals(psiData.getSomeAvg10SecPercentage(), (float) -1.0);
+        assertEquals(psiData.getSomeAvg60SecPercentage(), (float) -1.0);
+        assertEquals(psiData.getSomeAvg300SecPercentage(), (float) -1.0);
+        assertEquals(psiData.getSomeTotalUsec(), -1);
+        assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+        assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+        assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+        assertEquals(psiData.getFullTotalUsec(), 87654321);
+    }
+
+    @Test
+    public void getPsiData_emptyFile() {
+        Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn("");
+        PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+        assertEquals(psiData, null);
+
+        Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn("");
+        psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+        assertEquals(psiData, null);
+
+        Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn("");
+        psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+        assertEquals(psiData, null);
+    }
+
+    @Test
+    public void getPsiData_avg60Missing() {
+        Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+                BOTH_AVG60_MISSING_PSI_FILE_CONTENT);
+        PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+        assertEquals(psiData, null);
+
+        Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+                BOTH_AVG60_MISSING_PSI_FILE_CONTENT);
+        psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+        assertEquals(psiData, null);
+
+        Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+                BOTH_AVG60_MISSING_PSI_FILE_CONTENT);
+        psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+        assertEquals(psiData, null);
+    }
+
+    @Test
+    public void getPsiData_totalMissing() {
+        Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+                BOTH_TOTAL_MISSING_PSI_FILE_CONTENT);
+        PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+        assertEquals(psiData, null);
+
+        Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+                BOTH_TOTAL_MISSING_PSI_FILE_CONTENT);
+        psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+        assertEquals(psiData, null);
+
+        Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+                BOTH_TOTAL_MISSING_PSI_FILE_CONTENT);
+        psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+        assertEquals(psiData, null);
+    }
+
+    @Test
+    public void getPsiData_avg10NonNum() {
+        Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+                NON_NUM_AVG10_PSI_FILE_CONTENT);
+        PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+        assertEquals(psiData, null);
+
+        Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+                NON_NUM_AVG10_PSI_FILE_CONTENT);
+        psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+        assertEquals(psiData, null);
+
+        Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+                NON_NUM_AVG10_PSI_FILE_CONTENT);
+        psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+        assertEquals(psiData, null);
+    }
+
+    @Test
+    public void getPsiData_avg300NonNum() {
+        Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+                NON_NUM_AVG300_PSI_FILE_CONTENT);
+        PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+        assertEquals(psiData, null);
+
+        Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+                NON_NUM_AVG300_PSI_FILE_CONTENT);
+        psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+        assertEquals(psiData, null);
+
+        Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+                NON_NUM_AVG300_PSI_FILE_CONTENT);
+        psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+        assertEquals(psiData, null);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
index 6bd4279..79b06236 100644
--- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -16,12 +16,20 @@
 
 package com.android.server.supervision
 
+import android.app.admin.DevicePolicyManagerInternal
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.UserInfo
 import android.os.Bundle
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.internal.R
 import com.android.server.LocalServices
+import com.android.server.SystemService.TargetUser
 import com.android.server.pm.UserManagerInternal
 import com.google.common.truth.Truth.assertThat
-import androidx.test.platform.app.InstrumentationRegistry
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -29,11 +37,12 @@
 import org.mockito.Mock
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.whenever
 
 /**
- * Unit tests for {@link SupervisionService}.
- * <p/>
- * Run with <code>atest SupervisionServiceTest</code>.
+ * Unit tests for [SupervisionService].
+ *
+ * Run with `atest SupervisionServiceTest`.
  */
 @RunWith(AndroidJUnit4::class)
 class SupervisionServiceTest {
@@ -41,18 +50,21 @@
         const val USER_ID = 100
     }
 
+    @get:Rule val mocks: MockitoRule = MockitoJUnit.rule()
+    @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+    @Mock private lateinit var mockDpmInternal: DevicePolicyManagerInternal
+    @Mock private lateinit var mockUserManagerInternal: UserManagerInternal
+
+    private lateinit var context: Context
     private lateinit var service: SupervisionService
 
-    @Rule
-    @JvmField
-    val mocks: MockitoRule = MockitoJUnit.rule()
-
-    @Mock
-    private lateinit var mockUserManagerInternal: UserManagerInternal
-
     @Before
-    fun setup() {
-        val context = InstrumentationRegistry.getInstrumentation().context
+    fun setUp() {
+        context = InstrumentationRegistry.getInstrumentation().context
+
+        LocalServices.removeServiceForTest(DevicePolicyManagerInternal::class.java)
+        LocalServices.addService(DevicePolicyManagerInternal::class.java, mockDpmInternal)
 
         LocalServices.removeServiceForTest(UserManagerInternal::class.java)
         LocalServices.addService(UserManagerInternal::class.java, mockUserManagerInternal)
@@ -61,7 +73,46 @@
     }
 
     @Test
-    fun testSetSupervisionEnabledForUser() {
+    @RequiresFlagsEnabled(android.app.admin.flags.Flags.FLAG_ENABLE_SUPERVISION_SERVICE_SYNC)
+    fun syncStateWithDevicePolicyManager_supervisionAppIsProfileOwner_enablesSupervision() {
+        val supervisionPackageName =
+            context.getResources().getString(R.string.config_systemSupervision)
+
+        whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+            .thenReturn(ComponentName(supervisionPackageName, "MainActivity"))
+
+        service.syncStateWithDevicePolicyManager(newTargetUser(USER_ID))
+
+        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.app.admin.flags.Flags.FLAG_ENABLE_SUPERVISION_SERVICE_SYNC)
+    fun syncStateWithDevicePolicyManager_userPreCreated_doesNotEnableSupervision() {
+        val supervisionPackageName =
+            context.getResources().getString(R.string.config_systemSupervision)
+
+        whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+            .thenReturn(ComponentName(supervisionPackageName, "MainActivity"))
+
+        service.syncStateWithDevicePolicyManager(newTargetUser(USER_ID, preCreated = true))
+
+        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.app.admin.flags.Flags.FLAG_ENABLE_SUPERVISION_SERVICE_SYNC)
+    fun syncStateWithDevicePolicyManager_supervisionAppIsNotProfileOwner_doesNotEnableSupervision() {
+        whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+            .thenReturn(ComponentName("other.package", "MainActivity"))
+
+        service.syncStateWithDevicePolicyManager(newTargetUser(USER_ID))
+
+        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+    }
+
+    @Test
+    fun setSupervisionEnabledForUser() {
         assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
 
         service.setSupervisionEnabledForUser(USER_ID, true)
@@ -72,7 +123,18 @@
     }
 
     @Test
-    fun testSetSupervisionLockscreenEnabledForUser() {
+    fun supervisionEnabledForUser_internal() {
+        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+
+        service.mInternal.setSupervisionEnabledForUser(USER_ID, true)
+        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+
+        service.mInternal.setSupervisionEnabledForUser(USER_ID, false)
+        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+    }
+
+    @Test
+    fun setSupervisionLockscreenEnabledForUser() {
         var userData = service.getUserDataLocked(USER_ID)
         assertThat(userData.supervisionLockScreenEnabled).isFalse()
         assertThat(userData.supervisionLockScreenOptions).isNull()
@@ -87,4 +149,10 @@
         assertThat(userData.supervisionLockScreenEnabled).isFalse()
         assertThat(userData.supervisionLockScreenOptions).isNull()
     }
+
+    private fun newTargetUser(userId: Int, preCreated: Boolean = false): TargetUser {
+        val userInfo = UserInfo(userId, /* name= */ "tempUser", /* flags= */ 0)
+        userInfo.preCreated = preCreated
+        return TargetUser(userInfo)
+    }
 }
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 eae587b..704c1b8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -210,6 +210,7 @@
 import android.app.RemoteInput;
 import android.app.RemoteInputHistoryItem;
 import android.app.StatsManager;
+import android.app.ZenBypassingApp;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.backup.BackupRestoreEventLogger;
 import android.app.job.JobScheduler;
@@ -360,6 +361,9 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
@@ -374,9 +378,6 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.function.Consumer;
 
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4.class)
 @RunWithLooper
@@ -489,7 +490,8 @@
     private final NotificationChannel mParentChannel =
             new NotificationChannel(PARENT_CHANNEL_ID, "parentName", IMPORTANCE_DEFAULT);
     private final NotificationChannel mConversationChannel =
-            new NotificationChannel(CONVERSATION_CHANNEL_ID, "conversationName", IMPORTANCE_DEFAULT);
+            new NotificationChannel(
+                    CONVERSATION_CHANNEL_ID, "conversationName", IMPORTANCE_DEFAULT);
 
     private static final String PARENT_CHANNEL_ID = "parentChannelId";
     private static final String CONVERSATION_CHANNEL_ID = "conversationChannelId";
@@ -4296,8 +4298,13 @@
                     new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
 
         Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
-        mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "testTvExtenderChannelOverride_onTv", 0,
-                generateNotificationRecord(null, tv).getNotification(), mUserId);
+        mBinderService.enqueueNotificationWithTag(
+                mPkg,
+                mPkg,
+                "testTvExtenderChannelOverride_onTv",
+                0,
+                generateNotificationRecord(null, tv).getNotification(),
+                mUserId);
         verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
                 anyString(), anyInt(), eq("foo"), eq(null), anyBoolean(), anyBoolean());
     }
@@ -4311,8 +4318,13 @@
                 mTestNotificationChannel);
 
         Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
-        mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "testTvExtenderChannelOverride_notOnTv",
-                0, generateNotificationRecord(null, tv).getNotification(), mUserId);
+        mBinderService.enqueueNotificationWithTag(
+                mPkg,
+                mPkg,
+                "testTvExtenderChannelOverride_notOnTv",
+                0,
+                generateNotificationRecord(null, tv).getNotification(),
+                mUserId);
         verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
                 anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null),
                 anyBoolean(), anyBoolean());
@@ -7745,9 +7757,21 @@
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
                 .setStyle(new Notification.MessagingStyle("").addMessage(message2));
-        NotificationRecord recordB = new NotificationRecord(mContext, new StatusBarNotification(mPkg,
-                mPkg, 0, "tag", mUid, 0, nbB.build(), UserHandle.getUserHandleForUid(mUid), null, 0),
-                c);
+        NotificationRecord recordB =
+                new NotificationRecord(
+                        mContext,
+                        new StatusBarNotification(
+                                mPkg,
+                                mPkg,
+                                0,
+                                "tag",
+                                mUid,
+                                0,
+                                nbB.build(),
+                                UserHandle.getUserHandleForUid(mUid),
+                                null,
+                                0),
+                        c);
 
         // Update means we drop access to first
         reset(mUgmInternal);
@@ -13174,6 +13198,37 @@
     }
 
     @Test
+    public void getPackagesBypassingDnd_blocked()
+            throws RemoteException, PackageManager.NameNotFoundException {
+
+        NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+                NotificationManager.IMPORTANCE_MAX);
+        NotificationChannel channel2 = new NotificationChannel("id3", "name3",
+                NotificationManager.IMPORTANCE_MAX);
+        NotificationChannel channel3 = new NotificationChannel("id4", "name3",
+                NotificationManager.IMPORTANCE_MAX);
+        channel1.setBypassDnd(true);
+        channel2.setBypassDnd(true);
+        channel3.setBypassDnd(false);
+        // has DND access, so can set bypassDnd attribute
+        mService.mPreferencesHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true,
+                /*has DND access*/ true, UID_N_MR1, false);
+        mService.mPreferencesHelper.createNotificationChannel(PKG_P, UID_P, channel2, true, true,
+                UID_P, false);
+        mService.mPreferencesHelper.createNotificationChannel(PKG_P, UID_P, channel3, true, true,
+                UID_P, false);
+
+        when(mPackageManager.getPackageUid(eq(PKG_P), anyLong(), anyInt())).thenReturn(UID_P);
+        when(mPackageManager.getPackageUid(eq(PKG_N_MR1), anyLong(), anyInt()))
+                .thenReturn(UID_N_MR1);
+        when(mPermissionHelper.hasPermission(UID_N_MR1)).thenReturn(false);
+        when(mPermissionHelper.hasPermission(UID_P)).thenReturn(true);
+
+        assertThat(mBinderService.getPackagesBypassingDnd(UserHandle.getUserId(UID_P)).getList())
+                .containsExactly(new ZenBypassingApp(PKG_P, false));
+    }
+
+    @Test
     public void testGetNotificationChannelsBypassingDnd_blocked() throws RemoteException {
         mService.setPreferencesHelper(mPreferencesHelper);
 
@@ -13187,125 +13242,11 @@
     @Test
     public void testGetPackagesBypassingDnd_empty() throws RemoteException {
         mService.setPreferencesHelper(mPreferencesHelper);
-        List<String> result = mBinderService.getPackagesBypassingDnd(mUserId, true);
+        List<String> result = mBinderService.getPackagesBypassingDnd(mUserId).getList();
         assertThat(result).isEmpty();
     }
 
     @Test
-    public void testGetPackagesBypassingDnd_excludeConversationChannels() throws RemoteException {
-        mService.setPreferencesHelper(mPreferencesHelper);
-
-        // Set packages
-        PackageInfo pkg0 = new PackageInfo();
-        pkg0.packageName = "pkg0";
-        pkg0.applicationInfo = new ApplicationInfo();
-        pkg0.applicationInfo.uid = mUid;
-        PackageInfo pkg1 = new PackageInfo();
-        pkg1.packageName = "pkg1";
-        pkg1.applicationInfo = new ApplicationInfo();
-        pkg1.applicationInfo.uid = mUid;
-        PackageInfo pkg2 = new PackageInfo();
-        pkg2.packageName = "pkg2";
-        pkg2.applicationInfo = new ApplicationInfo();
-        pkg2.applicationInfo.uid = mUid;
-
-        when(mPackageManagerClient.getInstalledPackagesAsUser(0, mUserId))
-                .thenReturn(List.of(pkg0, pkg1, pkg2));
-
-        // Conversation channels
-        NotificationChannel nc0 = new NotificationChannel("id0", "id0",
-                NotificationManager.IMPORTANCE_HIGH);
-        nc0.setConversationId("parentChannel", "conversationId");
-
-        // Demoted conversation channel
-        NotificationChannel nc1 = new NotificationChannel("id1", "id1",
-                NotificationManager.IMPORTANCE_HIGH);
-        nc1.setConversationId("parentChannel", "conversationId");
-        nc1.setDemoted(true);
-
-        // Non-conversation channels
-        NotificationChannel nc2 = new NotificationChannel("id2", "id2",
-                NotificationManager.IMPORTANCE_HIGH);
-        NotificationChannel nc3 = new NotificationChannel("id3", "id3",
-                NotificationManager.IMPORTANCE_HIGH);
-
-        ParceledListSlice<NotificationChannel> pls0 =
-                new ParceledListSlice(ImmutableList.of(nc0));
-        ParceledListSlice<NotificationChannel> pls1 =
-                new ParceledListSlice(ImmutableList.of(nc1));
-        ParceledListSlice<NotificationChannel> pls2 =
-                new ParceledListSlice(ImmutableList.of(nc2, nc3));
-
-        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg0", mUid))
-                .thenReturn(pls0);
-        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg1", mUid))
-                .thenReturn(pls1);
-        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg2", mUid))
-                .thenReturn(pls2);
-
-        List<String> result = mBinderService.getPackagesBypassingDnd(mUserId, false);
-
-        assertThat(result).containsExactly("pkg1", "pkg2");
-    }
-
-    @Test
-    public void testGetPackagesBypassingDnd_includeConversationChannels() throws RemoteException {
-        mService.setPreferencesHelper(mPreferencesHelper);
-
-        // Set packages
-        PackageInfo pkg0 = new PackageInfo();
-        pkg0.packageName = "pkg0";
-        pkg0.applicationInfo = new ApplicationInfo();
-        pkg0.applicationInfo.uid = mUid;
-        PackageInfo pkg1 = new PackageInfo();
-        pkg1.packageName = "pkg1";
-        pkg1.applicationInfo = new ApplicationInfo();
-        pkg1.applicationInfo.uid = mUid;
-        PackageInfo pkg2 = new PackageInfo();
-        pkg2.packageName = "pkg2";
-        pkg2.applicationInfo = new ApplicationInfo();
-        pkg2.applicationInfo.uid = mUid;
-
-        when(mPackageManagerClient.getInstalledPackagesAsUser(0, mUserId))
-                .thenReturn(List.of(pkg0, pkg1, pkg2));
-
-        // Conversation channels
-        NotificationChannel nc0 = new NotificationChannel("id0", "id0",
-                NotificationManager.IMPORTANCE_HIGH);
-        nc0.setConversationId("parentChannel", "conversationId");
-
-        // Demoted conversation channel
-        NotificationChannel nc1 = new NotificationChannel("id1", "id1",
-                NotificationManager.IMPORTANCE_HIGH);
-        nc1.setConversationId("parentChannel", "conversationId");
-        nc1.setDemoted(true);
-
-        // Non-conversation channels
-        NotificationChannel nc2 = new NotificationChannel("id2", "id2",
-                NotificationManager.IMPORTANCE_HIGH);
-        NotificationChannel nc3 = new NotificationChannel("id3", "id3",
-                NotificationManager.IMPORTANCE_HIGH);
-
-        ParceledListSlice<NotificationChannel> pls0 =
-                new ParceledListSlice(ImmutableList.of(nc0));
-        ParceledListSlice<NotificationChannel> pls1 =
-                new ParceledListSlice(ImmutableList.of(nc1));
-        ParceledListSlice<NotificationChannel> pls2 =
-                new ParceledListSlice(ImmutableList.of(nc2, nc3));
-
-        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg0", mUid))
-                .thenReturn(pls0);
-        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg1", mUid))
-                .thenReturn(pls1);
-        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg2", mUid))
-                .thenReturn(pls2);
-
-        List<String> result = mBinderService.getPackagesBypassingDnd(mUserId, true);
-
-        assertThat(result).containsExactly("pkg0", "pkg1", "pkg2");
-    }
-
-    @Test
     public void testMatchesCallFilter_noPermissionShouldThrow() throws Exception {
         // set the testable NMS to not system uid/appid
         mService.isSystemUid = false;
@@ -15482,8 +15423,13 @@
         for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) {
             StatusBarNotification sbn = generateNotificationRecord(mTestNotificationChannel,
                     i, null, false).getSbn();
-            mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "testCannotPostNonUijWhenOverLimit",
-                    sbn.getId(), sbn.getNotification(), sbn.getUserId());
+            mBinderService.enqueueNotificationWithTag(
+                    mPkg,
+                    mPkg,
+                    "testCannotPostNonUijWhenOverLimit",
+                    sbn.getId(),
+                    sbn.getNotification(),
+                    sbn.getUserId());
             waitForIdle();
         }
 
@@ -16213,6 +16159,8 @@
         initNMS(SystemService.PHASE_SYSTEM_SERVICES_READY);
 
         mInternalService.setDeviceEffectsApplier(mock(DeviceEffectsApplier.class));
+
+        mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START, mMainLooper);
         // No exception!
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index e1b478c..dda060d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -108,6 +108,7 @@
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
+import android.app.ZenBypassingApp;
 import android.content.AttributionSource;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
@@ -2620,6 +2621,72 @@
     }
 
     @Test
+    public void getPackagesBypassingDnd_noChannelsBypassing() throws Exception {
+        assertThat(mHelper.getPackagesBypassingDnd(UserHandle.getUserId(UID_N_MR1))).isEmpty();
+    }
+
+    @Test
+    public void getPackagesBypassingDnd_oneChannelBypassing_deleted() {
+        NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+                NotificationManager.IMPORTANCE_MAX);
+        channel1.setBypassDnd(true);
+        channel1.setDeleted(true);
+        // has DND access, so can set bypassDnd attribute
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true,
+                /*has DND access*/ true, UID_N_MR1, false);
+
+        assertThat(mHelper.getPackagesBypassingDnd(UserHandle.getUserId(UID_N_MR1))).isEmpty();
+    }
+
+    @Test
+    public void getPackagesBypassingDnd_oneChannelBypassing_groupBlocked() {
+        int uid = UID_N_MR1;
+        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+        NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+                NotificationManager.IMPORTANCE_MAX);
+        channel1.setBypassDnd(true);
+        channel1.setGroup(ncg.getId());
+        mHelper.createNotificationChannelGroup(PKG_N_MR1, uid, ncg,  /* fromTargetApp */ true,
+                uid, false);
+        mHelper.createNotificationChannel(PKG_N_MR1, uid, channel1, true, /*has DND access*/ true,
+                uid, false);
+        ncg.setBlocked(true);
+
+        assertThat(mHelper.getPackagesBypassingDnd(UserHandle.getUserId(uid))).isEmpty();
+    }
+
+    @Test
+    public void getPackagesBypassingDnd_multipleApps() {
+        List<ZenBypassingApp> expected = ImmutableList.of(
+                new ZenBypassingApp(PKG_O, true), new ZenBypassingApp(PKG_P, false));
+
+        NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+                NotificationManager.IMPORTANCE_MAX);
+        NotificationChannel channel2 = new NotificationChannel("id2", "name2",
+                NotificationManager.IMPORTANCE_MAX);
+        NotificationChannel channel3 = new NotificationChannel("id3", "name3",
+                NotificationManager.IMPORTANCE_MAX);
+        NotificationChannel channel4 = new NotificationChannel("id4", "name3",
+                NotificationManager.IMPORTANCE_MAX);
+        channel1.setBypassDnd(false);
+        channel2.setBypassDnd(true);
+        channel3.setBypassDnd(true);
+        channel4.setBypassDnd(false);
+        // has DND access, so can set bypassDnd attribute
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true,
+                /*has DND access*/ true, UID_N_MR1, false);
+        mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, true,
+                UID_O, false);
+        mHelper.createNotificationChannel(PKG_P, UID_P, channel3, true, true,
+                UID_P, false);
+        mHelper.createNotificationChannel(PKG_P, UID_P, channel4, true, true,
+                UID_P, false);
+
+        assertThat(mHelper.getPackagesBypassingDnd(UserHandle.getUserId(UID_O)))
+                .containsExactlyElementsIn(expected);
+    }
+
+    @Test
     public void testCreateAndDeleteCanChannelsBypassDnd_localSettings() {
         int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
         when(mPermissionHelper.hasPermission(uid)).thenReturn(true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 4b94e10..020670d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -49,6 +49,8 @@
 import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_STARRED;
 import static android.app.NotificationManager.Policy.STATE_PRIORITY_CHANNELS_BLOCKED;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_RULES;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Process.SYSTEM_UID;
 import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
@@ -84,8 +86,6 @@
 import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
 import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
 import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
-import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
-import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_RULES;
 import static com.android.server.notification.ZenModeEventLogger.ACTIVE_RULE_TYPE_MANUAL;
 import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
 
@@ -102,6 +102,7 @@
 
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -200,6 +201,9 @@
 import org.mockito.MockitoAnnotations;
 import org.xmlpull.v1.XmlPullParserException;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
@@ -219,9 +223,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
 @SmallTest
 @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
 @RunWith(ParameterizedAndroidJunit4.class)
@@ -5348,6 +5349,22 @@
         mTestableLooper.processAllMessages();
 
         verify(mDeviceEffectsApplier).apply(eq(effects), eq(ORIGIN_APP));
+        assertTrue(mZenModeHelper.hasDeviceEffectsApplier());
+    }
+
+    @Test
+    public void testHasDeviceEffectsApplier_returnsFalseIfNotSet() {
+        assertFalse(mZenModeHelper.hasDeviceEffectsApplier());
+    }
+
+    @Test
+    @EnableFlags(FLAG_MODES_API)
+    public void testSettingDeviceEffects_throwsExceptionIfAlreadySet() {
+        mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+
+        assertThrows(
+                IllegalStateException.class,
+                () -> mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index c3466b9..fee646d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3139,11 +3139,13 @@
 
     @Test
     public void testOnStartingWindowDrawn() {
+        // Skip unnecessary resume top.
+        mSupervisor.beginDeferResume();
         final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
         // The task-has-been-visible should not affect the decision of making transition ready.
         activity.getTask().setHasBeenVisible(true);
         activity.detachFromProcess();
-        activity.mStartingData = mock(StartingData.class);
+        activity.mStartingData = new SplashScreenStartingData(mWm, 0, 0);
         registerTestTransitionPlayer();
         final Transition transition = activity.mTransitionController.requestTransitionIfNeeded(
                 WindowManager.TRANSIT_OPEN, 0 /* flags */, null /* trigger */, mDisplayContent);
@@ -3151,7 +3153,11 @@
         assertTrue(activity.mStartingData.mIsDisplayed);
         // The transition can be ready by the starting window of a visible-requested activity
         // without a running process.
-        assertTrue(transition.allReady());
+        if (!transition.allReady()) {
+            // Print unsatisfied conditions.
+            transition.onReadyTimeout();
+            Assert.fail(transition + " must be ready by onStartingWindowDrawn");
+        }
 
         // If other event makes the transition unready, the reentrant of onStartingWindowDrawn
         // should not replace the readiness again.
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 62a4711..41f1e23 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -87,6 +87,7 @@
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.times;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
@@ -4782,6 +4783,114 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    public void testCameraCompatAspectRatioAppliedForFixedOrientationCameraActivities() {
+        // Needed to create camera compat policy in DisplayContent.
+        allowDesktopMode();
+        // Create display that has all stable insets and does not rotate.
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
+                .setSystemDecorations(true).setCanRotate(false).build();
+
+        final float cameraCompatAspectRatio = 4.0f;
+        setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);
+
+        // Create task on test display.
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+
+        // Create fixed portrait activity.
+        final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
+                .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).build();
+        final Rect fixedOrientationAppBounds = new Rect(fixedOrientationActivity.getConfiguration()
+                .windowConfiguration.getAppBounds());
+
+        assertEquals(cameraCompatAspectRatio, computeAspectRatio(fixedOrientationAppBounds),
+                DELTA_ASPECT_RATIO_TOLERANCE);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    public void testCameraCompatAspectRatioForFixedOrientationCameraActivitiesPortraitWindow() {
+        // Needed to create camera compat policy in DisplayContent.
+        allowDesktopMode();
+        // Create portrait display that has all stable insets and does not rotate.
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 1600)
+                .setSystemDecorations(true).setCanRotate(false).build();
+
+        final float cameraCompatAspectRatio = 4.0f;
+        setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);
+
+        // Create task on test display.
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+
+        // Create fixed portrait activity.
+        final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
+                .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).build();
+        final Rect fixedOrientationAppBounds = new Rect(fixedOrientationActivity.getConfiguration()
+                .windowConfiguration.getAppBounds());
+
+        assertEquals(cameraCompatAspectRatio, computeAspectRatio(fixedOrientationAppBounds),
+                DELTA_ASPECT_RATIO_TOLERANCE);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    public void testCameraCompatAspectRatioAppliedInsteadOfDefaultAspectRatio() {
+        // Needed to create camera compat policy in DisplayContent.
+        allowDesktopMode();
+        // Create display that has all stable insets and does not rotate.
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
+                .setSystemDecorations(true).setCanRotate(false).build();
+
+        final float cameraCompatAspectRatio = 5.0f;
+        setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);
+
+        // Create task on test display.
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+
+        // App's target min aspect ratio - this should not be used, as camera controls aspect ratio.
+        final float targetMinAspectRatio = 4.0f;
+
+        // Create fixed portrait activity with min aspect ratio greater than parent aspect ratio.
+        final ActivityRecord minAspectRatioActivity = new ActivityBuilder(mAtm)
+                .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+                .setMinAspectRatio(targetMinAspectRatio).build();
+        final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration()
+                .windowConfiguration.getAppBounds());
+
+        assertEquals(cameraCompatAspectRatio, computeAspectRatio(minAspectRatioAppBounds),
+                DELTA_ASPECT_RATIO_TOLERANCE);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    public void testCameraCompatAspectRatio_defualtAspectRatioAppliedWhenGreater() {
+        // Needed to create camera compat policy in DisplayContent.
+        allowDesktopMode();
+        // Create display that has all stable insets and does not rotate.
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
+                .setSystemDecorations(true).setCanRotate(false).build();
+
+        final float cameraCompatAspectRatio = 5.0f;
+        setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);
+
+        // Create task on test display.
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+
+        // App's target min aspect ratio bigger than camera compat aspect ratio - use that instead.
+        final float targetMinAspectRatio = 6.0f;
+
+        // Create fixed portrait activity with min aspect ratio greater than parent aspect ratio.
+        final ActivityRecord minAspectRatioActivity = new ActivityBuilder(mAtm)
+                .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+                .setMinAspectRatio(targetMinAspectRatio).build();
+        final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration()
+                .windowConfiguration.getAppBounds());
+
+        assertEquals(targetMinAspectRatio, computeAspectRatio(minAspectRatioAppBounds),
+                DELTA_ASPECT_RATIO_TOLERANCE);
+    }
+
+    @Test
     public void testUniversalResizeable() {
         mWm.mConstants.mIgnoreActivityOrientationRequest = true;
         setUpApp(mDisplayContent);
@@ -4868,6 +4977,25 @@
         assertEquals(newDensity, mActivity.getConfiguration().densityDpi);
     }
 
+    /**
+     * {@code canEnterDesktopMode} is called when {@link CameraCompatFreeformPolicy} is created in
+     * {@link AppCompatCameraPolicy}.
+     *
+     * <p>{@link #allowDesktopMode()} needs to be called before {@link DisplayContent} is created.
+     */
+    private void allowDesktopMode() {
+        doReturn(true).when(() -> DesktopModeHelper.canEnterDesktopMode(any()));
+    }
+
+    private void setupCameraCompatAspectRatio(float cameraCompatAspectRatio,
+            @NonNull DisplayContent display) {
+        CameraCompatFreeformPolicy cameraPolicy = display.mAppCompatCameraPolicy
+                .mCameraCompatFreeformPolicy;
+        spyOn(cameraPolicy);
+        doReturn(true).when(cameraPolicy).shouldCameraCompatControlAspectRatio(any());
+        doReturn(cameraCompatAspectRatio).when(cameraPolicy).getCameraCompatAspectRatio(any());
+    }
+
     private void setUpAllowThinLetterboxed(boolean thinLetterboxAllowed) {
         final AppCompatReachabilityOverrides reachabilityOverrides =
                 mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 1750a14..b737d35 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -353,6 +353,29 @@
     }
 
     @Test
+    public void testDestroySurface() {
+        final WindowState win = createWindow(null, TYPE_APPLICATION, "win");
+        win.mHasSurface = win.mAnimatingExit = true;
+        win.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
+        win.onExitAnimationDone();
+
+        assertFalse("Case 1 destroySurface no-op",
+                win.destroySurface(false /* cleanupOnResume */, false /* appStopped */));
+        assertTrue(win.mHasSurface);
+        assertTrue(win.mDestroying);
+
+        assertFalse("Case 2 destroySurface no-op",
+                win.destroySurface(true /* cleanupOnResume */, false /* appStopped */));
+        assertTrue(win.mHasSurface);
+        assertTrue(win.mDestroying);
+
+        assertTrue("Case 3 destroySurface destroys surface",
+                win.destroySurface(false /* cleanupOnResume */, true /* appStopped */));
+        assertFalse(win.mDestroying);
+        assertFalse(win.mHasSurface);
+    }
+
+    @Test
     public void testPrepareWindowToDisplayDuringRelayout() {
         // Call prepareWindowToDisplayDuringRelayout for a window without FLAG_TURN_SCREEN_ON before
         // calling setCurrentLaunchCanTurnScreenOn for windows with flag in the same activity.
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 7e3d99a..887b798 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -3511,6 +3511,40 @@
         }
     }
 
+    /**
+     * Inform whether application supports NTN SMS in satellite mode.
+     *
+     * This method is used by default messaging application to inform framework whether it supports
+     * NTN SMS or not.
+     *
+     * Invoking this API will internally result in triggering
+     * {@link android.telephony.TelephonyCallback.CarrierRoamingNtnModeListener
+     * #onCarrierRoamingNtnAvailableServicesChanged(List)} and
+     * {@link android.telephony.TelephonyCallback.CarrierRoamingNtnModeListener
+     * #onCarrierRoamingNtnEligibleStateChanged(boolean)} callbacks.
+     *
+     * @param ntnSmsSupported {@code true} If application supports NTN SMS, else {@code false}.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @hide
+     */
+    @RequiresPermission(allOf = {Manifest.permission.SATELLITE_COMMUNICATION,
+            Manifest.permission.SEND_SMS})
+    public void setNtnSmsSupported(boolean ntnSmsSupported) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.setNtnSmsSupported(ntnSmsSupported);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("setNtnSmsSupported() RemoteException:" + ex);
+            ex.rethrowAsRuntimeException();
+        }
+    }
+
     @Nullable
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 210200b..a584273 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3497,4 +3497,15 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
     void deprovisionSatellite(in List<SatelliteSubscriberInfo> list, in ResultReceiver result);
+
+   /**
+    * Inform whether application supports NTN SMS in satellite mode.
+    *
+    * This method is used by default messaging application to inform framework whether it supports
+    * NTN SMS or not.
+    *
+    * @param ntnSmsSupported {@code true} If application supports NTN SMS, else {@code false}.
+    * @hide
+    */
+    void setNtnSmsSupported(boolean ntnSmsSupported);
 }
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index ea61ad9..9f5e6d1 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -347,6 +347,14 @@
         waitForTransitionToFullscreen(wmHelper)
     }
 
+    /** Maximize an app by dragging the app handle to the top drag zone. */
+    fun maximizeAppWithDragToTopDragZone(
+        wmHelper: WindowManagerStateHelper,
+        device: UiDevice,
+    ) {
+        dragAppWindowToTopDragZone(wmHelper, device)
+    }
+
     private fun dragAppWindowToTopDragZone(wmHelper: WindowManagerStateHelper, device: UiDevice) {
         val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
         val displayRect = getDisplayRect(wmHelper)
diff --git a/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
index 58fb4e1..938e2f8 100644
--- a/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -19,6 +19,7 @@
 import android.animation.ValueAnimator
 import android.content.Context
 import android.content.ContextWrapper
+import android.content.res.Resources
 import android.graphics.Color
 import android.hardware.input.IKeyboardBacklightListener
 import android.hardware.input.IKeyboardBacklightState
@@ -28,11 +29,12 @@
 import android.os.test.TestLooper
 import android.platform.test.annotations.Presubmit
 import android.view.InputDevice
+import android.util.TypedValue
 import androidx.test.annotation.UiThreadTest
 import androidx.test.core.app.ApplicationProvider
+import com.android.internal.R
 import com.android.server.input.KeyboardBacklightController.DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL
 import com.android.server.input.KeyboardBacklightController.MAX_BRIGHTNESS_CHANGE_STEPS
-import com.android.server.input.KeyboardBacklightController.USER_INACTIVITY_THRESHOLD_MILLIS
 import com.android.test.input.MockInputManagerRule
 import java.io.FileNotFoundException
 import java.io.FileOutputStream
@@ -49,6 +51,7 @@
 import org.junit.Test
 import org.mockito.Mock
 import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.spy
@@ -94,6 +97,7 @@
         const val LIGHT_ID = 2
         const val SECOND_LIGHT_ID = 3
         const val MAX_BRIGHTNESS = 255
+        const val USER_INACTIVITY_THRESHOLD_MILLIS = 30000
     }
 
     @get:Rule
@@ -105,6 +109,8 @@
     private lateinit var native: NativeInputManagerService
     @Mock
     private lateinit var uEventManager: UEventManager
+    @Mock
+    private lateinit var resources: Resources
     private lateinit var keyboardBacklightController: KeyboardBacklightController
     private lateinit var context: Context
     private lateinit var dataStore: PersistentDataStore
@@ -117,6 +123,7 @@
     @Before
     fun setup() {
         context = spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        `when`(context.resources).thenReturn(resources)
         dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
             override fun openRead(): InputStream? {
                 throw FileNotFoundException()
@@ -129,6 +136,7 @@
             override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
         })
         testLooper = TestLooper()
+        setupConfig()
         keyboardBacklightController = KeyboardBacklightController(context, native, dataStore,
                 testLooper.looper, FakeAnimatorFactory(), uEventManager)
         val inputManager = InputManager(context)
@@ -147,7 +155,31 @@
             sysfsNodeChanges++
         }
     }
-
+    private fun setupConfig() {
+        val brightnessValues = intArrayOf(100, 200, 0)
+        val decreaseThresholds = intArrayOf(-1, 900, 1900)
+        val increaseThresholds = intArrayOf(1000, 2000, -1)
+        `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightBrightnessValues))
+            .thenReturn(brightnessValues)
+        `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightDecreaseLuxThreshold))
+            .thenReturn(decreaseThresholds)
+        `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightIncreaseLuxThreshold))
+            .thenReturn(increaseThresholds)
+        `when`(resources.getInteger(R.integer.config_keyboardBacklightTimeoutMs))
+            .thenReturn(USER_INACTIVITY_THRESHOLD_MILLIS)
+        `when`(
+            resources.getValue(
+                eq(R.dimen.config_autoKeyboardBrightnessSmoothingConstant),
+                any(TypedValue::class.java),
+                anyBoolean()
+            )
+        ).then {
+            val args = it.arguments
+            val outValue = args[1] as TypedValue
+            outValue.data = java.lang.Float.floatToRawIntBits(1.0f)
+            Unit
+        }
+    }
     @Test
     fun testKeyboardBacklightIncrementDecrement() {
         KeyboardBacklightFlags(
@@ -365,7 +397,7 @@
                 lightColorMap[LIGHT_ID]
             )
 
-            testLooper.moveTimeForward(USER_INACTIVITY_THRESHOLD_MILLIS + 1000)
+            testLooper.moveTimeForward((USER_INACTIVITY_THRESHOLD_MILLIS + 1000).toLong())
             testLooper.dispatchNext()
             assertEquals(
                 "Keyboard backlight level should be turned off after inactivity",
diff --git a/tests/broadcasts/OWNERS b/tests/broadcasts/OWNERS
deleted file mode 100644
index d2e1f81..0000000
--- a/tests/broadcasts/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 316181
-include platform/frameworks/base:/BROADCASTS_OWNERS
diff --git a/tests/broadcasts/unit/Android.bp b/tests/broadcasts/unit/Android.bp
deleted file mode 100644
index 47166a7..0000000
--- a/tests/broadcasts/unit/Android.bp
+++ /dev/null
@@ -1,45 +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 {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-    default_team: "trendy_team_framework_backstage_power",
-}
-
-android_test {
-    name: "BroadcastUnitTests",
-    srcs: ["src/**/*.java"],
-    defaults: [
-        "modules-utils-extended-mockito-rule-defaults",
-    ],
-    static_libs: [
-        "androidx.test.runner",
-        "androidx.test.rules",
-        "androidx.test.ext.junit",
-        "mockito-target-extended-minus-junit4",
-        "truth",
-        "flag-junit",
-        "android.app.flags-aconfig-java",
-    ],
-    certificate: "platform",
-    platform_apis: true,
-    test_suites: ["device-tests"],
-}
diff --git a/tests/broadcasts/unit/AndroidManifest.xml b/tests/broadcasts/unit/AndroidManifest.xml
deleted file mode 100644
index e9c5248..0000000
--- a/tests/broadcasts/unit/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.broadcasts.unit" >
-
-    <application android:debuggable="true">
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.broadcasts.unit"
-            android:label="Broadcasts Unit Tests"/>
-</manifest>
\ No newline at end of file
diff --git a/tests/broadcasts/unit/AndroidTest.xml b/tests/broadcasts/unit/AndroidTest.xml
deleted file mode 100644
index b91e4783..0000000
--- a/tests/broadcasts/unit/AndroidTest.xml
+++ /dev/null
@@ -1,29 +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.
--->
-<configuration description="Runs Broadcasts tests">
-    <option name="test-suite-tag" value="apct" />
-    <option name="test-tag" value="BroadcastUnitTests" />
-
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="BroadcastUnitTests.apk" />
-    </target_preparer>
-
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="com.android.broadcasts.unit" />
-        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
-        <option name="hidden-api-checks" value="false"/>
-    </test>
-</configuration>
\ No newline at end of file
diff --git a/tests/broadcasts/unit/TEST_MAPPING b/tests/broadcasts/unit/TEST_MAPPING
deleted file mode 100644
index 8919fdc..0000000
--- a/tests/broadcasts/unit/TEST_MAPPING
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-    "presubmit": [
-        {
-            "name": "BroadcastUnitTests",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                }
-            ]
-        }
-    ]
-}
diff --git a/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
deleted file mode 100644
index b7c412d..0000000
--- a/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.app;
-
-import static android.content.Intent.ACTION_BATTERY_CHANGED;
-import static android.content.Intent.ACTION_DEVICE_STORAGE_LOW;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.BatteryManager;
-import android.os.Bundle;
-import android.os.SystemProperties;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
-import android.util.ArrayMap;
-
-import androidx.annotation.GuardedBy;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.modules.utils.testing.ExtendedMockitoRule;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-@EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class BroadcastStickyCacheTest {
-    @ClassRule
-    public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
-
-    @Rule
-    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
-            .mockStatic(SystemProperties.class)
-            .build();
-
-    private static final String PROP_KEY_BATTERY_CHANGED = BroadcastStickyCache.getKey(
-            ACTION_BATTERY_CHANGED);
-
-    private final TestSystemProps mTestSystemProps = new TestSystemProps();
-
-    @Before
-    public void setUp() {
-        doAnswer(invocation -> {
-            final String name = invocation.getArgument(0);
-            final long value = Long.parseLong(invocation.getArgument(1));
-            mTestSystemProps.add(name, value);
-            return null;
-        }).when(() -> SystemProperties.set(anyString(), anyString()));
-        doAnswer(invocation -> {
-            final String name = invocation.getArgument(0);
-            final TestSystemProps.Handle testHandle = mTestSystemProps.query(name);
-            if (testHandle == null) {
-                return null;
-            }
-            final SystemProperties.Handle handle = Mockito.mock(SystemProperties.Handle.class);
-            doAnswer(handleInvocation -> testHandle.getLong(-1)).when(handle).getLong(anyLong());
-            return handle;
-        }).when(() -> SystemProperties.find(anyString()));
-    }
-
-    @After
-    public void tearDown() {
-        mTestSystemProps.clear();
-        BroadcastStickyCache.clearForTest();
-    }
-
-    @Test
-    public void testUseCache_nullFilter() {
-        assertThat(BroadcastStickyCache.useCache(null)).isEqualTo(false);
-    }
-
-    @Test
-    public void testUseCache_noActions() {
-        final IntentFilter filter = new IntentFilter();
-        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
-    }
-
-    @Test
-    public void testUseCache_multipleActions() {
-        final IntentFilter filter = new IntentFilter();
-        filter.addAction(ACTION_DEVICE_STORAGE_LOW);
-        filter.addAction(ACTION_BATTERY_CHANGED);
-        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
-    }
-
-    @Test
-    public void testUseCache_valueNotSet() {
-        final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
-        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
-    }
-
-    @Test
-    public void testUseCache() {
-        final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
-        final Intent intent = new Intent(ACTION_BATTERY_CHANGED)
-                .putExtra(BatteryManager.EXTRA_LEVEL, 90);
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-        BroadcastStickyCache.add(filter, intent);
-        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(true);
-    }
-
-    @Test
-    public void testUseCache_versionMismatch() {
-        final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
-        final Intent intent = new Intent(ACTION_BATTERY_CHANGED)
-                .putExtra(BatteryManager.EXTRA_LEVEL, 90);
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-        BroadcastStickyCache.add(filter, intent);
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-
-        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
-    }
-
-    @Test
-    public void testAdd() {
-        final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
-        Intent intent = new Intent(ACTION_BATTERY_CHANGED)
-                .putExtra(BatteryManager.EXTRA_LEVEL, 90);
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-        BroadcastStickyCache.add(filter, intent);
-        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(true);
-        Intent actualIntent = BroadcastStickyCache.getIntentUnchecked(filter);
-        assertThat(actualIntent).isNotNull();
-        assertEquals(actualIntent, intent);
-
-        intent = new Intent(ACTION_BATTERY_CHANGED)
-                .putExtra(BatteryManager.EXTRA_LEVEL, 99);
-        BroadcastStickyCache.add(filter, intent);
-        actualIntent = BroadcastStickyCache.getIntentUnchecked(filter);
-        assertThat(actualIntent).isNotNull();
-        assertEquals(actualIntent, intent);
-    }
-
-    @Test
-    public void testIncrementVersion_propExists() {
-        SystemProperties.set(PROP_KEY_BATTERY_CHANGED, String.valueOf(100));
-
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(101);
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(102);
-    }
-
-    @Test
-    public void testIncrementVersion_propNotExists() {
-        // Verify that the property doesn't exist
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
-
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(1);
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(2);
-    }
-
-    @Test
-    public void testIncrementVersionIfExists_propExists() {
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-
-        BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(2);
-        BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(3);
-    }
-
-    @Test
-    public void testIncrementVersionIfExists_propNotExists() {
-        // Verify that the property doesn't exist
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
-
-        BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
-        // Verify that property is not added as part of the querying.
-        BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
-    }
-
-    private void assertEquals(Intent actualIntent, Intent expectedIntent) {
-        assertThat(actualIntent.getAction()).isEqualTo(expectedIntent.getAction());
-        assertEquals(actualIntent.getExtras(), expectedIntent.getExtras());
-    }
-
-    private void assertEquals(Bundle actualExtras, Bundle expectedExtras) {
-        assertWithMessage("Extras expected=%s, actual=%s", expectedExtras, actualExtras)
-                .that(actualExtras.kindofEquals(expectedExtras)).isTrue();
-    }
-
-    private static final class TestSystemProps {
-        @GuardedBy("mSysProps")
-        private final ArrayMap<String, Long> mSysProps = new ArrayMap<>();
-
-        public void add(String name, long value) {
-            synchronized (mSysProps) {
-                mSysProps.put(name, value);
-            }
-        }
-
-        public long get(String name, long defaultValue) {
-            synchronized (mSysProps) {
-                final int idx = mSysProps.indexOfKey(name);
-                return idx >= 0 ? mSysProps.valueAt(idx) : defaultValue;
-            }
-        }
-
-        public Handle query(String name) {
-            synchronized (mSysProps) {
-                return mSysProps.containsKey(name) ? new Handle(name) : null;
-            }
-        }
-
-        public void clear() {
-            synchronized (mSysProps) {
-                mSysProps.clear();
-            }
-        }
-
-        public class Handle {
-            private final String mName;
-
-            Handle(String name) {
-                mName = name;
-            }
-
-            public long getLong(long defaultValue) {
-                return get(mName, defaultValue);
-            }
-        }
-    }
-}