Merge "Add a functional test for no-desktop-task-limit" into main
diff --git a/FF_LEADS_OWNERS b/FF_LEADS_OWNERS
new file mode 100644
index 0000000..a650c6b
--- /dev/null
+++ b/FF_LEADS_OWNERS
@@ -0,0 +1,10 @@
+bills@google.com
+carmenjackson@google.com
+nalini@google.com
+nosh@google.com
+olilan@google.com
+philipcuadra@google.com
+rajekumar@google.com
+shayba@google.com
+timmurray@google.com
+zezeozue@google.com
diff --git a/core/api/current.txt b/core/api/current.txt
index 2056056..f9d505c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -23,6 +23,7 @@
     field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
     field public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL";
     field public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS";
+    field @FlaggedApi("android.media.tv.flags.apply_picture_profiles") public static final String APPLY_PICTURE_PROFILE = "android.permission.APPLY_PICTURE_PROFILE";
     field public static final String BATTERY_STATS = "android.permission.BATTERY_STATS";
     field public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
     field public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
@@ -13568,6 +13569,7 @@
     field public static final String PROPERTY_MEDIA_CAPABILITIES = "android.media.PROPERTY_MEDIA_CAPABILITIES";
     field public static final String PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES = "android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES";
     field public static final String PROPERTY_SPECIAL_USE_FGS_SUBTYPE = "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE";
+    field @FlaggedApi("com.android.server.backup.enable_restricted_mode_changes") public static final String PROPERTY_USE_RESTRICTED_BACKUP_MODE = "android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE";
     field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff
     field public static final int SIGNATURE_MATCH = 0; // 0x0
     field public static final int SIGNATURE_NEITHER_SIGNED = 1; // 0x1
@@ -21069,7 +21071,6 @@
     method public abstract android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface();
     method public abstract android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
     method public boolean onGenericMotionEvent(android.view.MotionEvent);
-    method @FlaggedApi("android.view.inputmethod.verify_key_event") public boolean onShouldVerifyKeyEvent(@NonNull android.view.KeyEvent);
     method public boolean onTrackballEvent(android.view.MotionEvent);
   }
 
@@ -21087,7 +21088,6 @@
     method public void dispatchTrackballEvent(int, android.view.MotionEvent, android.view.inputmethod.InputMethodSession.EventCallback);
     method public boolean isEnabled();
     method public boolean isRevoked();
-    method @FlaggedApi("android.view.inputmethod.verify_key_event") public boolean onShouldVerifyKeyEvent(@NonNull android.view.KeyEvent);
     method public void revokeSelf();
     method public void setEnabled(boolean);
   }
@@ -29895,6 +29895,7 @@
     method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities();
     method public boolean hasGatewayOption(int);
     method @FlaggedApi("android.net.vcn.safe_mode_config") public boolean isSafeModeEnabled();
+    field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET = -1; // 0xffffffff
     field public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0; // 0x0
   }
 
@@ -40756,6 +40757,7 @@
     method public int describeContents();
     method @Deprecated @Nullable public android.os.Bundle getClientState();
     method @Nullable public java.util.List<android.service.autofill.FillEventHistory.Event> getEvents();
+    method @FlaggedApi("android.service.autofill.autofill_w_metrics") public int getSessionId();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.FillEventHistory> CREATOR;
   }
@@ -40765,10 +40767,12 @@
     method @Nullable public android.os.Bundle getClientState();
     method @Nullable public String getDatasetId();
     method @NonNull public java.util.Map<android.view.autofill.AutofillId,android.service.autofill.FieldClassification> getFieldsClassification();
+    method @FlaggedApi("android.service.autofill.autofill_w_metrics") @Nullable public android.view.autofill.AutofillId getFocusedId();
     method @NonNull public java.util.Set<java.lang.String> getIgnoredDatasetIds();
     method @NonNull public java.util.Map<android.view.autofill.AutofillId,java.util.Set<java.lang.String>> getManuallyEnteredField();
     method public int getNoSaveUiReason();
     method @NonNull public java.util.Set<java.lang.String> getSelectedDatasetIds();
+    method @FlaggedApi("android.service.autofill.autofill_w_metrics") @NonNull public java.util.Set<java.lang.String> getShownDatasetIds();
     method public int getType();
     method public int getUiType();
     field public static final int NO_SAVE_UI_REASON_DATASET_MATCH = 6; // 0x6
@@ -40777,6 +40781,7 @@
     field public static final int NO_SAVE_UI_REASON_NONE = 0; // 0x0
     field public static final int NO_SAVE_UI_REASON_NO_SAVE_INFO = 1; // 0x1
     field public static final int NO_SAVE_UI_REASON_NO_VALUE_CHANGED = 4; // 0x4
+    field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final int NO_SAVE_UI_REASON_USING_CREDMAN = 7; // 0x7
     field public static final int NO_SAVE_UI_REASON_WITH_DELAY_SAVE_FLAG = 2; // 0x2
     field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
     field public static final int TYPE_CONTEXT_COMMITTED = 4; // 0x4
@@ -40785,6 +40790,7 @@
     field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
     field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
     field public static final int TYPE_VIEW_REQUESTED_AUTOFILL = 6; // 0x6
+    field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final int UI_TYPE_CREDMAN = 4; // 0x4
     field public static final int UI_TYPE_DIALOG = 3; // 0x3
     field public static final int UI_TYPE_INLINE = 2; // 0x2
     field public static final int UI_TYPE_MENU = 1; // 0x1
@@ -42070,6 +42076,7 @@
 
   public abstract class QuickAccessWalletService extends android.app.Service {
     ctor public QuickAccessWalletService();
+    method @FlaggedApi("android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap") @Nullable public android.app.PendingIntent getGestureTargetActivityPendingIntent();
     method @Nullable public android.app.PendingIntent getTargetActivityPendingIntent();
     method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
     method public abstract void onWalletCardSelected(@NonNull android.service.quickaccesswallet.SelectWalletCardRequest);
@@ -49755,6 +49762,14 @@
     method public abstract void updateMeasureState(@NonNull android.text.TextPaint);
   }
 
+  @FlaggedApi("android.view.inputmethod.writing_tools") public final class NoWritingToolsSpan implements android.text.ParcelableSpan {
+    ctor public NoWritingToolsSpan();
+    method public int describeContents();
+    method public int getSpanTypeId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.text.style.NoWritingToolsSpan> CREATOR;
+  }
+
   public interface ParagraphStyle {
   }
 
@@ -54926,6 +54941,8 @@
     method public abstract void setTransformation(android.graphics.Matrix);
     method public abstract void setVisibility(int);
     method public abstract void setWebDomain(@Nullable String);
+    field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE";
+    field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER";
   }
 
   public abstract static class ViewStructure.HtmlInfo {
@@ -56648,6 +56665,11 @@
   public final class AutofillId implements android.os.Parcelable {
     method @NonNull public static android.view.autofill.AutofillId create(@NonNull android.view.View, int);
     method public int describeContents();
+    method @FlaggedApi("android.service.autofill.autofill_w_metrics") public int getAutofillVirtualId();
+    method @FlaggedApi("android.service.autofill.autofill_w_metrics") public int getSessionId();
+    method @FlaggedApi("android.service.autofill.autofill_w_metrics") public int getViewId();
+    method @FlaggedApi("android.service.autofill.autofill_w_metrics") public boolean isInAutofillSession();
+    method @FlaggedApi("android.service.autofill.autofill_w_metrics") public boolean isVirtual();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.autofill.AutofillId> CREATOR;
   }
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index 4ada53e..ad5bd31 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -1,4 +1,10 @@
 // Baseline format: 1.0
+ActionValue: android.view.ViewStructure#EXTRA_VIRTUAL_STRUCTURE_TYPE:
+    Inconsistent extra value; expected `android.view.extra.VIRTUAL_STRUCTURE_TYPE`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE`
+ActionValue: android.view.ViewStructure#EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER:
+    Inconsistent extra value; expected `android.view.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`
+
+
 BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED:
     Field 'ACTION_NEXT_ALARM_CLOCK_CHANGED' is missing @BroadcastBehavior
 BroadcastBehavior: android.app.AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED:
@@ -1185,6 +1191,10 @@
     New API must be flagged with @FlaggedApi: field android.R.dimen.system_corner_radius_xlarge
 UnflaggedApi: android.R.dimen#system_corner_radius_xsmall:
     New API must be flagged with @FlaggedApi: field android.R.dimen.system_corner_radius_xsmall
+UnflaggedApi: android.R.integer#status_bar_notification_info_maxnum:
+    Changes from not deprecated to deprecated must be flagged with @FlaggedApi: field android.R.integer.status_bar_notification_info_maxnum
+UnflaggedApi: android.R.string#status_bar_notification_info_overflow:
+    Changes from not deprecated to deprecated must be flagged with @FlaggedApi: field android.R.string.status_bar_notification_info_overflow
 UnflaggedApi: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INTERNAL_ERROR:
     New API must be flagged with @FlaggedApi: field android.accessibilityservice.AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR
 UnflaggedApi: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INVALID:
@@ -1477,7 +1487,6 @@
     New API must be flagged with @FlaggedApi: method android.graphics.text.PositionedGlyphs.getItalicOverride(int)
 UnflaggedApi: android.graphics.text.PositionedGlyphs#getWeightOverride(int):
     New API must be flagged with @FlaggedApi: method android.graphics.text.PositionedGlyphs.getWeightOverride(int)
-
 UnflaggedApi: android.media.MediaRoute2Info#TYPE_REMOTE_CAR:
     New API must be flagged with @FlaggedApi: field android.media.MediaRoute2Info.TYPE_REMOTE_CAR
 UnflaggedApi: android.media.MediaRoute2Info#TYPE_REMOTE_COMPUTER:
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index dcb4a40..1a949d84 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -251,6 +251,10 @@
 
 package android.net {
 
+  @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class ConnectivityFrameworkInitializerBaklava {
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static void registerServiceWrappers();
+  }
+
   public class LocalSocket implements java.io.Closeable {
     ctor public LocalSocket(@NonNull java.io.FileDescriptor);
   }
@@ -310,6 +314,25 @@
 
 }
 
+package android.net.vcn {
+
+  @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class VcnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int describeContents();
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public long getApplicableRedactions();
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int getMinUdpPort4500NatTimeoutSeconds();
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.TransportInfo makeCopy(long);
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnTransportInfo> CREATOR;
+  }
+
+  @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final class VcnTransportInfo.Builder {
+    ctor @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public VcnTransportInfo.Builder();
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo build();
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
+  }
+
+}
+
 package android.net.wifi {
 
   public final class WifiMigration {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a46f872..e4f1582 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -57,6 +57,7 @@
     field @Deprecated public static final String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE";
     field public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE";
     field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE";
+    field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final String BIND_DEPENDENCY_INSTALLER = "android.permission.BIND_DEPENDENCY_INSTALLER";
     field public static final String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
     field public static final String BIND_DISPLAY_HASHING_SERVICE = "android.permission.BIND_DISPLAY_HASHING_SERVICE";
     field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String BIND_DOMAIN_SELECTION_SERVICE = "android.permission.BIND_DOMAIN_SELECTION_SERVICE";
@@ -165,6 +166,7 @@
     field public static final String HDMI_CEC = "android.permission.HDMI_CEC";
     field @Deprecated public static final String HIDE_NON_SYSTEM_OVERLAY_WINDOWS = "android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS";
     field public static final String INJECT_EVENTS = "android.permission.INJECT_EVENTS";
+    field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final String INSTALL_DEPENDENCY_SHARED_LIBRARIES = "android.permission.INSTALL_DEPENDENCY_SHARED_LIBRARIES";
     field public static final String INSTALL_DPC_PACKAGES = "android.permission.INSTALL_DPC_PACKAGES";
     field public static final String INSTALL_DYNAMIC_SYSTEM = "android.permission.INSTALL_DYNAMIC_SYSTEM";
     field public static final String INSTALL_EXISTING_PACKAGES = "com.android.permission.INSTALL_EXISTING_PACKAGES";
@@ -531,6 +533,7 @@
     field public static final int config_systemCallStreaming = 17039431; // 0x1040047
     field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039
     field public static final int config_systemContacts = 17039403; // 0x104002b
+    field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final int config_systemDependencyInstaller;
     field public static final int config_systemFinancedDeviceController = 17039430; // 0x1040046
     field public static final int config_systemGallery = 17039399; // 0x1040027
     field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035
@@ -1339,8 +1342,10 @@
   public class DevicePolicyManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPrecondition(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback();
-    method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
+    method @Deprecated @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
+    method @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
     method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
+    method @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeCreateManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams, @NonNull android.os.UserHandle) throws android.app.admin.ProvisioningException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public java.util.Set<java.lang.Integer> getApplicationExemptions(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
@@ -1935,6 +1940,7 @@
     method public android.os.IBinder getBinder();
     method public long getCurrentRestoreSet();
     method public int getNextFullRestoreDataChunk(android.os.ParcelFileDescriptor);
+    method @FlaggedApi("com.android.server.backup.enable_restricted_mode_changes") @NonNull public java.util.List<java.lang.String> getPackagesThatShouldNotUseRestrictedMode(@NonNull java.util.List<java.lang.String>, int);
     method public int getRestoreData(android.os.ParcelFileDescriptor);
     method public int getTransportFlags();
     method public int initializeDevice();
@@ -5220,6 +5226,19 @@
     method @NonNull public android.hardware.contexthub.HubEndpointInfo getHubEndpointInfo();
   }
 
+  @FlaggedApi("android.chre.flags.offload_api") public class HubEndpoint {
+    method @Nullable public android.hardware.contexthub.IHubEndpointLifecycleCallback getLifecycleCallback();
+    method @Nullable public String getTag();
+  }
+
+  public static final class HubEndpoint.Builder {
+    ctor public HubEndpoint.Builder(@NonNull android.content.Context);
+    method @NonNull public android.hardware.contexthub.HubEndpoint build();
+    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback);
+    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback);
+    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setTag(@NonNull String);
+  }
+
   @FlaggedApi("android.chre.flags.offload_api") public final class HubEndpointInfo implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.hardware.contexthub.HubEndpointInfo.HubEndpointIdentifier getIdentifier();
@@ -5234,6 +5253,26 @@
     method public long getHub();
   }
 
+  @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable {
+    method public void close();
+  }
+
+  @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSessionResult {
+    method @NonNull public static android.hardware.contexthub.HubEndpointSessionResult accept();
+    method @Nullable public String getReason();
+    method public boolean isAccepted();
+    method @NonNull public static android.hardware.contexthub.HubEndpointSessionResult reject(@NonNull String);
+  }
+
+  @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback {
+    method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
+    method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo);
+    method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession);
+    field public static final int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4; // 0x4
+    field public static final int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3; // 0x3
+    field public static final int REASON_UNSPECIFIED = 0; // 0x0
+  }
+
 }
 
 package android.hardware.devicestate {
@@ -6235,13 +6274,16 @@
     method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.NanoAppInstanceInfo getNanoAppInstanceInfo(int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int loadNanoApp(int, @NonNull android.hardware.location.NanoApp);
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> loadNanoApp(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.hardware.location.NanoAppBinary);
+    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo);
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.util.List<android.hardware.location.NanoAppState>> queryNanoApps(@NonNull android.hardware.location.ContextHubInfo);
     method @Deprecated public int registerCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
     method @Deprecated public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler);
+    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpoint(@NonNull android.hardware.contexthub.HubEndpoint);
     method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessage(int, int, @NonNull android.hardware.location.ContextHubMessage);
     method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int unloadNanoApp(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
     method @Deprecated public int unregisterCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
+    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpoint(@NonNull android.hardware.contexthub.HubEndpoint);
     field public static final int AUTHORIZATION_DENIED = 0; // 0x0
     field public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; // 0x1
     field public static final int AUTHORIZATION_GRANTED = 2; // 0x2
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1192713..136c636 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3240,6 +3240,7 @@
     method @Nullable public android.content.Intent createWalletIntent();
     method @Nullable public android.content.Intent createWalletSettingsIntent();
     method public void disconnect();
+    method @FlaggedApi("android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap") public void getGestureTargetActivityPendingIntent(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.GesturePendingIntentCallback);
     method public void getWalletCards(@NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.OnWalletCardsRetrievedCallback);
     method public void getWalletCards(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.OnWalletCardsRetrievedCallback);
     method public void getWalletPendingIntent(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.WalletPendingIntentCallback);
@@ -3251,6 +3252,10 @@
     method public void selectWalletCard(@NonNull android.service.quickaccesswallet.SelectWalletCardRequest);
   }
 
+  @FlaggedApi("android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap") public static interface QuickAccessWalletClient.GesturePendingIntentCallback {
+    method @FlaggedApi("android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap") public void onGesturePendingIntentRetrieved(@Nullable android.app.PendingIntent);
+  }
+
   public static interface QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
     method public void onWalletCardRetrievalError(@NonNull android.service.quickaccesswallet.GetWalletCardsError);
     method public void onWalletCardsRetrieved(@NonNull android.service.quickaccesswallet.GetWalletCardsResponse);
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 08bb082..c2fac70 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -1,4 +1,10 @@
 // Baseline format: 1.0
+ActionValue: android.view.contentcapture.ViewNode.ViewStructureImpl#EXTRA_VIRTUAL_STRUCTURE_TYPE:
+    Inconsistent extra value; expected `android.view.contentcapture.extra.VIRTUAL_STRUCTURE_TYPE`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE`
+ActionValue: android.view.contentcapture.ViewNode.ViewStructureImpl#EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER:
+    Inconsistent extra value; expected `android.view.contentcapture.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`
+
+
 BannedThrow: android.os.vibrator.persistence.VibrationXmlSerializer#serialize(android.os.VibrationEffect, java.io.Writer):
     Methods must not throw unchecked exceptions
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 6c03b32..ce0ec60 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1628,9 +1628,15 @@
     public static final int OP_WRITE_SYSTEM_PREFERENCES =
             AppOpEnums.APP_OP_WRITE_SYSTEM_PREFERENCES;
 
+    /** @hide Access to audio playback and control APIs. */
+    public static final int OP_CONTROL_AUDIO = AppOpEnums.APP_OP_CONTROL_AUDIO;
+
+    /** @hide Similar to {@link OP_CONTROL_AUDIO}, but doesn't require capabilities. */
+    public static final int OP_CONTROL_AUDIO_PARTIAL = AppOpEnums.APP_OP_CONTROL_AUDIO_PARTIAL;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 154;
+    public static final int _NUM_OP = 156;
 
     /**
      * All app ops represented as strings.
@@ -1788,6 +1794,8 @@
             OPSTR_RANGING,
             OPSTR_READ_OXYGEN_SATURATION,
             OPSTR_WRITE_SYSTEM_PREFERENCES,
+            OPSTR_CONTROL_AUDIO,
+            OPSTR_CONTROL_AUDIO_PARTIAL,
     })
     public @interface AppOpString {}
 
@@ -2548,6 +2556,12 @@
     /** @hide Access to system preferences write services */
     public static final String OPSTR_WRITE_SYSTEM_PREFERENCES = "android:write_system_preferences";
 
+    /** @hide Access to audio playback and control APIs */
+    public static final String OPSTR_CONTROL_AUDIO = "android:control_audio";
+
+    /** @hide Access to a audio playback and control APIs without capability requirements */
+    public static final String OPSTR_CONTROL_AUDIO_PARTIAL = "android:control_audio_partial";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -3157,6 +3171,10 @@
             "WRITE_SYSTEM_PREFERENCES").setPermission(
                      com.android.settingslib.flags.Flags.writeSystemPreferencePermissionEnabled()
                      ? Manifest.permission.WRITE_SYSTEM_PREFERENCES : null).build(),
+        new AppOpInfo.Builder(OP_CONTROL_AUDIO, OPSTR_CONTROL_AUDIO,
+                "CONTROL_AUDIO").setDefaultMode(AppOpsManager.MODE_FOREGROUND).build(),
+        new AppOpInfo.Builder(OP_CONTROL_AUDIO_PARTIAL, OPSTR_CONTROL_AUDIO_PARTIAL,
+                "CONTROL_AUDIO_PARTIAL").setDefaultMode(AppOpsManager.MODE_FOREGROUND).build(),
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index e2479169..7e0a9b6 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
 import static android.app.PropertyInvalidatedCache.createSystemCacheKey;
 import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
 import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_NOT_COLORED;
@@ -783,43 +784,24 @@
     }
 
     /**
+     * The API and cache name for hasSystemFeature.
+     */
+    private static final String HAS_SYSTEM_FEATURE_API = "has_system_feature";
+
+    /**
      * Identifies a single hasSystemFeature query.
      */
-    @Immutable
-    private static final class HasSystemFeatureQuery {
-        public final String name;
-        public final int version;
-        public HasSystemFeatureQuery(String n, int v) {
-            name = n;
-            version = v;
-        }
-        @Override
-        public String toString() {
-            return String.format("HasSystemFeatureQuery(name=\"%s\", version=%d)",
-                    name, version);
-        }
-        @Override
-        public boolean equals(@Nullable Object o) {
-            if (o instanceof HasSystemFeatureQuery) {
-                HasSystemFeatureQuery r = (HasSystemFeatureQuery) o;
-                return Objects.equals(name, r.name) &&  version == r.version;
-            } else {
-                return false;
-            }
-        }
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(name) * 13 + version;
-        }
-    }
+    private record HasSystemFeatureQuery(String name, int version) {}
 
     // Make this cache relatively large.  There are many system features and
     // none are ever invalidated.  MPTS tests suggests that the cache should
     // hold at least 150 entries.
     private final static PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean>
-            mHasSystemFeatureCache =
-            new PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean>(
-                256, createSystemCacheKey("has_system_feature")) {
+            mHasSystemFeatureCache = new PropertyInvalidatedCache<>(
+                new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
+                .api(HAS_SYSTEM_FEATURE_API).maxEntries(256).isolateUids(false),
+                HAS_SYSTEM_FEATURE_API, null) {
+
                 @Override
                 public Boolean recompute(HasSystemFeatureQuery query) {
                     try {
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 9615015..7a329cd 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -112,8 +112,8 @@
     oneway void requestMultiwindowFullscreen(in IBinder token, in int request,
             in IRemoteCallback callback);
 
-    oneway void startLockTaskModeByToken(in IBinder token);
-    oneway void stopLockTaskModeByToken(in IBinder token);
+    void startLockTaskModeByToken(in IBinder token);
+    void stopLockTaskModeByToken(in IBinder token);
     oneway void showLockTaskEscapeMessage(in IBinder token);
     void setTaskDescription(in IBinder token, in ActivityManager.TaskDescription values);
 
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 34a3ad1..a8412fa 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -358,7 +358,7 @@
     @UnsupportedAppUsage
     void resumeAppSwitches();
     boolean bindBackupAgent(in String packageName, int backupRestoreMode, int targetUserId,
-            int backupDestination);
+            int backupDestination, boolean useRestrictedMode);
     void backupAgentCreated(in String packageName, in IBinder agent, int userId);
     void unbindBackupAgent(in ApplicationInfo appInfo);
     int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 1dc7742..675152f 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -1947,10 +1947,12 @@
     }
 
     // Return true if this cache has had any activity.  If the hits, misses, and skips are all
-    // zero then the client never tried to use the cache.
-    private boolean isActive() {
+    // zero then the client never tried to use the cache.  If invalidations and corks are also
+    // zero then the server never tried to use the cache.
+    private boolean isActive(NonceHandler.Stats stats) {
         synchronized (mLock) {
-            return mHits + mMisses + getSkipsLocked() > 0;
+            return mHits + mMisses + getSkipsLocked()
+                    + stats.invalidated + stats.corkedInvalidates > 0;
         }
     }
 
@@ -1968,15 +1970,15 @@
         NonceHandler.Stats stats = mNonce.getStats();
 
         synchronized (mLock) {
-            if (brief && !isActive()) {
+            if (brief && !isActive(stats)) {
                 return;
             }
 
             pw.println(formatSimple("  Cache Name: %s", cacheName()));
             pw.println(formatSimple("    Property: %s", mPropertyName));
             pw.println(formatSimple(
-                "    Hits: %d, Misses: %d, Skips: %d, Clears: %d, Uids: %d",
-                mHits, mMisses, getSkipsLocked(), mClears, mCache.size()));
+                "    Hits: %d, Misses: %d, Skips: %d, Clears: %d",
+                mHits, mMisses, getSkipsLocked(), mClears));
 
             // Print all the skip reasons.
             pw.format("    Skip-%s: %d", sNonceName[0], mSkips[0]);
@@ -1986,7 +1988,7 @@
             pw.println();
 
             pw.println(formatSimple(
-                "    Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d",
+                "    Nonce: 0x%016x, Invalidates: %d, Corked: %d",
                 mLastSeenNonce, stats.invalidated, stats.corkedInvalidates));
             pw.println(formatSimple(
                 "    Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d",
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 8b8fdef..a063917 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -163,6 +163,7 @@
 import android.media.tv.tunerresourcemanager.TunerResourceManager;
 import android.nearby.NearbyFrameworkInitializer;
 import android.net.ConnectivityFrameworkInitializer;
+import android.net.ConnectivityFrameworkInitializerBaklava;
 import android.net.ConnectivityFrameworkInitializerTiramisu;
 import android.net.INetworkPolicyManager;
 import android.net.IPacProxyManager;
@@ -173,7 +174,6 @@
 import android.net.PacProxyManager;
 import android.net.TetheringManager;
 import android.net.VpnManager;
-import android.net.vcn.VcnFrameworkInitializer;
 import android.net.wifi.WifiFrameworkInitializer;
 import android.net.wifi.nl80211.WifiNl80211Manager;
 import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager;
@@ -1835,7 +1835,7 @@
             OnDevicePersonalizationFrameworkInitializer.registerServiceWrappers();
             DeviceLockFrameworkInitializer.registerServiceWrappers();
             VirtualizationFrameworkInitializer.registerServiceWrappers();
-            VcnFrameworkInitializer.registerServiceWrappers();
+            ConnectivityFrameworkInitializerBaklava.registerServiceWrappers();
 
             if (com.android.server.telecom.flags.Flags.telecomMainlineBlockedNumbersManager()) {
                 ProviderFrameworkInitializer.registerServiceWrappers();
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4e68b5a..e766ae2 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16,6 +16,7 @@
 
 package android.app.admin;
 
+import static android.app.admin.flags.Flags.FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.LOCK_DEVICE;
@@ -17196,11 +17197,14 @@
      * @throws SecurityException if the caller does not hold
      * {@link android.Manifest.permission#MANAGE_PROFILE_AND_DEVICE_OWNERS}.
      * @throws ProvisioningException if an error occurred during provisioning.
+     * @deprecated Use {@link #createManagedProfile} and {@link #finalizeCreateManagedProfile}
      * @hide
      */
     @Nullable
     @SystemApi
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
     public UserHandle createAndProvisionManagedProfile(
             @NonNull ManagedProfileProvisioningParams provisioningParams)
             throws ProvisioningException {
@@ -17218,6 +17222,69 @@
     }
 
     /**
+     * Creates a managed profile and sets the
+     * {@link ManagedProfileProvisioningParams#getProfileAdminComponentName()} as the profile
+     * owner. The method {@link #finalizeCreateManagedProfile} must be called after to finalize the
+     * creation of the managed profile.
+     *
+     * <p>The method {@link #checkProvisioningPrecondition} must return {@link #STATUS_OK}
+     * before calling this method. If it doesn't, a ProvisioningException will be thrown.
+     *
+     * @param provisioningParams Params required to provision a managed profile,
+     * see {@link ManagedProfileProvisioningParams}.
+     * @return The {@link UserHandle} of the created profile or {@code null} if the service is
+     * not available.
+     * @throws ProvisioningException if an error occurred during provisioning.
+     * @hide
+     */
+    @Nullable
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
+    public UserHandle createManagedProfile(
+            @NonNull ManagedProfileProvisioningParams provisioningParams)
+            throws ProvisioningException {
+        if (mService == null) {
+            return null;
+        }
+        try {
+            return mService.createManagedProfile(provisioningParams, mContext.getPackageName());
+        } catch (ServiceSpecificException e) {
+            throw new ProvisioningException(e, e.errorCode, getErrorMessage(e));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Finalizes the creation of a managed profile by informing the necessary components that
+     * the managed profile is ready.
+     *
+     * @param provisioningParams Params required to provision a managed profile,
+     * see {@link ManagedProfileProvisioningParams}.
+     * @param managedProfileUser The recently created managed profile.
+     * @throws ProvisioningException if an error occurred during provisioning.
+     * @hide
+     */
+    @SuppressLint("UserHandle")
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
+    public void finalizeCreateManagedProfile(
+            @NonNull ManagedProfileProvisioningParams provisioningParams,
+            @NonNull UserHandle managedProfileUser)
+            throws ProvisioningException {
+        if (mService == null) {
+            return;
+        }
+        try {
+            mService.finalizeCreateManagedProfile(provisioningParams, managedProfileUser);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * 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
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index fa984af..d048b53 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -570,6 +570,8 @@
     void setOrganizationIdForUser(in String callerPackage, in String enterpriseId, int userId);
 
     UserHandle createAndProvisionManagedProfile(in ManagedProfileProvisioningParams provisioningParams, in String callerPackage);
+    UserHandle createManagedProfile(in ManagedProfileProvisioningParams provisioningParams, in String callerPackage);
+    void finalizeCreateManagedProfile(in ManagedProfileProvisioningParams provisioningParams, in UserHandle managedProfileUser);
     void provisionFullyManagedDevice(in FullyManagedDeviceProvisioningParams provisioningParams, in String callerPackage);
 
     void finalizeWorkProfileProvisioning(in UserHandle managedProfileUser, in Account migratedAccount);
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 404471e..581efa5 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -343,6 +343,16 @@
 }
 
 flag {
+    name: "active_admin_cleanup"
+    namespace: "enterprise"
+    description: "Remove ActiveAdmin from EnforcingAdmin and related cleanups"
+    bug: "335663055"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "user_provisioning_same_state"
     namespace: "enterprise"
     description: "Handle exceptions while setting same provisioning state."
@@ -372,7 +382,7 @@
     is_exported: true
     namespace: "enterprise"
     description: "Allows DPMS to enable or disable SupervisionService based on whether the device is being managed by the supervision role holder."
-    bug: "376213673"
+    bug: "358134581"
 }
 
 flag {
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index dcac59c..5004c02 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -16,6 +16,8 @@
 
 package android.app.backup;
 
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.Intent;
@@ -27,6 +29,7 @@
 import com.android.internal.backup.IBackupTransport;
 import com.android.internal.backup.ITransportStatusCallback;
 import com.android.internal.infra.AndroidFuture;
+import com.android.server.backup.Flags;
 
 import java.util.Arrays;
 import java.util.List;
@@ -671,6 +674,22 @@
     }
 
     /**
+     * Ask the transport whether packages that are about to be backed up or restored should not be
+     * put into a restricted mode by the framework and started normally instead.
+     *
+     * @param operationType 0 for backup, 1 for restore.
+     * @return a subset of the {@code packageNames} passed in, indicating
+     * which packages should NOT be put into restricted mode for the given operation type.
+     */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+    public List<String> getPackagesThatShouldNotUseRestrictedMode(
+            @NonNull List<String> packageNames,
+            @BackupAnnotations.OperationType int operationType) {
+        return List.of();
+    }
+
+    /**
      * Bridge between the actual IBackupTransport implementation and the stable API.  If the
      * binder interface needs to change, we use this layer to translate so that we can
      * (if appropriate) decouple those framework-side changes from the BackupTransport
@@ -977,5 +996,19 @@
                 resultFuture.cancel(/* mayInterruptIfRunning */ true);
             }
         }
+
+        @Override
+        @FlaggedApi(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+        public void getPackagesThatShouldNotUseRestrictedMode(List<String> packageNames,
+                int operationType, AndroidFuture<List<String>> resultFuture) {
+            try {
+                List<String> result =
+                        BackupTransport.this.getPackagesThatShouldNotUseRestrictedMode(packageNames,
+                                operationType);
+                resultFuture.complete(result);
+            } catch (RuntimeException e) {
+                resultFuture.cancel(/* mayInterruptIfRunning */ true);
+            }
+        }
     }
 }
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index c47fe23..de01280 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -148,6 +148,14 @@
 
 flag {
     namespace: "virtual_devices"
+    name: "notifications_for_device_streaming"
+    description: "Add notifications permissions to device streaming role"
+    bug: "375240276"
+    is_exported: true
+}
+
+flag {
+    namespace: "virtual_devices"
     name: "default_device_camera_access_policy"
     description: "API for default device camera access policy"
     bug: "371173368"
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 3152ff4..37295ac 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -193,6 +193,42 @@
             "android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES";
 
     /**
+     * &lt;application&gt; level {@link android.content.pm.PackageManager.Property} tag
+     * specifying whether the app should be put into the "restricted" backup mode when it's started
+     * for backup and restore operations.
+     *
+     * <p> See <a
+     * href="https://developer.android.com/identity/data/autobackup#ImplementingBackupAgent"> for
+     * information about restricted mode</a>.
+     *
+     * <p> Starting with Android 16 apps may not be started in restricted mode based on this
+     * property.
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE"
+     *     android:value="true|false"/&gt;
+     * &lt;/application&gt;
+     * </pre>
+     *
+     * <p>If this property is set, the operating system will respect it for now (see Note below).
+     * If it's not set, the behavior depends on the SDK level that the app is targeting. For apps
+     * targeting SDK level {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or lower, the
+     * property defaults to {@code true}. For apps targeting SDK level
+     * {@link android.os.Build.VERSION_CODES#BAKLAVA} or higher, the operating system will make a
+     * decision dynamically.
+     *
+     * <p>Note: It's not recommended to set this property to {@code true} unless absolutely
+     * necessary. In a future Android version, this property may be deprecated in favor of removing
+     * restricted mode completely.
+     */
+    @FlaggedApi(com.android.server.backup.Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+    public static final String PROPERTY_USE_RESTRICTED_BACKUP_MODE =
+            "android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE";
+
+    /**
      * Application level property that an app can specify to opt-out from having private data
      * directories both on the internal and external storages.
      *
@@ -292,6 +328,10 @@
      * <p>
      * The value of a property will only have a single type, as defined by
      * the property itself.
+     *
+     * <p class="note"><strong>Note:</strong>
+     * In android version {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and earlier,
+     * the {@code equals} and {@code hashCode} methods for this class may not function as expected.
      */
     public static final class Property implements Parcelable {
         private static final int TYPE_BOOLEAN = 1;
@@ -523,6 +563,40 @@
                 return new Property[size];
             }
         };
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof Property)) {
+                return false;
+            }
+            final Property property = (Property) obj;
+            return mType == property.mType &&
+                    Objects.equals(mName, property.mName) &&
+                    Objects.equals(mClassName, property.mClassName) &&
+                    Objects.equals(mPackageName, property.mPackageName) &&
+                    (mType == TYPE_BOOLEAN ? mBooleanValue == property.mBooleanValue :
+                     mType == TYPE_FLOAT ? Float.compare(mFloatValue, property.mFloatValue) == 0 :
+                     mType == TYPE_INTEGER ? mIntegerValue == property.mIntegerValue :
+                     mType == TYPE_RESOURCE ? mIntegerValue == property.mIntegerValue :
+                     mStringValue.equals(property.mStringValue));
+        }
+
+        @Override
+        public int hashCode() {
+            int result = Objects.hash(mName, mType, mClassName, mPackageName);
+            if (mType == TYPE_BOOLEAN) {
+                result = 31 * result + (mBooleanValue ? 1 : 0);
+            } else if (mType == TYPE_FLOAT) {
+                result = 31 * result + Float.floatToIntBits(mFloatValue);
+            } else if (mType == TYPE_INTEGER) {
+                result = 31 * result + mIntegerValue;
+            } else if (mType == TYPE_RESOURCE) {
+                result = 31 * result + mIntegerValue;
+            } else if (mType == TYPE_STRING) {
+                result = 31 * result + mStringValue.hashCode();
+            }
+            return result;
+        }
     }
 
     /**
diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
new file mode 100644
index 0000000..99b05da
--- /dev/null
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -0,0 +1,406 @@
+/*
+ * 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.hardware.contexthub;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.chre.flags.Flags;
+import android.content.Context;
+import android.hardware.location.IContextHubService;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.annotation.GuardedBy;
+
+import java.util.concurrent.Executor;
+
+/**
+ * An object representing an endpoint exposed to ContextHub and VendorHub. The object encapsulates
+ * the lifecycle and message callbacks for an endpoint.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public class HubEndpoint {
+    private static final String TAG = "HubEndpoint";
+
+    private final Object mLock = new Object();
+    private final HubEndpointInfo mPendingHubEndpointInfo;
+    @Nullable private final IHubEndpointLifecycleCallback mLifecycleCallback;
+    @NonNull private final Executor mLifecycleCallbackExecutor;
+
+    @GuardedBy("mLock")
+    private final SparseArray<HubEndpointSession> mActiveSessions = new SparseArray<>();
+
+    private final IContextHubEndpointCallback mServiceCallback =
+            new IContextHubEndpointCallback.Stub() {
+                @Override
+                public void onSessionOpenRequest(int sessionId, HubEndpointInfo initiator)
+                        throws RemoteException {
+                    HubEndpointSession activeSession;
+                    synchronized (mLock) {
+                        activeSession = mActiveSessions.get(sessionId);
+                        // TODO(b/378974199): Consider refactor these assertions
+                        if (activeSession != null) {
+                            Log.i(
+                                    TAG,
+                                    "onSessionOpenComplete: session already exists, id="
+                                            + sessionId);
+                            return;
+                        }
+                    }
+
+                    if (mLifecycleCallback != null) {
+                        mLifecycleCallbackExecutor.execute(
+                                () ->
+                                        processSessionOpenRequestResult(
+                                                sessionId,
+                                                initiator,
+                                                mLifecycleCallback.onSessionOpenRequest(
+                                                        initiator)));
+                    }
+                }
+
+                private void processSessionOpenRequestResult(
+                        int sessionId, HubEndpointInfo initiator, HubEndpointSessionResult result) {
+                    if (result == null) {
+                        throw new IllegalArgumentException(
+                                "HubEndpointSessionResult shouldn't be null.");
+                    }
+
+                    if (result.isAccepted()) {
+                        acceptSession(sessionId, initiator);
+                    } else {
+                        Log.i(
+                                TAG,
+                                "Session "
+                                        + sessionId
+                                        + " from "
+                                        + initiator
+                                        + " was rejected, reason="
+                                        + result.getReason());
+                        rejectSession(sessionId);
+                    }
+                }
+
+                private void acceptSession(int sessionId, HubEndpointInfo initiator) {
+                    if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
+                        // No longer registered?
+                        return;
+                    }
+
+                    // Retrieve the active session
+                    HubEndpointSession activeSession;
+                    synchronized (mLock) {
+                        activeSession = mActiveSessions.get(sessionId);
+                        // TODO(b/378974199): Consider refactor these assertions
+                        if (activeSession != null) {
+                            Log.e(
+                                    TAG,
+                                    "onSessionOpenRequestResult: session already exists, id="
+                                            + sessionId);
+                            return;
+                        }
+
+                        activeSession =
+                                new HubEndpointSession(
+                                        sessionId,
+                                        HubEndpoint.this,
+                                        mAssignedHubEndpointInfo,
+                                        initiator);
+                        try {
+                            // oneway call to notify system service that the request is completed
+                            mServiceToken.openSessionRequestComplete(sessionId);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "onSessionOpenRequestResult: ", e);
+                            return;
+                        }
+
+                        mActiveSessions.put(sessionId, activeSession);
+                    }
+
+                    // Execute the callback
+                    activeSession.setOpened();
+                    if (mLifecycleCallback != null) {
+                        final HubEndpointSession finalActiveSession = activeSession;
+                        mLifecycleCallbackExecutor.execute(
+                                () -> mLifecycleCallback.onSessionOpened(finalActiveSession));
+                    }
+                }
+
+                private void rejectSession(int sessionId) {
+                    if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
+                        // No longer registered?
+                        return;
+                    }
+
+                    try {
+                        mServiceToken.closeSession(
+                                sessionId,
+                                IHubEndpointLifecycleCallback
+                                        .REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
+                    } catch (RemoteException e) {
+                        e.rethrowFromSystemServer();
+                    }
+                }
+
+                @Override
+                public void onSessionOpenComplete(int sessionId) throws RemoteException {
+                    final HubEndpointSession activeSession;
+
+                    // Retrieve the active session
+                    synchronized (mLock) {
+                        activeSession = mActiveSessions.get(sessionId);
+                    }
+                    // TODO(b/378974199): Consider refactor these assertions
+                    if (activeSession == null) {
+                        Log.i(
+                                TAG,
+                                "onSessionOpenComplete: no pending session open request? id="
+                                        + sessionId);
+                        return;
+                    }
+
+                    // Execute the callback
+                    activeSession.setOpened();
+                    if (mLifecycleCallback != null) {
+                        mLifecycleCallbackExecutor.execute(
+                                () -> mLifecycleCallback.onSessionOpened(activeSession));
+                    }
+                }
+
+                @Override
+                public void onSessionClosed(int sessionId, int reason) throws RemoteException {
+                    final HubEndpointSession activeSession;
+
+                    // Retrieve the active session
+                    synchronized (mLock) {
+                        activeSession = mActiveSessions.get(sessionId);
+                    }
+                    // TODO(b/378974199): Consider refactor these assertions
+                    if (activeSession == null) {
+                        Log.i(TAG, "onSessionClosed: session not active, id=" + sessionId);
+                        return;
+                    }
+
+                    // Execute the callback
+                    if (mLifecycleCallback != null) {
+                        mLifecycleCallbackExecutor.execute(
+                                () -> {
+                                    mLifecycleCallback.onSessionClosed(activeSession, reason);
+
+                                    // Remove the session object first to call
+                                    activeSession.setClosed();
+                                    synchronized (mLock) {
+                                        mActiveSessions.remove(sessionId);
+                                    }
+                                });
+                    }
+                }
+            };
+
+    /** Binder returned from system service, non-null while registered. */
+    @Nullable private IContextHubEndpoint mServiceToken;
+
+    /** HubEndpointInfo with the assigned endpoint id from system service. */
+    @Nullable private HubEndpointInfo mAssignedHubEndpointInfo;
+
+    private HubEndpoint(
+            @NonNull HubEndpointInfo pendingEndpointInfo,
+            @Nullable IHubEndpointLifecycleCallback endpointLifecycleCallback,
+            @NonNull Executor lifecycleCallbackExecutor) {
+        mPendingHubEndpointInfo = pendingEndpointInfo;
+        mLifecycleCallback = endpointLifecycleCallback;
+        mLifecycleCallbackExecutor = lifecycleCallbackExecutor;
+    }
+
+    /** @hide */
+    public void register(IContextHubService service) {
+        // TODO(b/378974199): Consider refactor these assertions
+        if (mServiceToken != null) {
+            // Already registered
+            return;
+        }
+        try {
+            IContextHubEndpoint serviceToken =
+                    service.registerEndpoint(mPendingHubEndpointInfo, mServiceCallback);
+            mAssignedHubEndpointInfo = serviceToken.getAssignedHubEndpointInfo();
+            mServiceToken = serviceToken;
+        } catch (RemoteException e) {
+            Log.e(TAG, "registerEndpoint: failed to register endpoint", e);
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void unregister() {
+        IContextHubEndpoint serviceToken = mServiceToken;
+        // TODO(b/378974199): Consider refactor these assertions
+        if (serviceToken == null) {
+            // Not yet registered
+            return;
+        }
+
+        try {
+            synchronized (mLock) {
+                // Don't call HubEndpointSession.close() here.
+                for (int i = 0; i < mActiveSessions.size(); i++) {
+                    mActiveSessions.get(mActiveSessions.keyAt(i)).setClosed();
+                }
+                mActiveSessions.clear();
+            }
+            mServiceToken.unregister();
+        } catch (RemoteException e) {
+            Log.e(TAG, "unregisterEndpoint: failed to unregister endpoint", e);
+            e.rethrowFromSystemServer();
+        } finally {
+            mServiceToken = null;
+            mAssignedHubEndpointInfo = null;
+        }
+    }
+
+    /** @hide */
+    public void openSession(HubEndpointInfo destinationInfo) {
+        // TODO(b/378974199): Consider refactor these assertions
+        if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
+            // No longer registered?
+            return;
+        }
+
+        HubEndpointSession newSession;
+        try {
+            // Request system service to assign session id.
+            int sessionId = mServiceToken.openSession(destinationInfo);
+
+            // Save the newly created session
+            synchronized (mLock) {
+                newSession =
+                        new HubEndpointSession(
+                                sessionId,
+                                HubEndpoint.this,
+                                destinationInfo,
+                                mAssignedHubEndpointInfo);
+                mActiveSessions.put(sessionId, newSession);
+            }
+        } catch (RemoteException e) {
+            // Move this to toString
+            Log.e(TAG, "openSession: failed to open session to " + destinationInfo, e);
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void closeSession(HubEndpointSession session) {
+        IContextHubEndpoint serviceToken = mServiceToken;
+        // TODO(b/378974199): Consider refactor these assertions
+        if (serviceToken == null || mAssignedHubEndpointInfo == null) {
+            // Not registered
+            return;
+        }
+
+        synchronized (mLock) {
+            if (!mActiveSessions.contains(session.getId())) {
+                // Already closed?
+                return;
+            }
+            session.setClosed();
+            mActiveSessions.remove(session.getId());
+        }
+
+        try {
+            // Oneway notification to system service
+            serviceToken.closeSession(
+                    session.getId(),
+                    IHubEndpointLifecycleCallback.REASON_CLOSE_ENDPOINT_SESSION_REQUESTED);
+        } catch (RemoteException e) {
+            Log.e(TAG, "closeSession: failed to close session " + session, e);
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    @Nullable
+    public String getTag() {
+        return mPendingHubEndpointInfo.getTag();
+    }
+
+    @Nullable
+    public IHubEndpointLifecycleCallback getLifecycleCallback() {
+        return mLifecycleCallback;
+    }
+
+    /** Builder for a {@link HubEndpoint} object. */
+    public static final class Builder {
+        private final String mPackageName;
+
+        @Nullable private IHubEndpointLifecycleCallback mLifecycleCallback;
+
+        @NonNull private Executor mLifecycleCallbackExecutor;
+
+        @Nullable private String mTag;
+
+        /** Create a builder for {@link HubEndpoint} */
+        public Builder(@NonNull Context context) {
+            mPackageName = context.getPackageName();
+            mLifecycleCallbackExecutor = context.getMainExecutor();
+        }
+
+        /**
+         * Set a tag string. The tag can be used to further identify the creator of the endpoint.
+         * Endpoints created by the same package share the same name but should have different tags.
+         */
+        @NonNull
+        public Builder setTag(@NonNull String tag) {
+            mTag = tag;
+            return this;
+        }
+
+        /** Attach a callback interface for lifecycle events for this Endpoint */
+        @NonNull
+        public Builder setLifecycleCallback(
+                @NonNull IHubEndpointLifecycleCallback lifecycleCallback) {
+            mLifecycleCallback = lifecycleCallback;
+            return this;
+        }
+
+        /**
+         * Attach a callback interface for lifecycle events for this Endpoint with a specified
+         * executor
+         */
+        @NonNull
+        public Builder setLifecycleCallback(
+                @NonNull @CallbackExecutor Executor executor,
+                @NonNull IHubEndpointLifecycleCallback lifecycleCallback) {
+            mLifecycleCallbackExecutor = executor;
+            mLifecycleCallback = lifecycleCallback;
+            return this;
+        }
+
+        /** Build the {@link HubEndpoint} object. */
+        @NonNull
+        public HubEndpoint build() {
+            return new HubEndpoint(
+                    new HubEndpointInfo(mPackageName, mTag),
+                    mLifecycleCallback,
+                    mLifecycleCallbackExecutor);
+        }
+    }
+}
diff --git a/core/java/android/hardware/contexthub/HubEndpointInfo.java b/core/java/android/hardware/contexthub/HubEndpointInfo.java
index c17fc00..ed8ff29 100644
--- a/core/java/android/hardware/contexthub/HubEndpointInfo.java
+++ b/core/java/android/hardware/contexthub/HubEndpointInfo.java
@@ -115,6 +115,13 @@
         mTag = endpointInfo.tag;
     }
 
+    /** @hide */
+    public HubEndpointInfo(String name, @Nullable String tag) {
+        mId = HubEndpointIdentifier.invalid();
+        mName = name;
+        mTag = tag;
+    }
+
     private HubEndpointInfo(Parcel in) {
         long hubId = in.readLong();
         long endpointId = in.readLong();
diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java
new file mode 100644
index 0000000..ef989f1f
--- /dev/null
+++ b/core/java/android/hardware/contexthub/HubEndpointSession.java
@@ -0,0 +1,115 @@
+/*
+ * 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.hardware.contexthub;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.chre.flags.Flags;
+import android.util.CloseGuard;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * An object representing a communication session between two different hub endpoints.
+ *
+ * <p>A published enpoint can receive
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public class HubEndpointSession implements AutoCloseable {
+    private final CloseGuard mCloseGuard = new CloseGuard();
+
+    private final int mId;
+
+    // TODO(b/377717509): Implement Message sending API & interface
+    @NonNull private final HubEndpoint mHubEndpoint;
+    @NonNull private final HubEndpointInfo mInitiator;
+    @NonNull private final HubEndpointInfo mDestination;
+
+    private final AtomicBoolean mIsClosed = new AtomicBoolean(true);
+
+    /** @hide */
+    HubEndpointSession(
+            int id,
+            @NonNull HubEndpoint hubEndpoint,
+            @NonNull HubEndpointInfo destination,
+            @NonNull HubEndpointInfo initiator) {
+        mId = id;
+        mHubEndpoint = hubEndpoint;
+        mDestination = destination;
+        mInitiator = initiator;
+    }
+
+    /** @hide */
+    public int getId() {
+        return mId;
+    }
+
+    /** @hide */
+    public void setOpened() {
+        mIsClosed.set(false);
+        mCloseGuard.open("close");
+    }
+
+    /** @hide */
+    public void setClosed() {
+        mIsClosed.set(true);
+        mCloseGuard.close();
+    }
+
+    /**
+     * Closes the connection for this session between an endpoint and the Context Hub Service.
+     *
+     * <p>When this function is invoked, the messaging associated with this session is invalidated.
+     * All futures messages targeted for this client are dropped.
+     */
+    public void close() {
+        if (!mIsClosed.getAndSet(true)) {
+            mCloseGuard.close();
+            mHubEndpoint.closeSession(this);
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder();
+        stringBuilder.append("Session [");
+        stringBuilder.append(mId);
+        stringBuilder.append("]: [");
+        stringBuilder.append(mInitiator);
+        stringBuilder.append("]->[");
+        stringBuilder.append(mDestination);
+        stringBuilder.append("]");
+        return stringBuilder.toString();
+    }
+
+    /** @hide */
+    protected void finalize() throws Throwable {
+        try {
+            // Note that guard could be null if the constructor threw.
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+}
diff --git a/core/java/android/hardware/contexthub/HubEndpointSessionResult.java b/core/java/android/hardware/contexthub/HubEndpointSessionResult.java
new file mode 100644
index 0000000..1f2bdb9
--- /dev/null
+++ b/core/java/android/hardware/contexthub/HubEndpointSessionResult.java
@@ -0,0 +1,79 @@
+/*
+ * 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.hardware.contexthub;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.chre.flags.Flags;
+
+/**
+ * Return type of {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}. The value determines
+ * whether a open session request from the remote is accepted or not.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public class HubEndpointSessionResult {
+    private final boolean mAccepted;
+
+    @Nullable private final String mReason;
+
+    private HubEndpointSessionResult(boolean accepted, @Nullable String reason) {
+        mAccepted = accepted;
+        mReason = reason;
+    }
+
+    /**
+     * Retrieve the decision of the session request.
+     *
+     * @return Whether a session request was accepted or not, previously set with {@link #accept()}
+     *     or {@link #reject(String)}.
+     */
+    public boolean isAccepted() {
+        return mAccepted;
+    }
+
+    /**
+     * Retrieve the decision of the session request.
+     *
+     * @return The reason previously set in {@link #reject(String)}. If the result was {@link
+     *     #accept()}, the reason will be null.
+     */
+    @Nullable
+    public String getReason() {
+        return mReason;
+    }
+
+    /** Accept the request. */
+    @NonNull
+    public static HubEndpointSessionResult accept() {
+        return new HubEndpointSessionResult(true, null);
+    }
+
+    /**
+     * Reject the request with a reason.
+     *
+     * @param reason Reason why the request was rejected, for diagnostic purposes.
+     */
+    @NonNull
+    public static HubEndpointSessionResult reject(@NonNull String reason) {
+        return new HubEndpointSessionResult(false, reason);
+    }
+}
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
new file mode 100644
index 0000000..61e60e3
--- /dev/null
+++ b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.contexthub;
+
+import android.hardware.contexthub.HubEndpointInfo;
+
+/**
+ * @hide
+ */
+interface IContextHubEndpoint {
+    /**
+     * Retrieve the up-to-date EndpointInfo, with assigned endpoint id.
+     */
+    HubEndpointInfo getAssignedHubEndpointInfo();
+
+    /**
+     * Request system service to open a session with a specific destination.
+     *
+     * @param destination A valid HubEndpointInfo representing the destination.
+     *
+     * @throws IllegalArgumentException If the HubEndpointInfo is not valid.
+     * @throws IllegalStateException If there are too many opened sessions.
+     */
+    int openSession(in HubEndpointInfo destination);
+
+    /**
+     * Request system service to close a specific session
+     *
+     * @param sessionId An integer identifying the session, assigned by system service
+     * @param reason An integer identifying the reason
+     *
+     * @throws IllegalStateException If the session wasn't opened.
+     */
+    void closeSession(int sessionId, int reason);
+
+    /**
+     * Callback when a session is opened. This callback is the status callback for a previous
+     * IContextHubEndpointCallback.onSessionOpenRequest().
+     *
+     * @param sessionId The integer representing the communication session, previously set in
+     *         onSessionOpenRequest().
+     *
+     * @throws IllegalStateException If the session wasn't opened.
+     */
+    void openSessionRequestComplete(int sessionId);
+
+    /**
+     * Unregister this endpoint from the HAL, invalidate the EndpointInfo previously assigned.
+     */
+    void unregister();
+}
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
new file mode 100644
index 0000000..5656a4a
--- /dev/null
+++ b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.contexthub;
+
+import android.hardware.contexthub.HubEndpointInfo;
+
+/**
+  * @hide
+ */
+oneway interface IContextHubEndpointCallback {
+    /**
+     * Request from system service to open a session, requested by a specific initiator.
+     *
+     * @param sessionId An integer identifying the session, assigned by the initiator
+     * @param initiator HubEndpointInfo representing the requester
+     */
+    void onSessionOpenRequest(int sessionId, in HubEndpointInfo initiator);
+
+    /**
+     * Request from system service to close a specific session
+     *
+     * @param sessionId An integer identifying the session
+     * @param reason An integer identifying the reason
+     */
+    void onSessionClosed(int sessionId, int reason);
+
+
+    /**
+     * Notifies the system service that the session requested by IContextHubEndpoint.openSession
+     * is ready to use.
+     *
+     * @param sessionId The integer representing the communication session, previously set in
+     *         IContextHubEndpoint.openSession(). This id is assigned by the HAL.
+     */
+    void onSessionOpenComplete(int sessionId);
+}
diff --git a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
new file mode 100644
index 0000000..5bd3c0e
--- /dev/null
+++ b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.contexthub;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.chre.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Interface for listening to lifecycle events of a hub endpoint.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public interface IHubEndpointLifecycleCallback {
+    /** Unknown reason. */
+    int REASON_UNSPECIFIED = 0;
+
+    /** The peer rejected the request to open this endpoint session. */
+    int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3;
+
+    /** The peer closed this endpoint session. */
+    int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        REASON_UNSPECIFIED,
+        REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED,
+        REASON_CLOSE_ENDPOINT_SESSION_REQUESTED,
+    })
+    @interface EndpointLifecycleReason {}
+
+    /**
+     * Called when an endpoint is requesting a session be opened with another endpoint.
+     *
+     * @param requester The {@link HubEndpointInfo} object representing the requester
+     */
+    @NonNull
+    HubEndpointSessionResult onSessionOpenRequest(@NonNull HubEndpointInfo requester);
+
+    /**
+     * Called when a communication session is opened and ready to be used.
+     *
+     * @param session The {@link HubEndpointSession} object that can be used for communication
+     */
+    void onSessionOpened(@NonNull HubEndpointSession session);
+
+    /**
+     * Called when a communication session is requested to be closed, or the peer endpoint rejected
+     * the session open request.
+     *
+     * @param session The {@link HubEndpointSession} object that is now closed and shouldn't be
+     *     used.
+     * @param reason The reason why this session was closed.
+     */
+    void onSessionClosed(@NonNull HubEndpointSession session, @EndpointLifecycleReason int reason);
+}
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index be710b1..1e66bee 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -17,6 +17,7 @@
 package android.hardware.display;
 
 
+import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
 import static android.hardware.display.DisplayManager.EventFlag;
 import static android.Manifest.permission.MANAGE_DISPLAYS;
 import static android.view.Display.HdrCapabilities.HdrType;
@@ -188,9 +189,11 @@
     }
 
     private PropertyInvalidatedCache<Integer, DisplayInfo> mDisplayCache =
-            new PropertyInvalidatedCache<Integer, DisplayInfo>(
-                8, // size of display cache
-                CACHE_KEY_DISPLAY_INFO_PROPERTY) {
+            new PropertyInvalidatedCache<>(
+                new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
+                .maxEntries(8).api(CACHE_KEY_DISPLAY_INFO_API).isolateUids(false),
+                CACHE_KEY_DISPLAY_INFO_API, null) {
+
                 @Override
                 public DisplayInfo recompute(Integer id) {
                     try {
@@ -1514,18 +1517,17 @@
     }
 
     /**
-     * Name of the property containing a unique token which changes every time we update the
-     * system's display configuration.
+     * The API portion of the key that identifies the unique PropertyInvalidatedCache token which
+     * changes every time we update the system's display configuration.
      */
-    public static final String CACHE_KEY_DISPLAY_INFO_PROPERTY =
-            PropertyInvalidatedCache.createSystemCacheKey("display_info");
+    private static final String CACHE_KEY_DISPLAY_INFO_API = "display_info";
 
     /**
      * Invalidates the contents of the display info cache for all applications. Can only
      * be called by system_server.
      */
     public static void invalidateLocalDisplayInfoCaches() {
-        PropertyInvalidatedCache.invalidateCache(CACHE_KEY_DISPLAY_INFO_PROPERTY);
+        PropertyInvalidatedCache.invalidateCache(MODULE_SYSTEM, CACHE_KEY_DISPLAY_INFO_API);
     }
 
     /**
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index e009c2f..b2c3bb8 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -35,7 +35,9 @@
 import android.content.pm.PackageManager;
 import android.hardware.contexthub.ErrorCode;
 import android.hardware.contexthub.HubDiscoveryInfo;
+import android.hardware.contexthub.HubEndpoint;
 import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IHubEndpointLifecycleCallback;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.Looper;
@@ -1036,6 +1038,55 @@
     }
 
     /**
+     * Registers an endpoint and its callback with the Context Hub Service.
+     *
+     * <p>An endpoint is registered with the Context Hub Service and published to the HAL. When the
+     * registration succeeds, the endpoint can receive notifications through the provided callback.
+     *
+     * @param hubEndpoint {@link HubEndpoint} object created by {@link HubEndpoint.Builder}
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+    public void registerEndpoint(@NonNull HubEndpoint hubEndpoint) {
+        hubEndpoint.register(mService);
+    }
+
+    /**
+     * Use a registered endpoint to connect to another endpoint (destination).
+     *
+     * <p>Context Hub Service will create the endpoint session and notify the registered endpoint.
+     * The registered endpoint will receive callbacks on its {@link IHubEndpointLifecycleCallback}
+     * object regarding the lifecycle events of the session
+     *
+     * @param hubEndpoint {@link HubEndpoint} object previously registered via {@link
+     *     ContextHubManager#registerEndpoint(HubEndpoint)}.
+     * @param destination {@link HubEndpointInfo} object that represents an endpoint from previous
+     *     endpoint discovery results (e.g. from {@link ContextHubManager#findEndpoints(long)}).
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+    public void openSession(
+            @NonNull HubEndpoint hubEndpoint, @NonNull HubEndpointInfo destination) {
+        hubEndpoint.openSession(destination);
+    }
+
+    /**
+     * Unregisters an endpoint and its callback with the Context Hub Service.
+     *
+     * <p>An endpoint is unregistered from the HAL. The endpoint object will no longer receive
+     * notification through the provided callback.
+     *
+     * @param hubEndpoint {@link HubEndpoint} object created by {@link HubEndpoint.Builder}. This
+     *     should match a previously registered object via {@link
+     *     ContextHubManager#registerEndpoint(HubEndpoint)}.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+    public void unregisterEndpoint(@NonNull HubEndpoint hubEndpoint) {
+        hubEndpoint.unregister();
+    }
+
+    /**
      * Queries for the list of preloaded nanoapp IDs on the system.
      *
      * @param hubInfo The Context Hub to query a list of nanoapp IDs from.
@@ -1194,6 +1245,7 @@
         requireNonNull(mainLooper, "mainLooper cannot be null");
         mService = service;
         mMainLooper = mainLooper;
+
         try {
             mService.registerCallback(mClientCallback);
         } catch (RemoteException e) {
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index fc6a70a..5128723 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -19,6 +19,8 @@
 // Declare any non-default types here with import statements
 import android.app.PendingIntent;
 import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IContextHubEndpoint;
+import android.hardware.contexthub.IContextHubEndpointCallback;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubMessage;
 import android.hardware.location.HubInfo;
@@ -127,4 +129,8 @@
     // Finds all endpoints that havea specific ID
     @EnforcePermission("ACCESS_CONTEXT_HUB")
     List<HubEndpointInfo> findEndpoints(long endpointId);
+
+    // Register an endpoint with the context hub
+    @EnforcePermission("ACCESS_CONTEXT_HUB")
+    IContextHubEndpoint registerEndpoint(in HubEndpointInfo pendingEndpointInfo, in IContextHubEndpointCallback callback);
 }
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index 26308f6..4bc5bd2 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -16,9 +16,6 @@
 
 package android.inputmethodservice;
 
-import static android.view.inputmethod.Flags.FLAG_VERIFY_KEY_EVENT;
-
-import android.annotation.FlaggedApi;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -196,12 +193,6 @@
             }
         }
 
-        @FlaggedApi(FLAG_VERIFY_KEY_EVENT)
-        @Override
-        public boolean onShouldVerifyKeyEvent(@NonNull KeyEvent event) {
-            return AbstractInputMethodService.this.onShouldVerifyKeyEvent(event);
-        }
-
         /**
          * Take care of dispatching incoming trackball events to the appropriate
          * callbacks on the service, and tell the client when this is done.
@@ -317,14 +308,6 @@
         return false;
     }
 
-    /**
-     * @see InputMethodService#onShouldVerifyKeyEvent(KeyEvent)
-     */
-    @FlaggedApi(FLAG_VERIFY_KEY_EVENT)
-    public boolean onShouldVerifyKeyEvent(@NonNull KeyEvent event) {
-        return false;
-    }
-
     /** @hide */
     @Override
     public final int getWindowType() {
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index 9b37533..62b131a 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -16,16 +16,12 @@
 
 package android.inputmethodservice;
 
-import static android.view.inputmethod.Flags.verifyKeyEvent;
-
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.Rect;
-import android.hardware.input.InputManager;
 import android.os.Bundle;
 import android.os.Looper;
 import android.os.Message;
-import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.InputChannel;
@@ -45,8 +41,6 @@
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 
-import java.util.Objects;
-
 class IInputMethodSessionWrapper extends IInputMethodSession.Stub
         implements HandlerCaller.Callback {
     private static final String TAG = "InputMethodWrapper";
@@ -62,7 +56,6 @@
     private static final int DO_REMOVE_IME_SURFACE = 130;
     private static final int DO_FINISH_INPUT = 140;
     private static final int DO_INVALIDATE_INPUT = 150;
-    private final Context mContext;
 
 
     @UnsupportedAppUsage
@@ -73,7 +66,6 @@
 
     public IInputMethodSessionWrapper(Context context,
             InputMethodSession inputMethodSession, InputChannel channel) {
-        mContext = context;
         mCaller = new HandlerCaller(context, null,
                 this, true /*asyncHandler*/);
         mInputMethodSession = inputMethodSession;
@@ -241,8 +233,6 @@
     }
     private final class ImeInputEventReceiver extends InputEventReceiver
             implements InputMethodSession.EventCallback {
-        // Time after which a KeyEvent is invalid
-        private static final long KEY_EVENT_ALLOW_PERIOD_MS = 100L;
         private final SparseArray<InputEvent> mPendingEvents = new SparseArray<InputEvent>();
 
         public ImeInputEventReceiver(InputChannel inputChannel, Looper looper) {
@@ -257,23 +247,10 @@
                 return;
             }
 
-            if (event instanceof KeyEvent keyEvent && needsVerification(keyEvent)) {
-                // any KeyEvent with modifiers (e.g. Ctrl/Alt/Fn) must be verified that
-                // they originated from system.
-                InputManager im = mContext.getSystemService(InputManager.class);
-                Objects.requireNonNull(im);
-                final long age = SystemClock.uptimeMillis() - keyEvent.getEventTime();
-                if (age >= KEY_EVENT_ALLOW_PERIOD_MS && im.verifyInputEvent(keyEvent) == null) {
-                    Log.w(TAG, "Unverified or Invalid KeyEvent injected into IME. Dropping "
-                            + keyEvent);
-                    finishInputEvent(event, false /* handled */);
-                    return;
-                }
-            }
-
             final int seq = event.getSequenceNumber();
             mPendingEvents.put(seq, event);
-            if (event instanceof KeyEvent keyEvent) {
+            if (event instanceof KeyEvent) {
+                KeyEvent keyEvent = (KeyEvent)event;
                 mInputMethodSession.dispatchKeyEvent(seq, keyEvent, this);
             } else {
                 MotionEvent motionEvent = (MotionEvent)event;
@@ -294,21 +271,5 @@
                 finishInputEvent(event, handled);
             }
         }
-
-        private boolean hasKeyModifiers(KeyEvent event) {
-            if (event.hasNoModifiers()) {
-                return false;
-            }
-            return event.hasModifiers(KeyEvent.META_CTRL_ON)
-                    || event.hasModifiers(KeyEvent.META_ALT_ON)
-                    || event.hasModifiers(KeyEvent.KEYCODE_FUNCTION);
-        }
-
-        private boolean needsVerification(KeyEvent event) {
-            //TODO(b/331730488): Handle a11y events as well.
-            return verifyKeyEvent()
-                    && (hasKeyModifiers(event)
-                            || mInputMethodSession.onShouldVerifyKeyEvent(event));
-        }
     }
 }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index a8fde4a..dadb5c38 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -56,7 +56,6 @@
 import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
 import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING;
 import static android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API;
-import static android.view.inputmethod.Flags.FLAG_VERIFY_KEY_EVENT;
 import static android.view.inputmethod.Flags.ctrlShiftShortcut;
 import static android.view.inputmethod.Flags.predictiveBackIme;
 
@@ -3736,23 +3735,6 @@
     }
 
     /**
-     * Received by the IME before dispatch to {@link #onKeyDown(int, KeyEvent)} to let the system
-     * know if the {@link KeyEvent} needs to be verified that it originated from the system.
-     * {@link KeyEvent}s may originate from outside of the system and any sensitive keys should be
-     * marked for verification. One example of this could be using key shortcuts for switching to
-     * another IME.
-     *
-     * @param keyEvent the event that may need verification.
-     * @return {@code true} if {@link KeyEvent} should have its HMAC verified before dispatch,
-     * {@code false} otherwise.
-     */
-    @FlaggedApi(FLAG_VERIFY_KEY_EVENT)
-    @Override
-    public boolean onShouldVerifyKeyEvent(@NonNull KeyEvent keyEvent) {
-        return false;
-    }
-
-    /**
      * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
      * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle
      * the event).
diff --git a/core/java/android/net/vcn/VcnFrameworkInitializer.java b/core/java/android/net/ConnectivityFrameworkInitializerBaklava.java
similarity index 86%
rename from core/java/android/net/vcn/VcnFrameworkInitializer.java
rename to core/java/android/net/ConnectivityFrameworkInitializerBaklava.java
index 8cb213b..1f0fa92 100644
--- a/core/java/android/net/vcn/VcnFrameworkInitializer.java
+++ b/core/java/android/net/ConnectivityFrameworkInitializerBaklava.java
@@ -14,15 +14,21 @@
  * limitations under the License.
  */
 
-package android.net.vcn;
+package android.net;
 
+import static android.net.vcn.Flags.FLAG_MAINLINE_VCN_MODULE_API;
+
+import android.annotation.FlaggedApi;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.app.SystemServiceRegistry;
 import android.compat.Compatibility;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.net.vcn.IVcnManagementService;
+import android.net.vcn.VcnManager;
 import android.os.Build;
 import android.os.SystemProperties;
 
@@ -31,8 +37,9 @@
  *
  * @hide
  */
-// TODO: Expose it as @SystemApi(client = MODULE_LIBRARIES)
-public final class VcnFrameworkInitializer {
+@FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class ConnectivityFrameworkInitializerBaklava {
     /**
      * Starting with {@link VANILLA_ICE_CREAM}, Telephony feature flags (e.g. {@link
      * PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}) are being checked before returning managers
@@ -55,7 +62,7 @@
      */
     private static final int VENDOR_API_FOR_ANDROID_V = 202404;
 
-    private VcnFrameworkInitializer() {}
+    private ConnectivityFrameworkInitializerBaklava() {}
 
     // Suppressing AndroidFrameworkCompatChange because we're querying vendor
     // partition SDK level, not application's target SDK version (which BTW we
@@ -86,7 +93,10 @@
      *
      * @throws IllegalStateException if this is called anywhere besides {@link
      *     SystemServiceRegistry}.
+     * @hide
      */
+    @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static void registerServiceWrappers() {
         SystemServiceRegistry.registerContextAwareService(
                 VcnManager.VCN_MANAGEMENT_SERVICE_STRING,
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index af93c96..3219ce8 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -16,6 +16,7 @@
 package android.net.vcn;
 
 import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
+import static android.net.vcn.Flags.FLAG_MAINLINE_VCN_MODULE_API;
 import static android.net.vcn.Flags.FLAG_SAFE_MODE_CONFIG;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 
@@ -82,7 +83,15 @@
  * </ul>
  */
 public final class VcnGatewayConnectionConfig {
-    /** @hide */
+    /**
+     * Minimum NAT timeout not set.
+     *
+     * <p>When the timeout is not set, the device will automatically choose a keepalive interval and
+     * may reduce the keepalive frequency for power-optimization.
+     */
+    @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+    // This constant does not represent a minimum value. It indicates the value is not configured.
+    @SuppressLint("MinMaxConstant")
     public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET = -1;
 
     /** @hide */
@@ -773,7 +782,7 @@
          *
          * @param minUdpPort4500NatTimeoutSeconds the maximum keepalive timeout supported by the VCN
          *     Gateway Connection, generally the minimum duration a NAT mapping is cached on the VCN
-         *     Gateway.
+         *     Gateway; or {@link MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET} to clear this value.
          * @return this {@link Builder} instance, for chaining
          */
         @NonNull
@@ -781,8 +790,10 @@
                 @IntRange(from = MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS)
                         int minUdpPort4500NatTimeoutSeconds) {
             Preconditions.checkArgument(
-                    minUdpPort4500NatTimeoutSeconds >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
-                    "Timeout must be at least 120s");
+                    minUdpPort4500NatTimeoutSeconds == MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET
+                            || minUdpPort4500NatTimeoutSeconds
+                                    >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
+                    "Timeout must be at least 120s or MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET");
 
             mMinUdpPort4500NatTimeoutSeconds = minUdpPort4500NatTimeoutSeconds;
             return this;
diff --git a/core/java/android/net/vcn/VcnTransportInfo.java b/core/java/android/net/vcn/VcnTransportInfo.java
index 1fc91ee..3638429 100644
--- a/core/java/android/net/vcn/VcnTransportInfo.java
+++ b/core/java/android/net/vcn/VcnTransportInfo.java
@@ -17,13 +17,16 @@
 package android.net.vcn;
 
 import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
+import static android.net.vcn.Flags.FLAG_MAINLINE_VCN_MODULE_API;
 import static android.net.vcn.VcnGatewayConnectionConfig.MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS;
 import static android.net.vcn.VcnGatewayConnectionConfig.MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.net.NetworkCapabilities;
 import android.net.TransportInfo;
 import android.net.wifi.WifiInfo;
@@ -52,23 +55,29 @@
  * @hide
  */
 // TODO: Do not store WifiInfo and subscription ID in VcnTransportInfo anymore
-public class VcnTransportInfo implements TransportInfo, Parcelable {
+@FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class VcnTransportInfo implements TransportInfo, Parcelable {
     @Nullable private final WifiInfo mWifiInfo;
     private final int mSubId;
     private final int mMinUdpPort4500NatTimeoutSeconds;
 
+    /** @hide */
     public VcnTransportInfo(@NonNull WifiInfo wifiInfo) {
         this(wifiInfo, INVALID_SUBSCRIPTION_ID, MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET);
     }
 
+    /** @hide */
     public VcnTransportInfo(@NonNull WifiInfo wifiInfo, int minUdpPort4500NatTimeoutSeconds) {
         this(wifiInfo, INVALID_SUBSCRIPTION_ID, minUdpPort4500NatTimeoutSeconds);
     }
 
+    /** @hide */
     public VcnTransportInfo(int subId) {
         this(null /* wifiInfo */, subId, MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET);
     }
 
+    /** @hide */
     public VcnTransportInfo(int subId, int minUdpPort4500NatTimeoutSeconds) {
         this(null /* wifiInfo */, subId, minUdpPort4500NatTimeoutSeconds);
     }
@@ -86,6 +95,7 @@
      * <p>If the underlying Network for the associated VCN is Cellular, returns null.
      *
      * @return the WifiInfo if there is an underlying WiFi connection, else null.
+     * @hide
      */
     @Nullable
     public WifiInfo getWifiInfo() {
@@ -100,17 +110,27 @@
      *
      * @return the Subscription ID if a cellular underlying Network is present, else {@link
      *     android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+     * @hide
      */
     public int getSubId() {
         return mSubId;
     }
 
     /**
-     * Get the VCN provided UDP port 4500 NAT timeout
+     * Get the minimum duration that the VCN Gateway guarantees to preserve a NAT mapping.
      *
-     * @return the UDP 4500 NAT timeout, or
+     * <p>To ensure uninterrupted connectivity, the device must send keepalive packets before the
+     * timeout. Failure to do so may result in the mapping being cleared and connection termination.
+     * This value is used as a power-optimization hint for other IKEv2/IPsec use cases (e.g. VPNs,
+     * or IWLAN) to reduce the necessary keepalive frequency, thus conserving power and data.
+     *
+     * @return the minimum duration that the VCN Gateway guarantees to preserve a NAT mapping, or
      *     VcnGatewayConnectionConfig.MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET if not set.
+     * @see VcnGatewayConnectionConfig.Builder#setMinUdpPort4500NatTimeoutSeconds(int)
+     * @hide
      */
+    @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public int getMinUdpPort4500NatTimeoutSeconds() {
         return mMinUdpPort4500NatTimeoutSeconds;
     }
@@ -129,12 +149,21 @@
                 && mMinUdpPort4500NatTimeoutSeconds == that.mMinUdpPort4500NatTimeoutSeconds;
     }
 
-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
+    @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @Override
     public int describeContents() {
         return 0;
     }
 
+    /** @hide */
+    @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @Override
     @NonNull
     public TransportInfo makeCopy(long redactions) {
@@ -149,6 +178,9 @@
                 mMinUdpPort4500NatTimeoutSeconds);
     }
 
+    /** @hide */
+    @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @Override
     public long getApplicableRedactions() {
         long redactions = REDACT_FOR_NETWORK_SETTINGS;
@@ -161,7 +193,13 @@
         return redactions;
     }
 
-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
+    @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mSubId);
@@ -174,7 +212,13 @@
         return "VcnTransportInfo { mWifiInfo = " + mWifiInfo + ", mSubId = " + mSubId + " }";
     }
 
-    /** Implement the Parcelable interface */
+    /**
+     * Implement the Parcelable interface
+     *
+     * @hide
+     */
+    @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final @NonNull Creator<VcnTransportInfo> CREATOR =
             new Creator<VcnTransportInfo>() {
                 public VcnTransportInfo createFromParcel(Parcel in) {
@@ -201,37 +245,63 @@
                 }
             };
 
-    /** This class can be used to construct a {@link VcnTransportInfo}. */
+    /**
+     * This class can be used to construct a {@link VcnTransportInfo}.
+     *
+     * @hide
+     */
+    @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final class Builder {
         private int mMinUdpPort4500NatTimeoutSeconds = MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET;
 
-        /** Construct Builder */
+        /**
+         * Construct Builder
+         *
+         * @hide
+         */
+        @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         public Builder() {}
 
         /**
-         * Sets the maximum supported IKEv2/IPsec NATT keepalive timeout.
+         * Set the minimum duration that the VCN Gateway guarantees to preserve a NAT mapping.
          *
          * <p>This is used as a power-optimization hint for other IKEv2/IPsec use cases (e.g. VPNs,
          * or IWLAN) to reduce the necessary keepalive frequency, thus conserving power and data.
          *
-         * @param minUdpPort4500NatTimeoutSeconds the maximum keepalive timeout supported by the VCN
-         *     Gateway Connection, generally the minimum duration a NAT mapping is cached on the VCN
-         *     Gateway.
+         * @param minUdpPort4500NatTimeoutSeconds the minimum duration that the VCN Gateway
+         *     guarantees to preserve a NAT mapping, or {@link MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET}
+         *     to clear this value. To ensure uninterrupted connectivity, the device must send
+         *     keepalive packets within this interval. Failure to do so may result in the mapping
+         *     being cleared and connection termination.
          * @return this {@link Builder} instance, for chaining
+         * @see VcnGatewayConnectionConfig.Builder#setMinUdpPort4500NatTimeoutSeconds(int)
+         * @hide
          */
+        @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         @NonNull
         public Builder setMinUdpPort4500NatTimeoutSeconds(
                 @IntRange(from = MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS)
                         int minUdpPort4500NatTimeoutSeconds) {
             Preconditions.checkArgument(
-                    minUdpPort4500NatTimeoutSeconds >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
-                    "Timeout must be at least 120s");
+                    minUdpPort4500NatTimeoutSeconds == MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET
+                            || minUdpPort4500NatTimeoutSeconds
+                                    >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
+                    "Timeout must be at least 120s or MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET");
 
             mMinUdpPort4500NatTimeoutSeconds = minUdpPort4500NatTimeoutSeconds;
             return Builder.this;
         }
 
-        /** Build a VcnTransportInfo instance */
+        /**
+         * Build a VcnTransportInfo instance
+         *
+         * @hide
+         */
+        @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         @NonNull
         public VcnTransportInfo build() {
             return new VcnTransportInfo(
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 7529ab9..9ad2e7f 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -19,6 +19,8 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.app.Instrumentation;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Process;
 import android.os.UserHandle;
@@ -31,7 +33,6 @@
 import android.util.proto.ProtoOutputStream;
 
 import dalvik.annotation.optimization.NeverCompile;
-import dalvik.system.VMDebug;
 
 import java.io.FileDescriptor;
 import java.lang.annotation.Retention;
@@ -111,7 +112,20 @@
     private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
 
     MessageQueue(boolean quitAllowed) {
-        mUseConcurrent = UserHandle.isCore(Process.myUid()) && !VMDebug.isDebuggingEnabled();
+        // Concurrent mode modifies behavior that is observable via reflection and is commonly used
+        // by tests.
+        // For now, we limit it to system processes to avoid breaking apps and their tests.
+        mUseConcurrent = UserHandle.isCore(Process.myUid());
+        // Even then, we don't use it if instrumentation is loaded as it breaks some
+        // platform tests.
+        final ActivityThread activityThread = ActivityThread.currentActivityThread();
+        if (activityThread != null) {
+            final Instrumentation instrumentation = activityThread.getInstrumentation();
+            mUseConcurrent &= instrumentation == null || !instrumentation.isInstrumenting();
+        }
+        // We can lift this restriction in the future after we've made it possible for test authors
+        // to test Looper and MessageQueue without resorting to reflection.
+
         mQuitAllowed = quitAllowed;
         mPtr = nativeInit();
     }
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 24e1d66..e63b664 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -128,3 +128,6 @@
 
 # Dropbox
 per-file DropBoxManager* = mwachens@google.com
+
+# Flags
+per-file flags.aconfig = file:/FF_LEADS_OWNERS
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 6264fbb..0a35fe3 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -399,4 +399,13 @@
     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
+}
+
+flag {
+    name: "permission_request_short_circuit_enabled"
+    is_fixed_read_only: true
+    is_exported: true
+    namespace: "permissions"
+    description: "This flag is used to short circuit the request for permananently denied permissions"
+    bug: "378923900"
+}
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 98dda10..14a14e6 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -16,8 +16,10 @@
 
 package android.service.autofill;
 
+import static android.service.autofill.Flags.FLAG_AUTOFILL_W_METRICS;
 import static android.view.autofill.Helper.sVerbose;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -63,14 +65,21 @@
     private static final String TAG = "FillEventHistory";
 
     /**
-     * Not in parcel. The ID of the autofill session that created the {@link FillResponse}.
+     * The ID of the autofill session that created the {@link FillResponse}.
+     *
+     * TODO: add this to the parcel.
      */
     private final int mSessionId;
 
     @Nullable private final Bundle mClientState;
     @Nullable List<Event> mEvents;
 
-    /** @hide */
+    /**
+     * Returns the unique identifier of this FillEventHistory.
+     *
+     * <p>This is used to differentiate individual FillEventHistory.
+     */
+    @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
     public int getSessionId() {
         return mSessionId;
     }
@@ -283,6 +292,13 @@
         /** All fields matched contents of datasets. */
         public static final int NO_SAVE_UI_REASON_DATASET_MATCH = 6;
 
+        /**
+         * Credential Manager is invoked instead of Autofill. When that happens, Save Dialog cannot
+         * be shown, and this will be populated in
+         */
+        @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+        public static final int NO_SAVE_UI_REASON_USING_CREDMAN = 7;
+
         /** @hide */
         @IntDef(prefix = { "NO_SAVE_UI_REASON_" }, value = {
                 NO_SAVE_UI_REASON_NONE,
@@ -310,11 +326,20 @@
         public static final int UI_TYPE_DIALOG = 3;
 
         /**
-         *  The autofill suggestion is shown os a credman bottom sheet
-         *  @hide
+         * The autofill suggestion is shown os a credman bottom sheet
+         *
+         * <p>Note, this was introduced as bottom sheet even though it applies to all credman UI
+         * types. Instead of exposing this directly to the public, the generic UI_TYPE_CREDMAN is
+         * introduced with the same number.
+         *
+         * @hide
          */
         public static final int UI_TYPE_CREDMAN_BOTTOM_SHEET = 4;
 
+        /** Credential Manager suggestions are shown instead of Autofill suggestion */
+        @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+        public static final int UI_TYPE_CREDMAN = 4;
+
         /** @hide */
         @IntDef(prefix = { "UI_TYPE_" }, value = {
                 UI_TYPE_UNKNOWN,
@@ -359,6 +384,13 @@
             return mEventType;
         }
 
+        /** Gets the {@code AutofillId} that's focused at the time of action */
+        @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+        @Nullable
+        public AutofillId getFocusedId() {
+            return null;
+        }
+
         /**
          * Returns the id of dataset the id was on.
          *
@@ -391,6 +423,17 @@
         }
 
         /**
+         * Returns which datasets were shown to the user.
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_DATASETS_SHOWN}.
+         */
+        @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+        @NonNull
+        public Set<String> getShownDatasetIds() {
+            return Collections.emptySet();
+        }
+
+        /**
          * Returns which datasets were NOT selected by the user.
          *
          * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
diff --git a/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl b/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl
index 0dca78d..03e27b8 100644
--- a/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl
+++ b/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl
@@ -43,4 +43,6 @@
     oneway void unregisterWalletServiceEventListener(in WalletServiceEventListenerRequest request);
     // Request to get a PendingIntent to launch an activity from which the user can manage their cards.
     oneway void onTargetActivityIntentRequested(in IQuickAccessWalletServiceCallbacks callbacks);
+    // Request to get a PendingIntent to launch an activity, triggered when the user performs a gesture.
+    oneway void onGestureTargetActivityIntentRequested(in IQuickAccessWalletServiceCallbacks callbacks);
    }
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl b/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl
index 1b69ca1..61d7fd1 100644
--- a/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl
+++ b/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl
@@ -37,4 +37,6 @@
     oneway void onWalletServiceEvent(in WalletServiceEvent event);
     // Called in response to onTargetActivityIntentRequested. May only be called once per request.
     oneway void onTargetActivityPendingIntentReceived(in PendingIntent pendingIntent);
+    //Called in response to onGesturePendingIntent
+    oneway void onGestureTargetActivityPendingIntentReceived(in PendingIntent pendingIntent);
 }
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
index faa5b2f..b5251db 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
@@ -17,6 +17,7 @@
 package android.service.quickaccesswallet;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
@@ -181,6 +182,23 @@
     }
 
     /**
+     * Gets the {@link PendingIntent} provided by QuickAccessWalletService to be sent when the user
+     * launches Wallet via gesture.
+     */
+    @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    void getGestureTargetActivityPendingIntent(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull GesturePendingIntentCallback gesturePendingIntentCallback);
+
+    /** Callback interface for getGesturePendingIntent. */
+    @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    interface GesturePendingIntentCallback {
+        /** Callback method for getGesturePendingIntent */
+        @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+        void onGesturePendingIntentRetrieved(@Nullable PendingIntent pendingIntent);
+    }
+
+    /**
      * The manifest entry for the QuickAccessWalletService may also publish information about the
      * activity that hosts the Wallet view. This is typically the home screen of the Wallet
      * application.
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
index a59f026..97a4bef 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
@@ -267,6 +267,34 @@
     }
 
     @Override
+    public void getGestureTargetActivityPendingIntent(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull GesturePendingIntentCallback gesturePendingIntentCallback) {
+        BaseCallbacks callbacks =
+                new BaseCallbacks() {
+                    @Override
+                    public void onGestureTargetActivityPendingIntentReceived(
+                            PendingIntent pendingIntent) {
+                        if (!Flags.launchWalletOptionOnPowerDoubleTap()) {
+                            return;
+                        }
+                        executor.execute(
+                                () ->
+                                        gesturePendingIntentCallback
+                                                .onGesturePendingIntentRetrieved(pendingIntent));
+                    }
+                };
+
+        executeApiCall(
+                new ApiCaller("getGestureTargetActivityPendingIntent") {
+                    @Override
+                    void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+                        service.onGestureTargetActivityIntentRequested(callbacks);
+                    }
+                });
+    }
+
+    @Override
     @Nullable
     public Intent createWalletSettingsIntent() {
         if (mServiceInfo == null) {
@@ -506,5 +534,9 @@
         public void onTargetActivityPendingIntentReceived(PendingIntent pendingIntent) {
             throw new IllegalStateException();
         }
+
+        public void onGestureTargetActivityPendingIntentReceived(PendingIntent pendingIntent) {
+            throw new IllegalStateException();
+        }
     }
 }
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
index 36fa21c..90136ae 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
@@ -16,6 +16,9 @@
 
 package android.service.quickaccesswallet;
 
+import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap;
+
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
@@ -247,6 +250,19 @@
                             callbacks));
         }
 
+        @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+        @Override
+        public void onGestureTargetActivityIntentRequested(
+                @NonNull IQuickAccessWalletServiceCallbacks callbacks) {
+            if (launchWalletOptionOnPowerDoubleTap()) {
+                mHandler.post(
+                        () ->
+                                QuickAccessWalletService.this
+                                        .onGestureTargetActivityIntentRequestedInternal(
+                                                callbacks));
+            }
+        }
+
         public void registerWalletServiceEventListener(
                 @NonNull WalletServiceEventListenerRequest request,
                 @NonNull IQuickAccessWalletServiceCallbacks callback) {
@@ -275,6 +291,20 @@
         }
     }
 
+    private void onGestureTargetActivityIntentRequestedInternal(
+            IQuickAccessWalletServiceCallbacks callbacks) {
+        if (!Flags.launchWalletOptionOnPowerDoubleTap()) {
+            return;
+        }
+
+        try {
+            callbacks.onGestureTargetActivityPendingIntentReceived(
+                    getGestureTargetActivityPendingIntent());
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error", e);
+        }
+    }
+
     @Override
     @Nullable
     public IBinder onBind(@NonNull Intent intent) {
@@ -349,6 +379,18 @@
         return null;
     }
 
+    /**
+     * Specify a {@link PendingIntent} to be launched on user gesture.
+     *
+     * <p>The pending intent will be sent when the user performs a gesture to open Wallet.
+     * The pending intent should launch an activity.
+     */
+    @Nullable
+    @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public PendingIntent getGestureTargetActivityPendingIntent() {
+        return null;
+    }
+
     private void sendWalletServiceEventInternal(WalletServiceEvent serviceEvent) {
         if (mEventListener == null) {
             Log.i(TAG, "No dismiss listener registered");
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 032f592..cb72b97 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -50,6 +50,7 @@
 import android.text.style.LineBreakConfigSpan;
 import android.text.style.LineHeightSpan;
 import android.text.style.LocaleSpan;
+import android.text.style.NoWritingToolsSpan;
 import android.text.style.ParagraphStyle;
 import android.text.style.QuoteSpan;
 import android.text.style.RelativeSizeSpan;
@@ -817,7 +818,9 @@
     /** @hide */
     public static final int LINE_BREAK_CONFIG_SPAN = 30;
     /** @hide */
-    public static final int LAST_SPAN = LINE_BREAK_CONFIG_SPAN;
+    public static final int NO_WRITING_TOOLS_SPAN = 31;
+    /** @hide */
+    public static final int LAST_SPAN = NO_WRITING_TOOLS_SPAN;
 
     /**
      * Flatten a CharSequence and whatever styles can be copied across processes
@@ -1025,6 +1028,10 @@
                     span = LineBreakConfigSpan.CREATOR.createFromParcel(p);
                     break;
 
+                case NO_WRITING_TOOLS_SPAN:
+                    span = NoWritingToolsSpan.CREATOR.createFromParcel(p);
+                    break;
+
                 default:
                     throw new RuntimeException("bogus span encoding " + kind);
                 }
diff --git a/core/java/android/text/style/NoWritingToolsSpan.java b/core/java/android/text/style/NoWritingToolsSpan.java
new file mode 100644
index 0000000..90f85aa
--- /dev/null
+++ b/core/java/android/text/style/NoWritingToolsSpan.java
@@ -0,0 +1,87 @@
+/*
+ * 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.text.style;
+
+import static android.view.inputmethod.Flags.FLAG_WRITING_TOOLS;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextUtils;
+
+/**
+ * A span that signals to IMEs that writing tools should not modify the text.
+ *
+ * <p>For example, a text field may contain a mix of user input text and quoted text. The app
+ * can apply {@code NoWritingToolsSpan} to the quoted text so that the IME knows that writing
+ * tools should only rewrite the user input text, and not modify the quoted text.
+ */
+@FlaggedApi(FLAG_WRITING_TOOLS)
+public final class NoWritingToolsSpan implements ParcelableSpan {
+
+    /**
+     * Creates a {@link NoWritingToolsSpan}.
+     */
+    public NoWritingToolsSpan() {
+    }
+
+    @Override
+    public int getSpanTypeId() {
+        return getSpanTypeIdInternal();
+    }
+
+    /** @hide */
+    @Override
+    public int getSpanTypeIdInternal() {
+        return TextUtils.NO_WRITING_TOOLS_SPAN;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        writeToParcelInternal(dest, flags);
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcelInternal(@NonNull Parcel dest, int flags) {
+    }
+
+    @Override
+    public String toString() {
+        return "NoWritingToolsSpan{}";
+    }
+
+    @NonNull
+    public static final Creator<NoWritingToolsSpan> CREATOR = new Creator<>() {
+
+        @Override
+        public NoWritingToolsSpan createFromParcel(Parcel source) {
+            return new NoWritingToolsSpan();
+        }
+
+        @Override
+        public NoWritingToolsSpan[] newArray(int size) {
+            return new NoWritingToolsSpan[size];
+        }
+    };
+}
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 1af9387..1be7f48 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION;
+import static android.service.autofill.Flags.FLAG_AUTOFILL_W_METRICS;
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
@@ -79,27 +80,24 @@
      * Key used for writing the type of the view that generated the virtual structure of its
      * children.
      *
-     * For example, if the virtual structure is generated by a webview, the value would be
+     * <p>For example, if the virtual structure is generated by a webview, the value would be
      * "WebView". If the virtual structure is generated by a compose view, then the value would be
      * "ComposeView". The value is of type String.
      *
-     * This value is added to mainly help with debugging purpose.
-     *
-     * @hide
+     * <p>This value is added to mainly help with debugging purpose.
      */
+    @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
     public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE =
             "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE";
 
-
     /**
      * Key used for specifying the version of the view that generated the virtual structure for
      * itself and its children
      *
-     * For example, if the virtual structure is generated by a webview of version "104.0.5112.69",
-     * then the value should be "104.0.5112.69"
-     *
-     * @hide
+     * <p>For example, if the virtual structure is generated by a webview of version
+     * "104.0.5112.69", then the value should be "104.0.5112.69"
      */
+    @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
     public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER =
             "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER";
 
diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java
index 6b60858..bd27778 100644
--- a/core/java/android/view/autofill/AutofillId.java
+++ b/core/java/android/view/autofill/AutofillId.java
@@ -15,6 +15,9 @@
  */
 package android.view.autofill;
 
+import static android.service.autofill.Flags.FLAG_AUTOFILL_W_METRICS;
+
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
@@ -111,12 +114,43 @@
         return new AutofillId(flags, id.mViewId, virtualChildId, NO_SESSION);
     }
 
-    /** @hide */
+    /**
+     * Returns the assigned unique identifier of this AutofillID.
+     *
+     * See @link{android.view.View#getAutofillId()} for more information on
+     * how this is generated for native Views.
+     */
+    @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
     public int getViewId() {
         return mViewId;
     }
 
     /**
+     * Gets the virtual id. This is set if the view is a virtual view, most commonly set if the View
+     * is of {@link android.webkit.WebView}.
+     */
+    @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+    public int getAutofillVirtualId() {
+        return mVirtualIntId;
+    }
+
+    /** Checks whether this AutofillId represents a virtual view. */
+    @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+    public boolean isVirtual() {
+        return !isNonVirtual();
+    }
+
+    /**
+     * Checks if this node is generate as part of a {@link android.app.assist.AssistStructure}. This
+     * will usually return true if it should be used by an autofill service provider, and false
+     * otherwise.
+     */
+    @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+    public boolean isInAutofillSession() {
+        return hasSession();
+    }
+
+    /**
      * Gets the virtual child id.
      *
      * <p>Should only be used on subsystems where such id is represented by an {@code int}
@@ -181,7 +215,12 @@
         return (mFlags & FLAG_HAS_SESSION) != 0;
     }
 
-    /** @hide */
+    /**
+     * Used to get the Session identifier associated with this AutofillId.
+     *
+     * @return a non-zero integer if {@link #isInAutofillSession()} returns true
+     */
+    @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
     public int getSessionId() {
         return mSessionId;
     }
diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig
index b180e58..ebda4d4 100644
--- a/core/java/android/view/flags/scroll_feedback_flags.aconfig
+++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig
@@ -28,5 +28,5 @@
     namespace: "wear_frameworks"
     name: "dynamic_view_rotary_haptics_configuration"
     description: "Whether ScrollFeedbackProvider dynamically disables View-based rotary haptics."
-    bug: "377998870 "
+    bug: "377998870"
 }
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
index 0f48f12..4f48cb6 100644
--- a/core/java/android/view/inputmethod/InputMethodSession.java
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -16,7 +16,6 @@
 
 package android.view.inputmethod;
 
-import android.annotation.NonNull;
 import android.graphics.Rect;
 import android.inputmethodservice.InputMethodService;
 import android.os.Bundle;
@@ -126,11 +125,6 @@
     public void dispatchKeyEvent(int seq, KeyEvent event, EventCallback callback);
 
     /**
-     * @hide
-     */
-    boolean onShouldVerifyKeyEvent(@NonNull KeyEvent event);
-
-    /**
      * This method is called when there is a track ball event.
      *
      * <p>
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 63f8c80..deaf957 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -177,9 +177,10 @@
 }
 
 flag {
-  name: "verify_key_event"
+  name: "adaptive_handwriting_bounds"
+  is_exported: true
   namespace: "input_method"
-  description: "Verify KeyEvents in IME"
-  bug: "331730488"
+  description: "Feature flag for adaptively increasing handwriting bounds."
+  bug: "350047836"
   is_fixed_read_only: true
-}
\ No newline at end of file
+}
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index 21c7baa..469ab48 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -410,4 +410,15 @@
      * however backups initiated by the framework will call this method to retrieve one.
      */
     void getBackupManagerMonitor(in AndroidFuture<IBackupManagerMonitor> resultFuture);
+
+    /**
+     * Ask the transport whether packages that are about to be backed up or restored should not be
+     * put into a restricted mode by the framework and started normally instead. The
+     * {@code resultFuture} should be completed with a subset of the packages passed in, indicating
+     * which packages should NOT be put into restricted mode for the given operation type.
+     *
+     * @param operationType 0 for backup, 1 for restore.
+     */
+    void getPackagesThatShouldNotUseRestrictedMode(in List<String> packageNames, int operationType,
+            in AndroidFuture<List<String>> resultFuture);
 }
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index a1c987f..eb682df 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -676,15 +676,30 @@
         return internMap.get(string);
     }
 
+    protected boolean validateGroups(ILogger logger, String[] groups) {
+        for (int i = 0; i < groups.length; i++) {
+            String group = groups[i];
+            IProtoLogGroup g = mLogGroups.get(group);
+            if (g == null) {
+                logger.log("No IProtoLogGroup named " + group);
+                return false;
+            }
+        }
+        return true;
+    }
+
     private int setTextLogging(boolean value, ILogger logger, String... groups) {
+        if (!validateGroups(logger, groups)) {
+            return -1;
+        }
+
         for (int i = 0; i < groups.length; i++) {
             String group = groups[i];
             IProtoLogGroup g = mLogGroups.get(group);
             if (g != null) {
                 g.setLogToLogcat(value);
             } else {
-                logger.log("No IProtoLogGroup named " + group);
-                return -1;
+                throw new RuntimeException("No IProtoLogGroup named " + group);
             }
         }
 
diff --git a/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
index 70d148a..967a5ed 100644
--- a/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
@@ -113,6 +113,10 @@
      */
     @Override
     public int startLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
+        if (!validateGroups(logger, groups)) {
+            return -1;
+        }
+
         mViewerConfigReader.loadViewerConfig(groups, logger);
         return super.startLoggingToLogcat(groups, logger);
     }
@@ -125,8 +129,19 @@
      */
     @Override
     public int stopLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
+        if (!validateGroups(logger, groups)) {
+            return -1;
+        }
+
+        var status = super.stopLoggingToLogcat(groups, logger);
+
+        if (status != 0) {
+            throw new RuntimeException("Failed to stop logging to logcat");
+        }
+
+        // If we successfully disabled logging, unload the viewer config.
         mViewerConfigReader.unloadViewerConfig(groups, logger);
-        return super.stopLoggingToLogcat(groups, logger);
+        return status;
     }
 
     @Deprecated
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 9514aaf..0042459 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1080,6 +1080,52 @@
     <permission android:name="android.permission.SATELLITE_COMMUNICATION"
                 android:protectionLevel="role|signature|privileged" />
 
+    <!-- ================================== -->
+    <!-- Permissions associated with picture and sound profiles  -->
+    <!-- ================================== -->
+    <eat-comment />
+
+    <!-- @FlaggedApi(android.media.tv.flags.Flags.FLAG_APPLY_PICTURE_PROFILES)
+         Allows an app to apply a {@link MediaQualityManager.PictureProfile} to a layer via
+         {@link MediaCodec.PARAMETER_KEY_PICTURE_PROFILE} and, additionally, system apps via
+         {@link SurfaceControl.Transaction#setPictureProfileHandle}.
+         -->
+    <permission android:name="android.permission.APPLY_PICTURE_PROFILE"
+        android:protectionLevel="normal"
+        android:featureFlag="android.media.tv.flags.apply_picture_profiles"/>
+
+    <!-- @hide
+         Allows MediaQualityService to observe any {@link MediaQualityManager.PictureProfile}
+         applied to any layer in the system by apps via
+         {@link MediaCodec.PARAMETER_KEY_PICTURE_PROFILE} and by system apps via
+         {@link SurfaceControl.Transaction#setPictureProfileHandle}.
+         -->
+    <permission android:name="android.permission.OBSERVE_PICTURE_PROFILES"
+        android:protectionLevel="signature|privileged"
+        android:featureFlag="android.media.tv.flags.apply_picture_profiles"/>
+
+    <!--
+        @SystemApi
+        @FlaggedApi("android.media.tv.flags.media_quality_fw")
+        Allows an application to access its picture profile from the media quality database.
+        <p> Protection level: signature|privileged|vendor privileged
+        @hide
+    -->
+    <permission android:name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"
+                android:protectionLevel="signature|privileged|vendorPrivileged"
+                android:featureFlag="android.media.tv.flags.media_quality_fw"/>
+
+    <!--
+        @SystemApi
+        @FlaggedApi("android.media.tv.flags.media_quality_fw")
+        Allows an application to access its sound profile from the media quality database.
+        <p> Protection level: signature|privileged|vendor privileged
+        @hide
+    -->
+    <permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE"
+                android:protectionLevel="signature|privileged|vendorPrivileged"
+                android:featureFlag="android.media.tv.flags.media_quality_fw"/>
+
     <!-- ====================================================================== -->
     <!-- Permissions for accessing external storage                             -->
     <!-- ====================================================================== -->
@@ -7875,7 +7921,31 @@
     <!-- @SystemApi Allows an application to access shared libraries.
          @hide -->
     <permission android:name="android.permission.ACCESS_SHARED_LIBRARIES"
-                android:protectionLevel="signature|installer" />
+        android:protectionLevel="signature|installer"
+        android:featureFlag="!android.content.pm.sdk_dependency_installer" />
+
+    <!-- @SystemApi Allows an application to access shared libraries.
+         @hide -->
+    <permission android:name="android.permission.ACCESS_SHARED_LIBRARIES"
+                android:protectionLevel="signature|installer|role"
+        android:featureFlag="android.content.pm.sdk_dependency_installer" />
+
+    <!-- @SystemApi Permission held by the system to allow binding to the dependency installer role
+         holder.
+         @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+         @hide -->
+    <permission android:name="android.permission.BIND_DEPENDENCY_INSTALLER"
+        android:protectionLevel="signature"
+        android:featureFlag="android.content.pm.sdk_dependency_installer" />
+
+    <!-- @SystemApi Allows an application to install shared libraries of types
+         {@link android.content.pm.SharedLibraryInfo#TYPE_STATIC} or
+         {@link android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE}.
+         @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+         @hide -->
+    <permission android:name="android.permission.INSTALL_DEPENDENCY_SHARED_LIBRARIES"
+        android:protectionLevel="signature|role"
+        android:featureFlag="android.content.pm.sdk_dependency_installer" />
 
     <!-- Allows an app to log compat change usage.
          @hide  <p>Not for use by third-party applications.</p> -->
@@ -8596,27 +8666,6 @@
     <permission android:name="android.permission.RESERVED_FOR_TESTING_SIGNATURE"
                 android:protectionLevel="signature"/>
 
-    <!--
-        @SystemApi
-        @FlaggedApi("android.media.tv.flags.media_quality_fw")
-        Allows an application to access its picture profile from the media quality database.
-        <p> Protection level: signature|privileged|vendor privileged
-        @hide
-    -->
-    <permission android:name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"
-                android:protectionLevel="signature|privileged|vendorPrivileged"
-                android:featureFlag="android.media.tv.flags.media_quality_fw"/>
-
-    <!--
-        @SystemApi
-        @FlaggedApi("android.media.tv.flags.media_quality_fw")
-        Allows an application to access its sound profile from the media quality database.
-        <p> Protection level: signature|privileged|vendor privileged
-        @hide
-    -->
-    <permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE"
-                android:protectionLevel="signature|privileged|vendorPrivileged"
-                android:featureFlag="android.media.tv.flags.media_quality_fw"/>
     <!-- @SystemApi
         @FlaggedApi("android.content.pm.verification_service")
         Allows app to be the verification agent to verify packages.
diff --git a/core/res/res/layout/list_content_simple.xml b/core/res/res/layout/list_content_simple.xml
index 6f9f1e0..961668e 100644
--- a/core/res/res/layout/list_content_simple.xml
+++ b/core/res/res/layout/list_content_simple.xml
@@ -20,5 +20,6 @@
 <ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list"
     android:layout_width="match_parent" 
     android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
     android:drawSelectorOnTop="false"
     />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e6dedce..72467b3 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -10285,22 +10285,25 @@
     </declare-styleable>
 
     <!-- @hide -->
+    <attr name="modifierState">
+        <flag name="META" value="0x10000" />
+        <flag name="CTRL" value="0x1000" />
+        <flag name="ALT" value="0x02" />
+        <flag name="SHIFT" value="0x1" />
+        <flag name="SYM" value="0x4" />
+        <flag name="FUNCTION" value="0x8" />
+        <flag name="CAPS_LOCK" value="0x100000" />
+        <flag name="NUM_LOCK" value="0x200000" />
+        <flag name="SCROLL_LOCK" value="0x400000" />
+    </attr>
+
+    <!-- @hide -->
     <declare-styleable name="HardwareDefinedShortcut">
         <attr name="keycode" />
         <!-- The values are taken from public constants for modifier state defined in
              {@see KeyEvent.java}. Here we allow multiple modifier flags as value, since this
              represents the modifier state -->
-        <attr name="modifierState">
-            <flag name="META" value="0x10000" />
-            <flag name="CTRL" value="0x1000" />
-            <flag name="ALT" value="0x02" />
-            <flag name="SHIFT" value="0x1" />
-            <flag name="SYM" value="0x4" />
-            <flag name="FUNCTION" value="0x8" />
-            <flag name="CAPS_LOCK" value="0x100000" />
-            <flag name="NUM_LOCK" value="0x200000" />
-            <flag name="SCROLL_LOCK" value="0x400000" />
-        </attr>
+        <attr name="modifierState" />
         <attr name="outKeycode" />
     </declare-styleable>
 
@@ -10309,6 +10312,11 @@
         <attr name="keycode" />
     </declare-styleable>
 
+    <declare-styleable name="Bookmark">
+        <attr name="keycode" />
+        <attr name="modifierState" />
+    </declare-styleable>
+
     <declare-styleable name="MediaRouteButton">
         <!-- This drawable is a state list where the "activated" state
              indicates active media routing. Non-activated indicates
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3287725..5088b5a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4205,9 +4205,17 @@
          must match the value of config_cameraLaunchGestureSensorType in OEM's HAL -->
     <string translatable="false" name="config_cameraLaunchGestureSensorStringType"></string>
 
+    <!-- Allow the gesture to double tap the power button to trigger a target action. -->
+    <bool name="config_doubleTapPowerGestureEnabled">true</bool>
     <!-- Allow the gesture to double tap the power button twice to start the camera while the device
          is non-interactive. -->
     <bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool>
+    <!-- Allow the gesture to double tap the power button twice to launch the wallet. -->
+    <bool name="config_walletDoubleTapPowerGestureEnabled">true</bool>
+    <!-- Default target action for double tap of the power button gesture.
+         0: Launch camera
+         1: Launch wallet -->
+    <integer name="config_defaultDoubleTapPowerGestureAction">0</integer>
 
     <!-- Allow the gesture to quick tap the power button multiple times to start the emergency sos
          experience while the device is non-interactive. -->
@@ -6567,7 +6575,7 @@
     </string-array>
 
     <!-- the number of the max cached processes in the system. -->
-    <integer name="config_customizedMaxCachedProcesses">32</integer>
+    <integer name="config_customizedMaxCachedProcesses">1024</integer>
 
     <!-- Whether this device should support taking app snapshots on closure -->
     <bool name="config_disableTaskSnapshots">false</bool>
@@ -7217,4 +7225,7 @@
 
     <!-- Whether to enable fp unlock when screen turns off on udfps devices -->
     <bool name="config_screen_off_udfps_enabled">false</bool>
+
+    <!-- The name of the system package that will hold the dependency installer role. -->
+    <string name="config_systemDependencyInstaller" translatable="false" />
 </resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index a0bf89d..ce46c64 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -142,6 +142,9 @@
   </staging-public-group>
 
   <staging-public-group type="string" first-id="0x01b40000">
+    <!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+         @hide @SystemApi -->
+    <public name="config_systemDependencyInstaller" />
   </staging-public-group>
 
   <staging-public-group type="dimen" first-id="0x01b30000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7fe0912..0e12075 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3140,9 +3140,12 @@
   <!-- Gesture -->
   <java-symbol type="integer" name="config_cameraLaunchGestureSensorType" />
   <java-symbol type="string" name="config_cameraLaunchGestureSensorStringType" />
-  <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" />
   <java-symbol type="integer" name="config_cameraLiftTriggerSensorType" />
   <java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" />
+  <java-symbol type="bool" name="config_doubleTapPowerGestureEnabled" />
+  <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" />
+  <java-symbol type="bool" name="config_walletDoubleTapPowerGestureEnabled" />
+  <java-symbol type="integer" name="config_defaultDoubleTapPowerGestureAction" />
   <java-symbol type="bool" name="config_emergencyGestureEnabled" />
   <java-symbol type="bool" name="config_defaultEmergencyGestureEnabled" />
   <java-symbol type="bool" name="config_defaultEmergencyGestureSoundEnabled" />
diff --git a/core/res/res/xml/bookmarks.xml b/core/res/res/xml/bookmarks.xml
index 22d0226..e735784 100644
--- a/core/res/res/xml/bookmarks.xml
+++ b/core/res/res/xml/bookmarks.xml
@@ -31,29 +31,37 @@
        'u': Calculator
        'y': YouTube
 -->
-<bookmarks>
+<bookmarks xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
     <bookmark
         role="android.app.role.BROWSER"
-        shortcut="b" />
+        androidprv:keycode="KEYCODE_B"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_CONTACTS"
-        shortcut="c" />
+        androidprv:keycode="KEYCODE_C"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_EMAIL"
-        shortcut="e" />
+        androidprv:keycode="KEYCODE_E"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_CALENDAR"
-        shortcut="k" />
+        androidprv:keycode="KEYCODE_K"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_MAPS"
-        shortcut="m" />
+        androidprv:keycode="KEYCODE_M"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_MUSIC"
-        shortcut="p" />
+        androidprv:keycode="KEYCODE_P"
+        androidprv:modifierState="META" />
     <bookmark
         role="android.app.role.SMS"
-        shortcut="s" />
+        androidprv:keycode="KEYCODE_S"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_CALCULATOR"
-        shortcut="u" />
+        androidprv:keycode="KEYCODE_U"
+        androidprv:modifierState="META" />
 </bookmarks>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 7423567..fea7cb4 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -93,6 +93,10 @@
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
     </privapp-permissions>
 
+    <privapp-permissions package="com.android.media.quality">
+        <permission name="android.permission.OBSERVE_PICTURE_PROFILES"/>
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.mtp">
         <permission name="android.permission.ACCESS_MTP"/>
         <permission name="android.permission.MANAGE_USB"/>
@@ -262,6 +266,8 @@
         <!-- BLUETOOTH_PRIVILEGED is needed for test only -->
         <permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
         <permission name="android.permission.BIND_APPWIDGET"/>
+        <!-- Needed for CTS tests only -->
+        <permission name="android.permission.OBSERVE_PICTURE_PROFILES"/>
         <permission name="android.permission.CHANGE_APP_IDLE_STATE"/>
         <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
         <permission name="android.permission.CHANGE_CONFIGURATION"/>
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 2c166c3..9bf4d65 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -836,11 +836,13 @@
             mNativeColorFilter = newNativeColorFilter;
             nSetColorFilter(mNativePaint, mNativeColorFilter);
         }
-        if (mXfermode instanceof RuntimeXfermode) {
-            long newNativeXfermode = ((RuntimeXfermode) mXfermode).createNativeInstance();
-            if (newNativeXfermode != mNativeXfermode) {
-                mNativeXfermode = newNativeXfermode;
-                nSetXfermode(mNativePaint, mNativeXfermode);
+        if (com.android.graphics.hwui.flags.Flags.runtimeColorFiltersBlenders()) {
+            if (mXfermode instanceof RuntimeXfermode) {
+                long newNativeXfermode = ((RuntimeXfermode) mXfermode).createNativeInstance();
+                if (newNativeXfermode != mNativeXfermode) {
+                    mNativeXfermode = newNativeXfermode;
+                    nSetXfermode(mNativePaint, mNativeXfermode);
+                }
             }
         }
         return mNativePaint;
@@ -1470,10 +1472,12 @@
 
     @Nullable
     private Xfermode installXfermode(Xfermode xfermode) {
-        if (xfermode instanceof RuntimeXfermode) {
-            mXfermode = xfermode;
-            nSetXfermode(mNativePaint, ((RuntimeXfermode) xfermode).createNativeInstance());
-            return xfermode;
+        if (com.android.graphics.hwui.flags.Flags.runtimeColorFiltersBlenders()) {
+            if (xfermode instanceof RuntimeXfermode) {
+                mXfermode = xfermode;
+                nSetXfermode(mNativePaint, ((RuntimeXfermode) xfermode).createNativeInstance());
+                return xfermode;
+            }
         }
         int newMode = (xfermode instanceof PorterDuffXfermode)
                 ? ((PorterDuffXfermode) xfermode).porterDuffMode : PorterDuffXfermode.DEFAULT;
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 249e9a2..7078d66 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -661,4 +661,7 @@
     <dimen name="desktop_windowing_education_promo_height">352dp</dimen>
     <!-- The corner radius of the desktop windowing education promo. -->
     <dimen name="desktop_windowing_education_promo_corner_radius">28dp</dimen>
+
+    <!-- The corner radius of freeform tasks in desktop windowing. -->
+    <dimen name="desktop_windowing_freeform_rounded_corner_radius">16dp</dimen>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index f532be6..12d20bf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -96,6 +96,14 @@
     }
 
     /**
+     * Get all the displays from DisplayManager.
+     */
+    public Display[] getDisplays() {
+        final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+        return displayManager.getDisplays();
+    }
+
+    /**
      * Gets the DisplayLayout associated with a display.
      */
     public @Nullable DisplayLayout getDisplayLayout(int displayId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index e455985..02df38e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -795,13 +795,14 @@
     static KeyguardTransitionHandler provideKeyguardTransitionHandler(
             ShellInit shellInit,
             ShellController shellController,
+            DisplayController displayController,
             Transitions transitions,
             TaskStackListenerImpl taskStackListener,
             @ShellMainThread Handler mainHandler,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new KeyguardTransitionHandler(
-                shellInit, shellController, transitions, taskStackListener, mainHandler,
-                mainExecutor);
+                shellInit, shellController, displayController, transitions, taskStackListener,
+                mainHandler, mainExecutor);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index e848b88..2ae9828 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -254,8 +254,13 @@
         finishT.hide(sc);
         final Rect startBounds = new Rect(change.getStartAbsBounds());
         animator.addUpdateListener(animation -> {
-            t.setPosition(sc, startBounds.left,
-                    startBounds.top + (animation.getAnimatedFraction() * screenHeight));
+            final float newTop = startBounds.top + (animation.getAnimatedFraction() * screenHeight);
+            t.setPosition(sc, startBounds.left, newTop);
+            if (newTop > screenHeight) {
+                // At this point the task surface is off-screen, so hide it to prevent flicker
+                // failures. See b/377651666.
+                t.hide(sc);
+            }
             t.apply();
         });
         animator.addListener(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index b618bf1..319bfac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -42,6 +42,7 @@
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.view.Display;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.IRemoteTransition;
@@ -54,6 +55,7 @@
 
 import com.android.internal.protolog.ProtoLog;
 import com.android.window.flags.Flags;
+import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
@@ -80,6 +82,8 @@
 
     private final Transitions mTransitions;
     private final ShellController mShellController;
+
+    private final DisplayController mDisplayController;
     private final Handler mMainHandler;
     private final ShellExecutor mMainExecutor;
 
@@ -121,12 +125,14 @@
     public KeyguardTransitionHandler(
             @NonNull ShellInit shellInit,
             @NonNull ShellController shellController,
+            @NonNull DisplayController displayController,
             @NonNull Transitions transitions,
             @NonNull TaskStackListenerImpl taskStackListener,
             @NonNull Handler mainHandler,
             @NonNull ShellExecutor mainExecutor) {
         mTransitions = transitions;
         mShellController = shellController;
+        mDisplayController = displayController;
         mMainHandler = mainHandler;
         mMainExecutor = mainExecutor;
         mTaskStackListener = taskStackListener;
@@ -429,10 +435,10 @@
         @Override
         public void startKeyguardTransition(boolean keyguardShowing, boolean aodShowing) {
             final WindowContainerTransaction wct = new WindowContainerTransaction();
-            final KeyguardState keyguardState =
-                    new KeyguardState.Builder(android.view.Display.DEFAULT_DISPLAY)
-                            .setKeyguardShowing(keyguardShowing).setAodShowing(aodShowing).build();
-            wct.addKeyguardState(keyguardState);
+            for (Display display : mDisplayController.getDisplays()) {
+                wct.addKeyguardState(new KeyguardState.Builder(display.getDisplayId())
+                        .setKeyguardShowing(keyguardShowing).setAodShowing(aodShowing).build());
+            }
             mMainExecutor.execute(() -> {
                 mTransitions.startTransition(keyguardShowing ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
                         wct, KeyguardTransitionHandler.this);
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 e901c39..6d2df95 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
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.pip2.phone;
 
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
 
@@ -293,6 +294,11 @@
 
         // Update the display layout caches even if we are not in PiP.
         setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
+        if (toRotation != ROTATION_UNDEFINED) {
+            // Make sure we rotate to final rotation ourselves in case display change is coming
+            // from the remote rotation as a part of an already collecting transition.
+            mPipDisplayLayoutState.rotateTo(toRotation);
+        }
 
         if (!mPipTransitionState.isInPip()) {
             // Skip the PiP-relevant updates if we aren't in a valid PiP state.
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 d415c10..08e6727 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
@@ -241,7 +241,7 @@
             extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash());
             mPipTransitionState.setState(PipTransitionState.ENTERING_PIP, extra);
 
-            if (mPipTransitionState.isInSwipePipToHomeTransition()) {
+            if (isInSwipePipToHomeTransition()) {
                 // If this is the second transition as a part of swipe PiP to home cuj,
                 // handle this transition as a special case with no-op animation.
                 return handleSwipePipToHomeTransition(info, startTransaction, finishTransaction,
@@ -702,6 +702,13 @@
             @NonNull TransitionInfo.Change pipChange) {
         TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
         int startRotation = pipChange.getStartRotation();
+        if (pipChange.getEndRotation() != ROTATION_UNDEFINED
+                && startRotation != pipChange.getEndRotation()) {
+            // If PiP change was collected along with the display change and the orientation change
+            // happened in sync with the PiP change, then do not treat this as fixed-rotation case.
+            return ROTATION_0;
+        }
+
         int endRotation = fixedRotationChange != null
                 ? fixedRotationChange.getEndFixedRotation() : mPipDisplayLayoutState.getRotation();
         int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index cdcf14e..9cb9d25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -81,7 +81,6 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.IconProvider;
 import com.android.window.flags.Flags;
@@ -1008,8 +1007,10 @@
         relayoutParams.mWindowDecorConfig = windowDecorConfig;
 
         if (DesktopModeStatus.useRoundedCorners()) {
-            relayoutParams.mCornerRadius =
-                    (int) ScreenDecorationsUtils.getWindowCornerRadius(context);
+            relayoutParams.mCornerRadius = taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+                    ? loadDimensionPixelSize(context.getResources(),
+                    R.dimen.desktop_windowing_freeform_rounded_corner_radius)
+                    : INVALID_CORNER_RADIUS;
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index a3c75bf..99f3799 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -107,6 +107,11 @@
     static final int INPUT_SINK_Z_ORDER = -2;
 
     /**
+     * Invalid corner radius that signifies that corner radius should not be set.
+     */
+    static final int INVALID_CORNER_RADIUS = -1;
+
+    /**
      * System-wide context. Only used to create context with overridden configurations.
      */
     final Context mContext;
@@ -449,20 +454,22 @@
             startT.show(mTaskSurface);
         }
 
-        if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
-            if (!DesktopModeStatus.isVeiledResizeEnabled()) {
-                // When fluid resize is enabled, add a background to freeform tasks
-                int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
-                mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
-                mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
-                mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
-                startT.setColor(mTaskSurface, mTmpColor);
-            }
-            startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
-            finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+        if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+                && !DesktopModeStatus.isVeiledResizeEnabled()) {
+            // When fluid resize is enabled, add a background to freeform tasks
+            int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
+            mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
+            mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
+            mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
+            startT.setColor(mTaskSurface, mTmpColor);
         } else if (!DesktopModeStatus.isVeiledResizeEnabled()) {
             startT.unsetColor(mTaskSurface);
         }
+
+        if (params.mCornerRadius != INVALID_CORNER_RADIUS) {
+            startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+            finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontLandscape.kt
new file mode 100644
index 0000000..898964f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontLandscape.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_90
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.BRING_APPS_TO_FRONT
+import com.android.wm.shell.scenarios.BringDesktopAppsToFront
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Bring apps to front by clicking on the App Header.
+ *
+ * Assert that the app windows move to front.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class BringDesktopAppsToFrontLandscape : BringDesktopAppsToFront(rotation = ROTATION_90) {
+
+    @ExpectedScenarios(["BRING_APPS_TO_FRONT"])
+    @Test
+    override fun bringDesktopAppsToFront() = super.bringDesktopAppsToFront()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig()
+                .use(FlickerServiceConfig.DEFAULT)
+                .use(BRING_APPS_TO_FRONT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontPortrait.kt
new file mode 100644
index 0000000..b123d7d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontPortrait.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_0
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.BRING_APPS_TO_FRONT
+import com.android.wm.shell.scenarios.BringDesktopAppsToFront
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Bring apps to front by clicking on the App Header.
+ *
+ * Assert that the app windows move to front.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class BringDesktopAppsToFrontPortrait : BringDesktopAppsToFront(rotation = ROTATION_0) {
+
+    @ExpectedScenarios(["BRING_APPS_TO_FRONT"])
+    @Test
+    override fun bringDesktopAppsToFront() = super.bringDesktopAppsToFront()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig()
+                .use(FlickerServiceConfig.DEFAULT)
+                .use(BRING_APPS_TO_FRONT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index 4cddf31..88dc548 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -25,6 +25,7 @@
 import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
 import android.tools.flicker.assertors.assertions.AppWindowAlignsWithOnlyOneDisplayCornerAtEnd
 import android.tools.flicker.assertors.assertions.AppWindowBecomesInvisible
+import android.tools.flicker.assertors.assertions.AppWindowBecomesTopWindow
 import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible
 import android.tools.flicker.assertors.assertions.AppWindowCoversLeftHalfScreenAtEnd
 import android.tools.flicker.assertors.assertions.AppWindowCoversRightHalfScreenAtEnd
@@ -345,6 +346,30 @@
                         ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
             )
 
+        val BRING_APPS_TO_FRONT =
+            FlickerConfigEntry(
+                scenarioId = ScenarioId("BRING_APPS_TO_FRONT"),
+                extractor =
+                    ShellTransitionScenarioExtractor(
+                        transitionMatcher =
+                            object : ITransitionMatcher {
+                                override fun findAll(
+                                    transitions: Collection<Transition>
+                                ): Collection<Transition> {
+                                    return transitions.filter {
+                                        it.type == TransitionType.TO_FRONT
+                                    }
+                                }
+                            }
+                    ),
+                assertions =
+                    AssertionTemplates.COMMON_ASSERTIONS +
+                            listOf(
+                                AppWindowBecomesTopWindow(DESKTOP_MODE_APP),
+                                AppWindowOnTopAtEnd(DESKTOP_MODE_APP),
+                            ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+            )
+
         val CASCADE_APP =
             FlickerConfigEntry(
                 scenarioId = ScenarioId("CASCADE_APP"),
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/BringDesktopAppsToFrontTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/BringDesktopAppsToFrontTest.kt
new file mode 100644
index 0000000..6c8cc68
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/BringDesktopAppsToFrontTest.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.BringDesktopAppsToFront
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [BringDesktopAppsToFront]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class BringDesktopAppsToFrontTest : BringDesktopAppsToFront()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/BringDesktopAppsToFront.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/BringDesktopAppsToFront.kt
new file mode 100644
index 0000000..1db22eb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/BringDesktopAppsToFront.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+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
+
+@Ignore("Test Base Class")
+abstract class BringDesktopAppsToFront(
+    val rotation: Rotation = Rotation.ROTATION_0,
+    isResizable: Boolean = true,
+    isLandscapeApp: Boolean = true,
+) : DesktopScenarioCustomAppTestBase(isResizable, isLandscapeApp) {
+
+    private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+
+    @Rule
+    @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        ChangeDisplayOrientationRule.setRotation(rotation)
+        tapl.enableTransientTaskbar(false)
+        // Launch a first app and snap it to left side so that it doesn't overlap too much with
+        // the next launching app, and their headers are visible enough to switch focus by tapping
+        // on them.
+        testApp.enterDesktopMode(wmHelper, device)
+        testApp.snapResizeDesktopApp(wmHelper, device, instrumentation.context, toLeft = true)
+        mailApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun bringDesktopAppsToFront() {
+        testApp.bringToFront(wmHelper, device)
+        mailApp.bringToFront(wmHelper, device)
+        testApp.bringToFront(wmHelper, device)
+        mailApp.bringToFront(wmHelper, device)
+    }
+
+    @After
+    fun teardown() {
+        mailApp.exit(wmHelper)
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt
index 351a700..f9bf49e 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt
@@ -57,7 +57,7 @@
         Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
         mailApp.launchViaIntent(wmHelper)
         nonResizeableApp.launchViaIntent(wmHelper)
     }
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt
index 3f9927f..16e5373 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt
@@ -40,7 +40,7 @@
     @Before
     fun setup() {
         Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
         mailApp.launchViaIntent(wmHelper)
         newTasksApp.launchViaIntent(wmHelper)
         imeApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt
index 6d52a11..c43a575 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt
@@ -47,7 +47,7 @@
         Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
         // Set string extra to ensure the app is on PiP mode at launch
         pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = mapOf("enter_pip" to "true"))
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
         mailApp.launchViaIntent(wmHelper)
         newTasksApp.launchViaIntent(wmHelper)
         imeApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowSingleWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowSingleWindow.kt
index 91cfd17..786a8b7 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowSingleWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowSingleWindow.kt
@@ -36,7 +36,7 @@
     @Before
     fun setup() {
         Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
index 967bd29..0f546cd 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
@@ -49,7 +49,7 @@
 
     @Test
     open fun enterDesktopWithDrag() {
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopModeWithDrag(wmHelper, device)
     }
 
     @After
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
index f442fdb..2800839 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
@@ -46,7 +46,7 @@
             instrumentation.context.resources.getBoolean(R.bool.config_dragToMaximizeInDesktopMode))
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximiseAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximiseAppWithCornerResize.kt
index 6637b01..5cf51e3 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximiseAppWithCornerResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximiseAppWithCornerResize.kt
@@ -66,7 +66,7 @@
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
         ChangeDisplayOrientationRule.setRotation(rotation)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
         testApp.cornerResize(
             wmHelper,
             device,
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
index a54d497..d2be494 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
@@ -58,7 +58,7 @@
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
         ChangeDisplayOrientationRule.setRotation(rotation)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
     }
 
     @Test
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
index a2b88f2..60a0fb5 100644
--- 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
@@ -59,7 +59,7 @@
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
         ChangeDisplayOrientationRule.setRotation(rotation)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
index b548363..971637b 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
@@ -61,7 +61,7 @@
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
         ChangeDisplayOrientationRule.setRotation(rotation)
-        testApp1.enterDesktopWithDrag(wmHelper, device)
+        testApp1.enterDesktopMode(wmHelper, device)
         testApp2.launchViaIntent(wmHelper)
         testApp3.launchViaIntent(wmHelper)
     }
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt
index b86765e..7987f7e 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt
@@ -58,7 +58,7 @@
     @Before
     fun setup() {
         Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
         mailApp.launchViaIntent(wmHelper)
         newTasksApp.launchViaIntent(wmHelper)
         imeApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt
index aad266f..6ce36f5 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt
@@ -61,7 +61,7 @@
         tapl.setExpectedRotation(rotation.value)
         tapl.enableTransientTaskbar(false)
         ChangeDisplayOrientationRule.setRotation(rotation)
-        firstApp.enterDesktopWithDrag(wmHelper, device)
+        firstApp.enterDesktopMode(wmHelper, device)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
index bfee318..eefa0bb 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
@@ -61,7 +61,7 @@
         Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
         mailApp.launchViaIntent(wmHelper)
         newTasksApp.launchViaIntent(wmHelper)
         imeApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
index 5b1b64e..0226eb3 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
@@ -65,7 +65,7 @@
         tapl.setExpectedRotation(rotation.value)
         // Set string extra to ensure the app is on PiP mode at launch
         pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = mapOf("enter_pip" to "true"))
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
         mailApp.launchViaIntent(wmHelper)
         newTasksApp.launchViaIntent(wmHelper)
         imeApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
index a7cebf4..6463623 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
@@ -67,7 +67,7 @@
         tapl.setEnableRotation(true)
         ChangeDisplayOrientationRule.setRotation(rotation)
         tapl.setExpectedRotation(rotation.value)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt
index 6780238..f198cfe 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt
@@ -60,7 +60,7 @@
         )
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
index 2b40497..fd4c243 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
@@ -56,7 +56,7 @@
     @Before
     fun setup() {
         Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
index b4bd7e1..62e860e 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
@@ -56,7 +56,7 @@
     @Before
     fun setup() {
         Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt
index f08e50e..de330e0 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt
@@ -59,7 +59,7 @@
         Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(0)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
index ce235d4..4b3f15f 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
@@ -67,7 +67,7 @@
         Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(0)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt
index 0051952..a108367 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt
@@ -62,7 +62,7 @@
     @Before
     fun setup() {
         Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt
index dad2eb6..1455bd1 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt
@@ -54,7 +54,7 @@
     @Before
     fun setup() {
         Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
-        testApp.enterDesktopWithDrag(wmHelper, device)
+        testApp.enterDesktopMode(wmHelper, device)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 1d2d0f0..f7b190c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -30,6 +30,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
 import static com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.CLOSE_MAXIMIZE_MENU_DELAY_MS;
+import static com.android.wm.shell.windowdecor.WindowDecoration.INVALID_CORNER_RADIUS;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -312,8 +313,9 @@
     }
 
     @Test
-    public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersAreEnabled() {
+    public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersSetForFreeform() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
         fillRoundedCornersResources(/* fillValue= */ 30);
         RelayoutParams relayoutParams = new RelayoutParams();
 
@@ -334,6 +336,29 @@
     }
 
     @Test
+    public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForFullscreen() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        fillRoundedCornersResources(/* fillValue= */ 30);
+        RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false,
+                /* isStatusBarVisible */ true,
+                /* isKeyguardVisibleAndOccluded */ false,
+                /* inFullImmersiveMode */ false,
+                new InsetsState(),
+                /* hasGlobalFocus= */ true,
+                mExclusionRegion);
+
+        assertThat(relayoutParams.mCornerRadius).isEqualTo(INVALID_CORNER_RADIUS);
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY)
     public void updateRelayoutParams_appHeader_usesTaskDensity() {
         final int systemDensity = mTestableContext.getOrCreateTestableResources().getResources()
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 9325999..0a79f41 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -1157,7 +1157,11 @@
         @Override
         public void setFence(@NonNull SyncFence fence) throws IOException {
             throwISEIfImageIsInvalid();
-            nativeSetFenceFd(fence.getFdDup().detachFd());
+            if (fence.isValid()) {
+                nativeSetFenceFd(fence.getFdDup().detachFd());
+            } else {
+                nativeSetFenceFd(-1);
+            }
         }
 
         @Override
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 5b1ea8b..d8a8c8b 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -1,13 +1,7 @@
 package: "com.android.media.flags"
 container: "system"
 
-flag {
-    name: "enable_rlp_callbacks_in_media_router2"
-    is_exported: true
-    namespace: "media_solutions"
-    description: "Make RouteListingPreference getter and callbacks public in MediaRouter2."
-    bug: "281067101"
-}
+# Flags are ordered alphabetically by name.
 
 flag {
     name: "adjust_volume_for_foreground_app_playing_audio_without_media_session"
@@ -17,6 +11,13 @@
 }
 
 flag {
+    name: "enable_audio_input_device_routing_and_volume_control"
+    namespace: "media_better_together"
+    description: "Allows audio input devices routing and volume control via system settings."
+    bug: "355684672"
+}
+
+flag {
     name: "enable_audio_policies_device_and_bluetooth_controller"
     is_exported: true
     namespace: "media_solutions"
@@ -25,17 +26,54 @@
 }
 
 flag {
-    name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling"
-    namespace: "media_solutions"
-    description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller."
-    bug: "293743975"
+     name: "enable_built_in_speaker_route_suitability_statuses"
+     is_exported: true
+     namespace: "media_solutions"
+     description: "Make MediaRoute2Info provide information about routes suitability for transfer."
+     bug: "279555229"
 }
 
 flag {
-    name: "enable_waiting_state_for_system_session_creation_request"
+    name: "enable_cross_user_routing_in_media_router2"
+    is_exported: true
     namespace: "media_solutions"
-    description: "Introduces a waiting state for the session creation request and prevents it from early failing when the selectedRoute from the bluetooth stack doesn't match the pending request route id."
-    bug: "307723189"
+    description: "Allows clients of privileged MediaRouter2 that hold INTERACT_ACROSS_USERS_FULL to control routing across users."
+    bug: "288580225"
+}
+
+flag {
+    name: "enable_full_scan_with_media_content_control"
+    namespace: "media_better_together"
+    description: "Allows holders of the MEDIA_CONTENT_CONTROL permission to scan for routes while not in the foreground."
+    bug: "352401364"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
+    name: "enable_get_transferable_routes"
+    is_exported: true
+    namespace: "media_solutions"
+    description: "Exposes RoutingController#getTransferableRoutes() (previously hidden) to the public API."
+    bug: "323154573"
+}
+
+flag {
+    name: "enable_mirroring_in_media_router_2"
+    namespace: "media_better_together"
+    description: "Enables support for mirroring routes in the MediaRouter2 framework, allowing Output Switcher to offer mirroring routes."
+    bug: "362507305"
+}
+
+flag {
+    name: "enable_mr2_service_non_main_bg_thread"
+    namespace: "media_solutions"
+    description: "Enables the use of a background thread in the media routing framework, instead of using the main thread."
+    bug: "310145678"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
 }
 
 flag {
@@ -55,37 +93,6 @@
 }
 
 flag {
-    name: "enable_privileged_routing_for_media_routing_control"
-    is_exported: true
-    namespace: "media_solutions"
-    description: "Allow access to privileged routing capabilities to MEDIA_ROUTING_CONTROL holders."
-    bug: "305919655"
-}
-
-flag {
-    name: "enable_cross_user_routing_in_media_router2"
-    is_exported: true
-    namespace: "media_solutions"
-    description: "Allows clients of privileged MediaRouter2 that hold INTERACT_ACROSS_USERS_FULL to control routing across users."
-    bug: "288580225"
-}
-
-flag {
-    name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name"
-    namespace: "media_solutions"
-    description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos."
-    bug: "314324170"
-}
-
-flag {
-     name: "enable_built_in_speaker_route_suitability_statuses"
-     is_exported: true
-     namespace: "media_solutions"
-     description: "Make MediaRoute2Info provide information about routes suitability for transfer."
-     bug: "279555229"
-}
-
-flag {
     name: "enable_notifying_activity_manager_with_media_session_status_change"
     is_exported: true
     namespace: "media_solutions"
@@ -94,11 +101,10 @@
 }
 
 flag {
-    name: "enable_get_transferable_routes"
-    is_exported: true
+    name: "enable_null_session_in_media_browser_service"
     namespace: "media_solutions"
-    description: "Exposes RoutingController#getTransferableRoutes() (previously hidden) to the public API."
-    bug: "323154573"
+    description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
+    bug: "185136506"
 }
 
 flag {
@@ -109,31 +115,6 @@
 }
 
 flag {
-    name: "enable_mr2_service_non_main_bg_thread"
-    namespace: "media_solutions"
-    description: "Enables the use of a background thread in the media routing framework, instead of using the main thread."
-    bug: "310145678"
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
-}
-
-flag {
-    name: "enable_screen_off_scanning"
-    is_exported: true
-    namespace: "media_solutions"
-    description: "Enable new MediaRouter2 API to enable watch companion apps to scan while the phone screen is off."
-    bug: "281072508"
-}
-
-flag {
-    name: "enable_null_session_in_media_browser_service"
-    namespace: "media_solutions"
-    description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
-    bug: "185136506"
-}
-
-flag {
     name: "enable_prevention_of_manager_scans_when_no_apps_scan"
     namespace: "media_solutions"
     description: "Prevents waking up route providers when no apps are scanning, even if SysUI or Settings are scanning."
@@ -144,25 +125,46 @@
 }
 
 flag {
-    name: "enable_full_scan_with_media_content_control"
-    namespace: "media_better_together"
-    description: "Allows holders of the MEDIA_CONTENT_CONTROL permission to scan for routes while not in the foreground."
-    bug: "352401364"
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
+    name: "enable_privileged_routing_for_media_routing_control"
+    is_exported: true
+    namespace: "media_solutions"
+    description: "Allow access to privileged routing capabilities to MEDIA_ROUTING_CONTROL holders."
+    bug: "305919655"
 }
 
 flag {
-    name: "enable_audio_input_device_routing_and_volume_control"
-    namespace: "media_better_together"
-    description: "Allows audio input devices routing and volume control via system settings."
-    bug: "355684672"
+    name: "enable_rlp_callbacks_in_media_router2"
+    is_exported: true
+    namespace: "media_solutions"
+    description: "Make RouteListingPreference getter and callbacks public in MediaRouter2."
+    bug: "281067101"
 }
 
 flag {
-    name: "enable_mirroring_in_media_router_2"
-    namespace: "media_better_together"
-    description: "Enables support for mirroring routes in the MediaRouter2 framework, allowing Output Switcher to offer mirroring routes."
-    bug: "362507305"
+    name: "enable_screen_off_scanning"
+    is_exported: true
+    namespace: "media_solutions"
+    description: "Enable new MediaRouter2 API to enable watch companion apps to scan while the phone screen is off."
+    bug: "281072508"
+}
+
+flag {
+    name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name"
+    namespace: "media_solutions"
+    description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos."
+    bug: "314324170"
+}
+
+flag {
+    name: "enable_waiting_state_for_system_session_creation_request"
+    namespace: "media_solutions"
+    description: "Introduces a waiting state for the session creation request and prevents it from early failing when the selectedRoute from the bluetooth stack doesn't match the pending request route id."
+    bug: "307723189"
+}
+
+flag {
+    name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling"
+    namespace: "media_solutions"
+    description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller."
+    bug: "293743975"
 }
diff --git a/media/java/android/media/flags/projection.aconfig b/media/java/android/media/flags/projection.aconfig
index 17d1ff6..1bb9a8e 100644
--- a/media/java/android/media/flags/projection.aconfig
+++ b/media/java/android/media/flags/projection.aconfig
@@ -18,3 +18,10 @@
     bug: "362720120"
     is_exported: true
 }
+
+flag {
+     namespace: "media_projection"
+     name: "stop_media_projection_on_call_end"
+     description: "Stops MediaProjection sessions when a call ends"
+     bug: "368336349"
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/extension/oad/IOadUpdateInterface.aidl b/media/java/android/media/tv/extension/oad/IOadUpdateInterface.aidl
new file mode 100644
index 0000000..2a2e71a
--- /dev/null
+++ b/media/java/android/media/tv/extension/oad/IOadUpdateInterface.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.tv.extension.oad;
+
+/**
+ * @hide
+ */
+interface IOadUpdateInterface {
+    // Enable or disable the OAD function.
+    void setOadStatus(boolean enable);
+    // Get status of OAD function.
+    boolean getOadStatus();
+    // Start OAD scan of all frequency in the program list.
+    void startScan();
+    // Stop OAD scan of all frequency in the program list.
+    void stopScan();
+    // Start OAD detect for the current channel.
+    void startDetect();
+    // Stop OAD detect for the current channel.
+    void stopDetect();
+    // Start OAD download after it has been detected or scanned.
+    void startDownload();
+    // Stop OAD download.
+    void stopDownload();
+    // Retrieves current OAD software version.
+    int getSoftwareVersion();
+}
diff --git a/media/java/android/media/tv/extension/rating/IDownloadableRatingTableMonitor.aidl b/media/java/android/media/tv/extension/rating/IDownloadableRatingTableMonitor.aidl
new file mode 100644
index 0000000..bf1a385
--- /dev/null
+++ b/media/java/android/media/tv/extension/rating/IDownloadableRatingTableMonitor.aidl
@@ -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 android.media.tv.extension.rating;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IDownloadableRatingTableMonitor {
+    // Get RRT rating info on downloadable rating data
+    Bundle[] getTable();
+}
diff --git a/media/java/android/media/tv/extension/rating/IPmtRatingInterface.aidl b/media/java/android/media/tv/extension/rating/IPmtRatingInterface.aidl
new file mode 100644
index 0000000..06cac3d
--- /dev/null
+++ b/media/java/android/media/tv/extension/rating/IPmtRatingInterface.aidl
@@ -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 android.media.tv.extension.rating;
+
+import android.media.tv.extension.rating.IPmtRatingListener;
+
+/**
+ * @hide
+ */
+interface IPmtRatingInterface {
+    // Get Pmt rating information.
+    String getPmtRating(String sessionToken);
+    // Register a listener for pmt rating updates.
+    void addPmtRatingListener(String clientToken, in IPmtRatingListener listener);
+    // Remove the previously added IPmtRatingListener.
+    void removePmtRatingListener(in IPmtRatingListener listener);
+}
diff --git a/media/java/android/media/tv/extension/rating/IPmtRatingListener.aidl b/media/java/android/media/tv/extension/rating/IPmtRatingListener.aidl
new file mode 100644
index 0000000..d88ae94
--- /dev/null
+++ b/media/java/android/media/tv/extension/rating/IPmtRatingListener.aidl
@@ -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 android.media.tv.extension.rating;
+
+/**
+ * @hide
+ */
+oneway interface IPmtRatingListener {
+    void onPmtRatingChanged(String sessionToken, String newTvContentRating);
+}
diff --git a/media/java/android/media/tv/extension/rating/IProgramRatingInfo.aidl b/media/java/android/media/tv/extension/rating/IProgramRatingInfo.aidl
new file mode 100644
index 0000000..a490491
--- /dev/null
+++ b/media/java/android/media/tv/extension/rating/IProgramRatingInfo.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.rating;
+
+import android.media.tv.extension.rating.IProgramRatingInfoListener;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IProgramRatingInfo {
+    // Register a listener to receive notifications when ProgramRatingInfo is updated.
+    void addProgramRatingInfoListener(String clientToken, in IProgramRatingInfoListener listener);
+    // Remove a listener for ProgramRatingInfo update notifications.
+    void removeProgramRatingInfoListener(in IProgramRatingInfoListener listener);
+    // Get ProgramRatingInfo that may only be obtained when viewing.
+    Bundle getProgramRatingInfo(String sessionToken);
+}
diff --git a/media/java/android/media/tv/extension/rating/IProgramRatingInfoListener.aidl b/media/java/android/media/tv/extension/rating/IProgramRatingInfoListener.aidl
new file mode 100644
index 0000000..6777cd3
--- /dev/null
+++ b/media/java/android/media/tv/extension/rating/IProgramRatingInfoListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.rating;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IProgramRatingInfoListener {
+    void onProgramInfoChanged(String sessionToken,in Bundle changedProgramInfo);
+}
diff --git a/media/java/android/media/tv/extension/rating/IRatingInterface.aidl b/media/java/android/media/tv/extension/rating/IRatingInterface.aidl
new file mode 100644
index 0000000..d68fe76
--- /dev/null
+++ b/media/java/android/media/tv/extension/rating/IRatingInterface.aidl
@@ -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 android.media.tv.extension.rating;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IRatingInterface {
+    // Get RRT rating information
+    Bundle getRRTRatingInfo();
+    // Set RRT rating information when user select
+    boolean setRRTRatingInfo(in Bundle param);
+    // Reset RRT5 to clear information
+    boolean setResetRrt5();
+}
diff --git a/media/java/android/media/tv/extension/rating/IVbiRatingInterface.aidl b/media/java/android/media/tv/extension/rating/IVbiRatingInterface.aidl
new file mode 100644
index 0000000..bad4067
--- /dev/null
+++ b/media/java/android/media/tv/extension/rating/IVbiRatingInterface.aidl
@@ -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 android.media.tv.extension.rating;
+
+import android.media.tv.extension.rating.IVbiRatingListener;
+
+/**
+ * @hide
+ */
+interface IVbiRatingInterface {
+    // Get Vbi rating.
+    String getVbiRating(String sessionToken);
+    // Register a listener for Vbi rating updates.
+    void addVbiRatingListener(String clientToken, in IVbiRatingListener listener);
+    // Remove the previously added VbiRatingListener.
+    void removeVbiRatingListener(in IVbiRatingListener listener);
+}
diff --git a/media/java/android/media/tv/extension/rating/IVbiRatingListener.aidl b/media/java/android/media/tv/extension/rating/IVbiRatingListener.aidl
new file mode 100644
index 0000000..36d523f
--- /dev/null
+++ b/media/java/android/media/tv/extension/rating/IVbiRatingListener.aidl
@@ -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 android.media.tv.extension.rating;
+
+/**
+ * @hide
+ */
+oneway interface IVbiRatingListener {
+    void onVbiRatingChanged(String sessionToken, String newTvContentRating);
+}
diff --git a/media/java/android/media/tv/extension/time/IBroadcastTime.aidl b/media/java/android/media/tv/extension/time/IBroadcastTime.aidl
new file mode 100644
index 0000000..123d00f
--- /dev/null
+++ b/media/java/android/media/tv/extension/time/IBroadcastTime.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.time;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IBroadcastTime {
+    long getUtcTime();
+    long getLocalTime();
+    Bundle getTimeZoneInfo();
+    long getUtcTimePerStream(String SessionToken);
+    long getLocalTimePerStream(String SessionToken);
+}
\ No newline at end of file
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index a6b1c4f..79a0607 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -89,12 +89,12 @@
     method public void onBootFinished(int);
     method public void onBootStarted();
     method public void onCardEmulationActivated(boolean);
-    method public void onDisable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onDisableFinished(int);
+    method public void onDisableRequested(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onDisableStarted();
     method public void onEeListenActivated(boolean);
-    method public void onEnable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onEnableFinished(int);
+    method public void onEnableRequested(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onEnableStarted();
     method public void onGetOemAppSearchIntent(@NonNull java.util.List<java.lang.String>, @NonNull java.util.function.Consumer<android.content.Intent>);
     method public void onHceEventReceived(int);
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 57ee981..fd131b8 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -23,11 +23,11 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.DurationMillisLong;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
-import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
@@ -233,8 +233,7 @@
          *                  {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}.
          * false if NFC cannot be enabled at this time.
          */
-        @SuppressLint("MethodNameTense")
-        void onEnable(@NonNull Consumer<Boolean> isAllowed);
+        void onEnableRequested(@NonNull Consumer<Boolean> isAllowed);
         /**
          * Method to check if Nfc is allowed to be disabled by OEMs.
          * @param isAllowed The {@link Consumer} to be completed. If disabling NFC is allowed,
@@ -242,7 +241,7 @@
          *                  {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}.
          * false if NFC cannot be disabled at this time.
          */
-        void onDisable(@NonNull Consumer<Boolean> isAllowed);
+        void onDisableRequested(@NonNull Consumer<Boolean> isAllowed);
 
         /**
          * Callback to indicate that Nfc starts to boot.
@@ -255,7 +254,7 @@
         void onEnableStarted();
 
         /**
-         * Callback to indicate that Nfc starts to enable.
+         * Callback to indicate that Nfc starts to disable.
          */
         void onDisableStarted();
 
@@ -605,12 +604,12 @@
     /**
      * Pauses NFC tag reader mode polling for a {@code timeoutInMs} millisecond.
      * In case of {@code timeoutInMs} is zero or invalid polling will be stopped indefinitely
-     * use {@link #resumePolling() to resume the polling.
-     * @param timeoutInMs the pause polling duration in millisecond
+     * use {@link #resumePolling()} to resume the polling.
+     * @param timeoutInMs the pause polling duration in millisecond, ranging from 0 to 40000.
      */
     @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    public void pausePolling(int timeoutInMs) {
+    public void pausePolling(@DurationMillisLong int timeoutInMs) {
         NfcAdapter.callService(() -> NfcAdapter.sService.pausePolling(timeoutInMs));
     }
 
@@ -799,13 +798,13 @@
         public void onEnable(ResultReceiver isAllowed) throws RemoteException {
             mCallbackMap.forEach((cb, ex) ->
                     handleVoidCallback(
-                        new ReceiverWrapper<>(isAllowed), cb::onEnable, ex));
+                        new ReceiverWrapper<>(isAllowed), cb::onEnableRequested, ex));
         }
         @Override
         public void onDisable(ResultReceiver isAllowed) throws RemoteException {
             mCallbackMap.forEach((cb, ex) ->
                     handleVoidCallback(
-                        new ReceiverWrapper<>(isAllowed), cb::onDisable, ex));
+                        new ReceiverWrapper<>(isAllowed), cb::onDisableRequested, ex));
         }
         @Override
         public void onBootStarted() throws RemoteException {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
index 83ee975..80e5e59 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
@@ -15,6 +15,7 @@
  */
 package com.android.settingslib.media;
 
+import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET;
 import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
 import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
 import static android.media.AudioDeviceInfo.TYPE_USB_ACCESSORY;
@@ -103,7 +104,8 @@
                             TYPE_USB_DEVICE,
                             TYPE_USB_HEADSET,
                             TYPE_USB_ACCESSORY,
-                            TYPE_BLUETOOTH_SCO ->
+                            TYPE_BLUETOOTH_SCO,
+                            TYPE_BLE_HEADSET ->
                     true;
             default -> false;
         };
@@ -124,7 +126,7 @@
                     mProductName != null
                             ? mProductName
                             : mContext.getString(R.string.media_transfer_usb_device_mic_name);
-            case TYPE_BLUETOOTH_SCO ->
+            case TYPE_BLUETOOTH_SCO, TYPE_BLE_HEADSET ->
                     mProductName != null
                             ? mProductName
                             : mContext.getString(R.string.media_transfer_bt_device_mic_name);
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
index 5eeb49a..6842d0a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -204,6 +204,13 @@
         return this;
     }
 
+    public TestModeBuilder implicitForPackage(String pkg) {
+        setPackage(pkg);
+        setId(ZenModeConfig.implicitRuleId(pkg));
+        setName("Do Not Disturb (" + pkg + ")");
+        return this;
+    }
+
     public TestModeBuilder setActive(boolean active) {
         if (active) {
             mConfigZenRule.enabled = true;
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 34d3bd9..d5cfe55 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -208,6 +208,11 @@
     }
 
     @NonNull
+    public Kind getKind() {
+        return mKind;
+    }
+
+    @NonNull
     public Status getStatus() {
         return mStatus;
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
index 7775b91..8624c4d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
@@ -38,6 +38,7 @@
     private final int WIRED_HEADSET_ID = 2;
     private final int USB_HEADSET_ID = 3;
     private final int BT_HEADSET_ID = 4;
+    private final int BLE_HEADSET_ID = 5;
     private final int MAX_VOLUME = 1;
     private final int CURRENT_VOLUME = 0;
     private final boolean IS_VOLUME_FIXED = true;
@@ -45,6 +46,7 @@
     private static final String PRODUCT_NAME_WIRED_HEADSET = "My Wired Headset";
     private static final String PRODUCT_NAME_USB_HEADSET = "My USB Headset";
     private static final String PRODUCT_NAME_BT_HEADSET = "My Bluetooth Headset";
+    private static final String PRODUCT_NAME_BLE_HEADSET = "My BLE Headset";
 
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
@@ -163,4 +165,35 @@
         assertThat(btMediaDevice.getName())
                 .isEqualTo(mContext.getString(R.string.media_transfer_bt_device_mic_name));
     }
+
+    @Test
+    public void getName_returnCorrectName_bleHeadset() {
+        InputMediaDevice bleMediaDevice =
+                InputMediaDevice.create(
+                        mContext,
+                        String.valueOf(BLE_HEADSET_ID),
+                        AudioDeviceInfo.TYPE_BLE_HEADSET,
+                        MAX_VOLUME,
+                        CURRENT_VOLUME,
+                        IS_VOLUME_FIXED,
+                        PRODUCT_NAME_BLE_HEADSET);
+        assertThat(bleMediaDevice).isNotNull();
+        assertThat(bleMediaDevice.getName()).isEqualTo(PRODUCT_NAME_BLE_HEADSET);
+    }
+
+    @Test
+    public void getName_returnCorrectName_bleHeadset_nullProductName() {
+        InputMediaDevice bleMediaDevice =
+                InputMediaDevice.create(
+                        mContext,
+                        String.valueOf(BLE_HEADSET_ID),
+                        AudioDeviceInfo.TYPE_BLE_HEADSET,
+                        MAX_VOLUME,
+                        CURRENT_VOLUME,
+                        IS_VOLUME_FIXED,
+                        null);
+        assertThat(bleMediaDevice).isNotNull();
+        assertThat(bleMediaDevice.getName())
+                .isEqualTo(mContext.getString(R.string.media_transfer_bt_device_mic_name));
+    }
 }
diff --git a/packages/SettingsProvider/res/xml/bookmarks.xml b/packages/SettingsProvider/res/xml/bookmarks.xml
index 22d0226..645b275 100644
--- a/packages/SettingsProvider/res/xml/bookmarks.xml
+++ b/packages/SettingsProvider/res/xml/bookmarks.xml
@@ -32,6 +32,9 @@
        'y': YouTube
 -->
 <bookmarks>
+    <!-- TODO(b/358569822): Remove this from Settings DB
+         This is legacy implementation to store bookmarks in Settings DB, which is deprecated and
+         no longer used -->
     <bookmark
         role="android.app.role.BROWSER"
         shortcut="b" />
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 859445e..526320d 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -369,6 +369,9 @@
     <!-- Permission needed for CTS test - UnsupportedErrorDialogTests -->
     <uses-permission android:name="android.permission.RESET_APP_ERRORS" />
 
+    <!-- Permission needed tests -->
+    <uses-permission android:name="android.permission.OBSERVE_PICTURE_PROFILES" />
+
     <!-- Permission needed for CTS test - CtsSystemUiTestCases:PipNotificationTests -->
     <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
 
diff --git a/packages/Shell/aconfig/Android.bp b/packages/Shell/aconfig/Android.bp
new file mode 100644
index 0000000..1d797b2
--- /dev/null
+++ b/packages/Shell/aconfig/Android.bp
@@ -0,0 +1,13 @@
+aconfig_declarations {
+    name: "wear_aconfig_declarations",
+    package: "com.android.shell.flags",
+    container: "system",
+    srcs: [
+        "wear.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "wear_aconfig_declarations_flags_java_lib",
+    aconfig_declarations: "wear_aconfig_declarations",
+}
diff --git a/packages/Shell/aconfig/wear.aconfig b/packages/Shell/aconfig/wear.aconfig
new file mode 100644
index 0000000..e07bd96
--- /dev/null
+++ b/packages/Shell/aconfig/wear.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.shell.flags"
+container: "system"
+
+flag {
+    name: "handle_bugreports_for_wear"
+    namespace: "wear_services"
+    description: "This flag enables Shell to propagate bugreport results to WearServices."
+    bug: "378060870"
+}
\ No newline at end of file
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a4b8821..123f823 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1748,6 +1748,13 @@
 }
 
 flag {
+    name: "notification_shade_blur"
+    namespace: "systemui"
+    description: "Enables the new blur effect on the Notification Shade."
+    bug: "370555223"
+}
+
+flag {
   name: "ensure_enr_views_visibility"
   namespace: "systemui"
   description: "Ensures public and private visibilities"
@@ -1790,4 +1797,4 @@
     metadata {
       purpose: PURPOSE_BUGFIX
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 18f40c9..eee0caf 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -24,18 +24,22 @@
 import android.content.ComponentName
 import android.graphics.Color
 import android.graphics.Matrix
+import android.graphics.PointF
 import android.graphics.Rect
 import android.graphics.RectF
 import android.os.Binder
 import android.os.Build
 import android.os.Handler
+import android.os.IBinder
 import android.os.Looper
 import android.os.RemoteException
+import android.util.ArrayMap
 import android.util.Log
 import android.view.IRemoteAnimationFinishedCallback
 import android.view.IRemoteAnimationRunner
 import android.view.RemoteAnimationAdapter
 import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
 import android.view.SyncRtSurfaceTransactionApplier
 import android.view.View
 import android.view.ViewGroup
@@ -45,8 +49,12 @@
 import android.view.WindowManager.TRANSIT_TO_BACK
 import android.view.WindowManager.TRANSIT_TO_FRONT
 import android.view.animation.PathInterpolator
+import android.window.IRemoteTransition
+import android.window.IRemoteTransitionFinishedCallback
 import android.window.RemoteTransition
 import android.window.TransitionFilter
+import android.window.TransitionInfo
+import android.window.WindowAnimationState
 import androidx.annotation.AnyThread
 import androidx.annotation.BinderThread
 import androidx.annotation.UiThread
@@ -55,11 +63,14 @@
 import com.android.internal.policy.ScreenDecorationsUtils
 import com.android.systemui.Flags.activityTransitionUseLargestWindow
 import com.android.systemui.Flags.translucentOccludingActivityFix
+import com.android.systemui.animation.TransitionAnimator.Companion.assertLongLivedReturnAnimations
+import com.android.systemui.animation.TransitionAnimator.Companion.assertReturnAnimations
+import com.android.systemui.animation.TransitionAnimator.Companion.longLivedReturnAnimationsEnabled
+import com.android.systemui.animation.TransitionAnimator.Companion.returnAnimationsEnabled
 import com.android.systemui.animation.TransitionAnimator.Companion.toTransitionState
-import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
-import com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived
 import com.android.wm.shell.shared.IShellTransitions
 import com.android.wm.shell.shared.ShellTransitions
+import com.android.wm.shell.shared.TransitionUtil
 import java.util.concurrent.Executor
 import kotlin.math.roundToInt
 
@@ -194,7 +205,13 @@
         private const val LONG_TRANSITION_TIMEOUT = 5_000L
 
         private fun defaultTransitionAnimator(mainExecutor: Executor): TransitionAnimator {
-            return TransitionAnimator(mainExecutor, TIMINGS, INTERPOLATORS)
+            return TransitionAnimator(
+                mainExecutor,
+                TIMINGS,
+                INTERPOLATORS,
+                SPRING_TIMINGS,
+                SPRING_INTERPOLATORS,
+            )
         }
 
         private fun defaultDialogToAppAnimator(mainExecutor: Executor): TransitionAnimator {
@@ -275,7 +292,7 @@
                     "ActivityTransitionAnimator.callback must be set before using this animator"
                 )
         val runner = createRunner(controller)
-        val runnerDelegate = runner.delegate!!
+        val runnerDelegate = runner.delegate
         val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen
 
         // Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the
@@ -330,7 +347,11 @@
         // If we expect an animation, post a timeout to cancel it in case the remote animation is
         // never started.
         if (willAnimate) {
-            runnerDelegate.postTimeouts()
+            if (longLivedReturnAnimationsEnabled()) {
+                runner.postTimeouts()
+            } else {
+                runnerDelegate!!.postTimeouts()
+            }
 
             // Hide the keyguard using the launch animation instead of the default unlock animation.
             if (hideKeyguardWithAnimation) {
@@ -390,7 +411,7 @@
         launchController: Controller,
         transitionRegister: TransitionRegister?,
     ) {
-        if (!returnAnimationFrameworkLibrary()) return
+        if (!returnAnimationsEnabled()) return
 
         var cleanUpRunnable: Runnable? = null
         val returnRunner =
@@ -413,7 +434,8 @@
                     private fun cleanUp() {
                         cleanUpRunnable?.run()
                     }
-                }
+                },
+                initializeLazily = longLivedReturnAnimationsEnabled(),
             )
 
         // mTypeSet and mModes match back signals only, and not home. This is on purpose, because
@@ -435,7 +457,11 @@
                 "${launchController.transitionCookie}_returnTransition",
             )
 
-        transitionRegister?.register(filter, transition)
+        transitionRegister?.register(
+            filter,
+            transition,
+            includeTakeover = longLivedReturnAnimationsEnabled(),
+        )
         cleanUpRunnable = Runnable { transitionRegister?.unregister(transition) }
     }
 
@@ -451,7 +477,10 @@
 
     /** Create a new animation [Runner] controlled by [controller]. */
     @VisibleForTesting
-    fun createRunner(controller: Controller): Runner {
+    @JvmOverloads
+    fun createRunner(controller: Controller, initializeLazily: Boolean = false): Runner {
+        if (initializeLazily) assertLongLivedReturnAnimations()
+
         // Make sure we use the modified timings when animating a dialog into an app.
         val transitionAnimator =
             if (controller.isDialogLaunch) {
@@ -460,7 +489,13 @@
                 transitionAnimator
             }
 
-        return Runner(controller, callback!!, transitionAnimator, lifecycleListener)
+        return Runner(
+            controller,
+            callback!!,
+            transitionAnimator,
+            lifecycleListener,
+            initializeLazily,
+        )
     }
 
     interface PendingIntentStarter {
@@ -628,10 +663,7 @@
      * this registration.
      */
     fun register(controller: Controller) {
-        check(returnAnimationFrameworkLongLived()) {
-            "Long-lived registrations cannot be used when the returnAnimationFrameworkLongLived " +
-                "flag is disabled"
-        }
+        assertLongLivedReturnAnimations()
 
         if (transitionRegister == null) {
             throw IllegalStateException(
@@ -667,10 +699,10 @@
             }
         val launchRemoteTransition =
             RemoteTransition(
-                RemoteAnimationRunnerCompat.wrap(createRunner(controller)),
+                OriginTransition(createRunner(controller, initializeLazily = true)),
                 "${cookie}_launchTransition",
             )
-        transitionRegister.register(launchFilter, launchRemoteTransition)
+        transitionRegister.register(launchFilter, launchRemoteTransition, includeTakeover = true)
 
         val returnController =
             object : Controller by controller {
@@ -689,10 +721,10 @@
             }
         val returnRemoteTransition =
             RemoteTransition(
-                RemoteAnimationRunnerCompat.wrap(createRunner(returnController)),
+                OriginTransition(createRunner(returnController, initializeLazily = true)),
                 "${cookie}_returnTransition",
             )
-        transitionRegister.register(returnFilter, returnRemoteTransition)
+        transitionRegister.register(returnFilter, returnRemoteTransition, includeTakeover = true)
 
         longLivedTransitions[cookie] = Pair(launchRemoteTransition, returnRemoteTransition)
     }
@@ -738,14 +770,154 @@
         }
     }
 
+    /** [Runner] wrapper that supports animation takeovers. */
+    private inner class OriginTransition(private val runner: Runner) : IRemoteTransition {
+        private val delegate = RemoteAnimationRunnerCompat.wrap(runner)
+
+        init {
+            assertLongLivedReturnAnimations()
+        }
+
+        override fun startAnimation(
+            token: IBinder?,
+            info: TransitionInfo?,
+            t: SurfaceControl.Transaction?,
+            finishCallback: IRemoteTransitionFinishedCallback?,
+        ) {
+            delegate.startAnimation(token, info, t, finishCallback)
+        }
+
+        override fun mergeAnimation(
+            transition: IBinder?,
+            info: TransitionInfo?,
+            t: SurfaceControl.Transaction?,
+            mergeTarget: IBinder?,
+            finishCallback: IRemoteTransitionFinishedCallback?,
+        ) {
+            delegate.mergeAnimation(transition, info, t, mergeTarget, finishCallback)
+        }
+
+        override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {
+            delegate.onTransitionConsumed(transition, aborted)
+        }
+
+        override fun takeOverAnimation(
+            token: IBinder?,
+            info: TransitionInfo?,
+            t: SurfaceControl.Transaction?,
+            finishCallback: IRemoteTransitionFinishedCallback?,
+            states: Array<WindowAnimationState>,
+        ) {
+            if (info == null || t == null) {
+                Log.e(
+                    TAG,
+                    "Skipping the animation takeover because the required data is missing: " +
+                        "info=$info, transaction=$t",
+                )
+                return
+            }
+
+            // The following code converts the contents of the given TransitionInfo into
+            // RemoteAnimationTargets. This is necessary because we must currently support both the
+            // new (Shell, remote transitions) and old (remote animations) framework to maintain
+            // functionality for all users of the library.
+            val apps = ArrayList<RemoteAnimationTarget>()
+            val filteredStates = ArrayList<WindowAnimationState>()
+            val leashMap = ArrayMap<SurfaceControl, SurfaceControl>()
+            val leafTaskFilter = TransitionUtil.LeafTaskFilter()
+
+            // About layering: we divide up the "layer space" into 2 regions (each the size of the
+            // change count). This lets us categorize things into above and below while
+            // maintaining their relative ordering.
+            val belowLayers = info.changes.size
+            val aboveLayers = info.changes.size * 2
+            for (i in info.changes.indices) {
+                val change = info.changes[i]
+                if (change == null || change.taskInfo == null) {
+                    continue
+                }
+
+                val taskInfo = change.taskInfo
+
+                if (TransitionUtil.isWallpaper(change)) {
+                    val target =
+                        TransitionUtil.newTarget(
+                            change,
+                            belowLayers - i, // wallpapers go into the "below" layer space
+                            info,
+                            t,
+                            leashMap,
+                        )
+
+                    // Make all the wallpapers opaque.
+                    t.setAlpha(target.leash, 1f)
+                } else if (leafTaskFilter.test(change)) {
+                    // Start by putting everything into the "below" layer space.
+                    val target =
+                        TransitionUtil.newTarget(change, belowLayers - i, info, t, leashMap)
+                    apps.add(target)
+                    filteredStates.add(states[i])
+
+                    // Make all the apps opaque.
+                    t.setAlpha(target.leash, 1f)
+
+                    if (
+                        TransitionUtil.isClosingType(change.mode) &&
+                            taskInfo?.topActivityType != WindowConfiguration.ACTIVITY_TYPE_HOME
+                    ) {
+                        // Raise closing task to "above" layer so it isn't covered.
+                        t.setLayer(target.leash, aboveLayers - i)
+                    }
+                } else if (TransitionInfo.isIndependent(change, info)) {
+                    // Root tasks
+                    if (TransitionUtil.isClosingType(change.mode)) {
+                        // Raise closing task to "above" layer so it isn't covered.
+                        t.setLayer(change.leash, aboveLayers - i)
+                    } else if (TransitionUtil.isOpeningType(change.mode)) {
+                        // Put into the "below" layer space.
+                        t.setLayer(change.leash, belowLayers - i)
+                    }
+                } else if (TransitionUtil.isDividerBar(change)) {
+                    val target =
+                        TransitionUtil.newTarget(change, belowLayers - i, info, t, leashMap)
+                    apps.add(target)
+                    filteredStates.add(states[i])
+                }
+            }
+
+            val wrappedCallback: IRemoteAnimationFinishedCallback =
+                object : IRemoteAnimationFinishedCallback.Stub() {
+                    override fun onAnimationFinished() {
+                        leashMap.clear()
+                        val finishTransaction = SurfaceControl.Transaction()
+                        finishCallback?.onTransitionFinished(null, finishTransaction)
+                        finishTransaction.close()
+                    }
+                }
+
+            runner.takeOverAnimation(
+                apps.toTypedArray(),
+                filteredStates.toTypedArray(),
+                t,
+                wrappedCallback,
+            )
+        }
+
+        override fun asBinder(): IBinder {
+            return delegate.asBinder()
+        }
+    }
+
     @VisibleForTesting
     inner class Runner(
-        controller: Controller,
-        callback: Callback,
+        private val controller: Controller,
+        private val callback: Callback,
         /** The animator to use to animate the window transition. */
-        transitionAnimator: TransitionAnimator,
+        private val transitionAnimator: TransitionAnimator,
         /** Listener for animation lifecycle events. */
-        listener: Listener? = null,
+        private val listener: Listener? = null,
+        /** Whether the internal [delegate] should be initialized lazily. */
+        private val initializeLazily: Boolean = false,
     ) : IRemoteAnimationRunner.Stub() {
         // This is being passed across IPC boundaries and cycles (through PendingIntentRecords,
         // etc.) are possible. So we need to make sure we drop any references that might
@@ -753,15 +925,14 @@
         @VisibleForTesting var delegate: AnimationDelegate?
 
         init {
-            delegate =
-                AnimationDelegate(
-                    mainExecutor,
-                    controller,
-                    callback,
-                    DelegatingAnimationCompletionListener(listener, this::dispose),
-                    transitionAnimator,
-                    disableWmTimeout,
-                )
+            delegate = null
+            if (!initializeLazily) {
+                // Ephemeral launches bundle the runner with the launch request (instead of being
+                // registered ahead of time for later use). This means that there could be a timeout
+                // between creation and invocation, so the delegate needs to exist from the
+                // beginning in order to handle such timeout.
+                createDelegate()
+            }
         }
 
         @BinderThread
@@ -772,6 +943,36 @@
             nonApps: Array<out RemoteAnimationTarget>?,
             finishedCallback: IRemoteAnimationFinishedCallback?,
         ) {
+            initAndRun(finishedCallback) { delegate ->
+                delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback)
+            }
+        }
+
+        @VisibleForTesting
+        @BinderThread
+        fun takeOverAnimation(
+            apps: Array<RemoteAnimationTarget>?,
+            windowAnimationStates: Array<WindowAnimationState>,
+            startTransaction: SurfaceControl.Transaction,
+            finishedCallback: IRemoteAnimationFinishedCallback?,
+        ) {
+            assertLongLivedReturnAnimations()
+            initAndRun(finishedCallback) { delegate ->
+                delegate.takeOverAnimation(
+                    apps,
+                    windowAnimationStates,
+                    startTransaction,
+                    finishedCallback,
+                )
+            }
+        }
+
+        @BinderThread
+        private fun initAndRun(
+            finishedCallback: IRemoteAnimationFinishedCallback?,
+            performAnimation: (AnimationDelegate) -> Unit,
+        ) {
+            maybeSetUp()
             val delegate = delegate
             mainExecutor.execute {
                 if (delegate == null) {
@@ -780,7 +981,7 @@
                     // signal back that we're done with it.
                     finishedCallback?.onAnimationFinished()
                 } else {
-                    delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback)
+                    performAnimation(delegate)
                 }
             }
         }
@@ -794,6 +995,32 @@
             }
         }
 
+        @VisibleForTesting
+        @UiThread
+        fun postTimeouts() {
+            maybeSetUp()
+            delegate?.postTimeouts()
+        }
+
+        @AnyThread
+        private fun maybeSetUp() {
+            if (!initializeLazily || delegate != null) return
+            createDelegate()
+        }
+
+        @AnyThread
+        private fun createDelegate() {
+            delegate =
+                AnimationDelegate(
+                    mainExecutor,
+                    controller,
+                    callback,
+                    DelegatingAnimationCompletionListener(listener, this::dispose),
+                    transitionAnimator,
+                    disableWmTimeout,
+                )
+        }
+
         @AnyThread
         fun dispose() {
             // Drop references to animation controller once we're done with the animation
@@ -867,7 +1094,7 @@
         init {
             // We do this check here to cover all entry points, including Launcher which doesn't
             // call startIntentWithAnimation()
-            if (!controller.isLaunching) TransitionAnimator.checkReturnAnimationFrameworkFlag()
+            if (!controller.isLaunching) assertReturnAnimations()
         }
 
         @UiThread
@@ -893,19 +1120,73 @@
             nonApps: Array<out RemoteAnimationTarget>?,
             callback: IRemoteAnimationFinishedCallback?,
         ) {
+            val window = setUpAnimation(apps, callback) ?: return
+
+            if (controller.windowAnimatorState == null || !longLivedReturnAnimationsEnabled()) {
+                val navigationBar =
+                    nonApps?.firstOrNull {
+                        it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
+                    }
+
+                startAnimation(window, navigationBar, iCallback = callback)
+            } else {
+                // If a [controller.windowAnimatorState] exists, treat this like a takeover.
+                takeOverAnimationInternal(
+                    window,
+                    startWindowStates = null,
+                    startTransaction = null,
+                    callback,
+                )
+            }
+        }
+
+        @UiThread
+        internal fun takeOverAnimation(
+            apps: Array<out RemoteAnimationTarget>?,
+            startWindowStates: Array<WindowAnimationState>,
+            startTransaction: SurfaceControl.Transaction,
+            callback: IRemoteAnimationFinishedCallback?,
+        ) {
+            val window = setUpAnimation(apps, callback) ?: return
+            takeOverAnimationInternal(window, startWindowStates, startTransaction, callback)
+        }
+
+        private fun takeOverAnimationInternal(
+            window: RemoteAnimationTarget,
+            startWindowStates: Array<WindowAnimationState>?,
+            startTransaction: SurfaceControl.Transaction?,
+            callback: IRemoteAnimationFinishedCallback?,
+        ) {
+            val useSpring =
+                !controller.isLaunching && startWindowStates != null && startTransaction != null
+            startAnimation(
+                window,
+                navigationBar = null,
+                useSpring,
+                startWindowStates,
+                startTransaction,
+                callback,
+            )
+        }
+
+        @UiThread
+        private fun setUpAnimation(
+            apps: Array<out RemoteAnimationTarget>?,
+            callback: IRemoteAnimationFinishedCallback?,
+        ): RemoteAnimationTarget? {
             removeTimeouts()
 
             // The animation was started too late and we already notified the controller that it
             // timed out.
             if (timedOut) {
                 callback?.invoke()
-                return
+                return null
             }
 
             // This should not happen, but let's make sure we don't start the animation if it was
             // cancelled before and we already notified the controller.
             if (cancelled) {
-                return
+                return null
             }
 
             val window = findTargetWindowIfPossible(apps)
@@ -921,15 +1202,10 @@
                 }
                 controller.onTransitionAnimationCancelled()
                 listener?.onTransitionAnimationCancelled()
-                return
+                return null
             }
 
-            val navigationBar =
-                nonApps?.firstOrNull {
-                    it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
-                }
-
-            startAnimation(window, navigationBar, callback)
+            return window
         }
 
         private fun findTargetWindowIfPossible(
@@ -950,7 +1226,7 @@
             for (it in apps) {
                 if (it.mode == targetMode) {
                     if (activityTransitionUseLargestWindow()) {
-                        if (returnAnimationFrameworkLibrary()) {
+                        if (returnAnimationsEnabled()) {
                             // If the controller contains a cookie, _only_ match if either the
                             // candidate contains the matching cookie, or a component is also
                             // defined and is a match.
@@ -995,8 +1271,11 @@
 
         private fun startAnimation(
             window: RemoteAnimationTarget,
-            navigationBar: RemoteAnimationTarget?,
-            iCallback: IRemoteAnimationFinishedCallback?,
+            navigationBar: RemoteAnimationTarget? = null,
+            useSpring: Boolean = false,
+            startingWindowStates: Array<WindowAnimationState>? = null,
+            startTransaction: SurfaceControl.Transaction? = null,
+            iCallback: IRemoteAnimationFinishedCallback? = null,
         ) {
             if (TransitionAnimator.DEBUG) {
                 Log.d(TAG, "Remote animation started")
@@ -1046,18 +1325,66 @@
             val controller =
                 object : Controller by delegate {
                     override fun createAnimatorState(): TransitionAnimator.State {
-                        if (isLaunching) return delegate.createAnimatorState()
-                        return delegate.windowAnimatorState?.toTransitionState()
-                            ?: getWindowRadius(isExpandingFullyAbove).let {
-                                TransitionAnimator.State(
-                                    top = windowBounds.top,
-                                    bottom = windowBounds.bottom,
-                                    left = windowBounds.left,
-                                    right = windowBounds.right,
-                                    topCornerRadius = it,
-                                    bottomCornerRadius = it,
-                                )
+                        if (isLaunching) {
+                            return delegate.createAnimatorState()
+                        } else if (!longLivedReturnAnimationsEnabled()) {
+                            return delegate.windowAnimatorState?.toTransitionState()
+                                ?: getWindowRadius(isExpandingFullyAbove).let {
+                                    TransitionAnimator.State(
+                                        top = windowBounds.top,
+                                        bottom = windowBounds.bottom,
+                                        left = windowBounds.left,
+                                        right = windowBounds.right,
+                                        topCornerRadius = it,
+                                        bottomCornerRadius = it,
+                                    )
+                                }
+                        }
+
+                        // The states are sorted matching the changes inside the transition info.
+                        // Using this info, the RemoteAnimationTargets are created, with their
+                        // prefixOrderIndex fields in reverse order to that of changes. To extract
+                        // the right state, we need to invert again.
+                        val windowState =
+                            if (startingWindowStates != null) {
+                                startingWindowStates[
+                                    startingWindowStates.size - window.prefixOrderIndex]
+                            } else {
+                                controller.windowAnimatorState
                             }
+
+                        // TODO(b/323863002): use the timestamp and velocity to update the initial
+                        //   position.
+                        val bounds = windowState?.bounds
+                        val left: Int = bounds?.left?.toInt() ?: windowBounds.left
+                        val top: Int = bounds?.top?.toInt() ?: windowBounds.top
+                        val right: Int = bounds?.right?.toInt() ?: windowBounds.right
+                        val bottom: Int = bounds?.bottom?.toInt() ?: windowBounds.bottom
+
+                        val width = windowBounds.right - windowBounds.left
+                        val height = windowBounds.bottom - windowBounds.top
+                        // Scale the window. We use the max of (widthRatio, heightRatio) so that
+                        // there is no blank space on any side.
+                        val widthRatio = (right - left).toFloat() / width
+                        val heightRatio = (bottom - top).toFloat() / height
+                        val startScale = maxOf(widthRatio, heightRatio)
+
+                        val maybeRadius = windowState?.topLeftRadius
+                        val windowRadius =
+                            if (maybeRadius != null) {
+                                maybeRadius * startScale
+                            } else {
+                                getWindowRadius(isExpandingFullyAbove)
+                            }
+
+                        return TransitionAnimator.State(
+                            top = top,
+                            bottom = bottom,
+                            left = left,
+                            right = right,
+                            topCornerRadius = windowRadius,
+                            bottomCornerRadius = windowRadius,
+                        )
                     }
 
                     override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -1071,6 +1398,19 @@
                                     "[controller=$delegate]",
                             )
                         }
+
+                        if (startTransaction != null) {
+                            // Calling applyStateToWindow() here avoids skipping a frame when taking
+                            // over an animation.
+                            applyStateToWindow(
+                                window,
+                                createAnimatorState(),
+                                linearProgress = 0f,
+                                useSpring,
+                                startTransaction,
+                            )
+                        }
+
                         delegate.onTransitionAnimationStart(isExpandingFullyAbove)
                     }
 
@@ -1094,14 +1434,29 @@
                         progress: Float,
                         linearProgress: Float,
                     ) {
-                        applyStateToWindow(window, state, linearProgress)
+                        applyStateToWindow(window, state, linearProgress, useSpring)
                         navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
 
                         listener?.onTransitionAnimationProgress(linearProgress)
                         delegate.onTransitionAnimationProgress(state, progress, linearProgress)
                     }
                 }
-
+            val windowState =
+                if (startingWindowStates != null) {
+                    startingWindowStates[startingWindowStates.size - window.prefixOrderIndex]
+                } else {
+                    controller.windowAnimatorState
+                }
+            val velocityPxPerS =
+                if (longLivedReturnAnimationsEnabled() && windowState?.velocityPxPerMs != null) {
+                    val xVelocityPxPerS = windowState.velocityPxPerMs.x * 1000
+                    val yVelocityPxPerS = windowState.velocityPxPerMs.y * 1000
+                    PointF(xVelocityPxPerS, yVelocityPxPerS)
+                } else if (useSpring) {
+                    PointF(0f, 0f)
+                } else {
+                    null
+                }
             animation =
                 transitionAnimator.startAnimation(
                     controller,
@@ -1109,6 +1464,7 @@
                     windowBackgroundColor,
                     fadeWindowBackgroundLayer = !controller.isBelowAnimatingWindow,
                     drawHole = !controller.isBelowAnimatingWindow,
+                    startVelocity = velocityPxPerS,
                 )
         }
 
@@ -1128,6 +1484,8 @@
             window: RemoteAnimationTarget,
             state: TransitionAnimator.State,
             linearProgress: Float,
+            useSpring: Boolean,
+            transaction: SurfaceControl.Transaction? = null,
         ) {
             if (transactionApplierView.viewRootImpl == null || !window.leash.isValid) {
                 // Don't apply any transaction if the view root we synchronize with was detached or
@@ -1171,25 +1529,47 @@
                 windowCropF.bottom.roundToInt(),
             )
 
-            val windowAnimationDelay =
+            val interpolators: TransitionAnimator.Interpolators
+            val windowProgress: Float
+
+            if (useSpring) {
+                val windowAnimationDelay: Float
+                val windowAnimationDuration: Float
                 if (controller.isLaunching) {
-                    TIMINGS.contentAfterFadeInDelay
+                    windowAnimationDelay = SPRING_TIMINGS.contentAfterFadeInDelay
+                    windowAnimationDuration = SPRING_TIMINGS.contentAfterFadeInDuration
                 } else {
-                    TIMINGS.contentBeforeFadeOutDelay
+                    windowAnimationDelay = SPRING_TIMINGS.contentBeforeFadeOutDelay
+                    windowAnimationDuration = SPRING_TIMINGS.contentBeforeFadeOutDuration
                 }
-            val windowAnimationDuration =
+
+                interpolators = SPRING_INTERPOLATORS
+                windowProgress =
+                    TransitionAnimator.getProgress(
+                        linearProgress,
+                        windowAnimationDelay,
+                        windowAnimationDuration,
+                    )
+            } else {
+                val windowAnimationDelay: Long
+                val windowAnimationDuration: Long
                 if (controller.isLaunching) {
-                    TIMINGS.contentAfterFadeInDuration
+                    windowAnimationDelay = TIMINGS.contentAfterFadeInDelay
+                    windowAnimationDuration = TIMINGS.contentAfterFadeInDuration
                 } else {
-                    TIMINGS.contentBeforeFadeOutDuration
+                    windowAnimationDelay = TIMINGS.contentBeforeFadeOutDelay
+                    windowAnimationDuration = TIMINGS.contentBeforeFadeOutDuration
                 }
-            val windowProgress =
-                TransitionAnimator.getProgress(
-                    TIMINGS,
-                    linearProgress,
-                    windowAnimationDelay,
-                    windowAnimationDuration,
-                )
+
+                interpolators = INTERPOLATORS
+                windowProgress =
+                    TransitionAnimator.getProgress(
+                        TIMINGS,
+                        linearProgress,
+                        windowAnimationDelay,
+                        windowAnimationDuration,
+                    )
+            }
 
             // The alpha of the opening window. If it opens above the expandable, then it should
             // fade in progressively. Otherwise, it should be fully opaque and will be progressively
@@ -1197,12 +1577,12 @@
             val alpha =
                 if (controller.isBelowAnimatingWindow) {
                     if (controller.isLaunching) {
-                        INTERPOLATORS.contentAfterFadeInInterpolator.getInterpolation(
+                        interpolators.contentAfterFadeInInterpolator.getInterpolation(
                             windowProgress
                         )
                     } else {
                         1 -
-                            INTERPOLATORS.contentBeforeFadeOutInterpolator.getInterpolation(
+                            interpolators.contentBeforeFadeOutInterpolator.getInterpolation(
                                 windowProgress
                             )
                     }
@@ -1216,6 +1596,7 @@
             // especially important for lock screen animations, where the window is not clipped by
             // the shade.
             val cornerRadius = maxOf(state.topCornerRadius, state.bottomCornerRadius) / scale
+
             val params =
                 SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash)
                     .withAlpha(alpha)
@@ -1223,11 +1604,15 @@
                     .withWindowCrop(windowCrop)
                     .withCornerRadius(cornerRadius)
                     .withVisibility(true)
-                    .build()
+            if (transaction != null) params.withMergeTransaction(transaction)
 
-            transactionApplier.scheduleApply(params)
+            transactionApplier.scheduleApply(params.build())
         }
 
+        // TODO(b/377643129): remote transitions have no way of identifying the navbar when
+        //  converting to RemoteAnimationTargets (and in my testing it was never included in the
+        //  transition at all). So this method is not used anymore. Remove or adapt once we fully
+        //  convert to remote transitions.
         private fun applyStateToNavigationBar(
             navigationBar: RemoteAnimationTarget,
             state: TransitionAnimator.State,
@@ -1362,9 +1747,17 @@
         }
 
         /** Register [remoteTransition] with WM Shell using the given [filter]. */
-        internal fun register(filter: TransitionFilter, remoteTransition: RemoteTransition) {
+        internal fun register(
+            filter: TransitionFilter,
+            remoteTransition: RemoteTransition,
+            includeTakeover: Boolean,
+        ) {
             shellTransitions?.registerRemote(filter, remoteTransition)
             iShellTransitions?.registerRemote(filter, remoteTransition)
+            if (includeTakeover) {
+                shellTransitions?.registerRemoteForTakeover(filter, remoteTransition)
+                iShellTransitions?.registerRemoteForTakeover(filter, remoteTransition)
+            }
         }
 
         /** Unregister [remoteTransition] from WM Shell. */
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index fdb4871..de4bdbc 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -38,6 +38,7 @@
 import com.android.internal.dynamicanimation.animation.SpringAnimation
 import com.android.internal.dynamicanimation.animation.SpringForce
 import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
+import com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived
 import java.util.concurrent.Executor
 import kotlin.math.abs
 import kotlin.math.max
@@ -113,13 +114,26 @@
             )
         }
 
-        internal fun checkReturnAnimationFrameworkFlag() {
-            check(returnAnimationFrameworkLibrary()) {
-                "isLaunching cannot be false when the returnAnimationFrameworkLibrary flag is " +
-                    "disabled"
+        internal fun assertReturnAnimations() {
+            check(returnAnimationsEnabled()) {
+                "isLaunching cannot be false when the returnAnimationFrameworkLibrary flag " +
+                    "is disabled"
             }
         }
 
+        internal fun returnAnimationsEnabled() = returnAnimationFrameworkLibrary()
+
+        internal fun assertLongLivedReturnAnimations() {
+            check(longLivedReturnAnimationsEnabled()) {
+                "Long-lived registrations cannot be used when the " +
+                    "returnAnimationFrameworkLibrary or the " +
+                    "returnAnimationFrameworkLongLived flag are disabled"
+            }
+        }
+
+        internal fun longLivedReturnAnimationsEnabled() =
+            returnAnimationFrameworkLibrary() && returnAnimationFrameworkLongLived()
+
         internal fun WindowAnimationState.toTransitionState() =
             State().also {
                 bounds?.let { b ->
@@ -467,7 +481,8 @@
         drawHole: Boolean = false,
         startVelocity: PointF? = null,
     ): Animation {
-        if (!controller.isLaunching || startVelocity != null) checkReturnAnimationFrameworkFlag()
+        if (!controller.isLaunching) assertReturnAnimations()
+        if (startVelocity != null) assertLongLivedReturnAnimations()
 
         // We add an extra layer with the same color as the dialog/app splash screen background
         // color, which is usually the same color of the app background. We first fade in this layer
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 96e99b1..778d7e7 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
@@ -67,6 +67,7 @@
                             dialogFactory = dialogFactory,
                             widgetSection = widgetSection,
                             modifier = Modifier.element(Communal.Elements.Grid),
+                            sceneScope = this@Content,
                         )
                     }
                     with(lockSection) {
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 df1185c..5b1203f 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
@@ -163,6 +163,7 @@
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.window.layout.WindowMetricsCalculator
 import com.android.compose.animation.Easings.Emphasized
+import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.thenIf
 import com.android.compose.ui.graphics.painter.rememberDrawablePainter
 import com.android.internal.R.dimen.system_app_widget_background_radius
@@ -186,7 +187,9 @@
 import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.media.controls.ui.composable.MediaCarousel
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import kotlin.math.max
 import kotlin.math.min
@@ -205,6 +208,7 @@
     widgetConfigurator: WidgetConfigurator? = null,
     onOpenWidgetPicker: (() -> Unit)? = null,
     onEditDone: (() -> Unit)? = null,
+    sceneScope: SceneScope? = null,
 ) {
     val communalContent by
         viewModel.communalContent.collectAsStateWithLifecycle(initialValue = emptyList())
@@ -414,6 +418,7 @@
                             widgetConfigurator = widgetConfigurator,
                             interactionHandler = interactionHandler,
                             widgetSection = widgetSection,
+                            sceneScope = sceneScope,
                         )
                     }
                 }
@@ -735,6 +740,7 @@
     widgetConfigurator: WidgetConfigurator?,
     interactionHandler: RemoteViews.InteractionHandler?,
     widgetSection: CommunalAppWidgetSection,
+    sceneScope: SceneScope?,
 ) {
     var gridModifier =
         Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) }
@@ -871,6 +877,7 @@
                     interactionHandler = interactionHandler,
                     widgetSection = widgetSection,
                     resizeableItemFrameViewModel = resizeableItemFrameViewModel,
+                    sceneScope = sceneScope,
                 )
             }
         }
@@ -1095,6 +1102,7 @@
     interactionHandler: RemoteViews.InteractionHandler?,
     widgetSection: CommunalAppWidgetSection,
     resizeableItemFrameViewModel: ResizeableItemFrameViewModel,
+    sceneScope: SceneScope? = null,
 ) {
     when (model) {
         is CommunalContentModel.WidgetContent.Widget ->
@@ -1118,7 +1126,7 @@
         is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier)
         is CommunalContentModel.Smartspace -> SmartspaceContent(interactionHandler, model, modifier)
         is CommunalContentModel.Tutorial -> TutorialContent(modifier)
-        is CommunalContentModel.Umo -> Umo(viewModel, modifier)
+        is CommunalContentModel.Umo -> Umo(viewModel, sceneScope, modifier)
     }
 }
 
@@ -1529,7 +1537,25 @@
 }
 
 @Composable
-private fun Umo(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier) {
+private fun Umo(
+    viewModel: BaseCommunalViewModel,
+    sceneScope: SceneScope?,
+    modifier: Modifier = Modifier,
+) {
+    if (SceneContainerFlag.isEnabled && sceneScope != null) {
+        sceneScope.MediaCarousel(
+            modifier = modifier.fillMaxSize(),
+            isVisible = true,
+            mediaHost = viewModel.mediaHost,
+            carouselController = viewModel.mediaCarouselController,
+        )
+    } else {
+        UmoLegacy(viewModel, modifier)
+    }
+}
+
+@Composable
+private fun UmoLegacy(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier) {
     AndroidView(
         modifier =
             modifier.pointerInput(Unit) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
index 71fa6c9..ce7a85b1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
@@ -19,11 +19,8 @@
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.ui.unit.IntSize
 import com.android.compose.animation.scene.TransitionBuilder
 import com.android.compose.animation.scene.UserActionDistance
-import com.android.compose.animation.scene.UserActionDistanceScope
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
 import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.qs.ui.composable.QuickSettings
@@ -31,24 +28,17 @@
 import com.android.systemui.shade.ui.composable.ShadeHeader
 import kotlin.time.Duration.Companion.milliseconds
 
-fun TransitionBuilder.goneToSplitShadeTransition(
-    durationScale: Double = 1.0,
-) {
+fun TransitionBuilder.goneToSplitShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
     swipeSpec =
         spring(
             stiffness = Spring.StiffnessMediumLow,
             visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
         )
-    distance =
-        object : UserActionDistance {
-            override fun UserActionDistanceScope.absoluteDistance(
-                fromSceneSize: IntSize,
-                orientation: Orientation,
-            ): Float {
-                return fromSceneSize.height.toFloat() * 2 / 3f
-            }
-        }
+    distance = UserActionDistance { fromContent, _, _ ->
+        val fromContentSize = checkNotNull(fromContent.targetSize())
+        fromContentSize.height.toFloat() * 2 / 3f
+    }
 
     fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
index 1486ea7..1f7a738 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
@@ -19,35 +19,25 @@
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.ui.unit.IntSize
 import com.android.compose.animation.scene.TransitionBuilder
 import com.android.compose.animation.scene.UserActionDistance
-import com.android.compose.animation.scene.UserActionDistanceScope
 import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.shade.ui.composable.Shade
 import com.android.systemui.shade.ui.composable.ShadeHeader
 import kotlin.time.Duration.Companion.milliseconds
 
-fun TransitionBuilder.lockscreenToSplitShadeTransition(
-    durationScale: Double = 1.0,
-) {
+fun TransitionBuilder.lockscreenToSplitShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
     swipeSpec =
         spring(
             stiffness = Spring.StiffnessMediumLow,
             visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
         )
-    distance =
-        object : UserActionDistance {
-            override fun UserActionDistanceScope.absoluteDistance(
-                fromSceneSize: IntSize,
-                orientation: Orientation,
-            ): Float {
-                return fromSceneSize.height.toFloat() * 2 / 3f
-            }
-        }
+    distance = UserActionDistance { fromContent, _, _ ->
+        val fromContentSize = checkNotNull(fromContent.targetSize())
+        fromContentSize.height.toFloat() * 2 / 3f
+    }
 
     fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
index 8cd357e..ba1972f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -1,46 +1,36 @@
 package com.android.systemui.scene.ui.composable.transitions
 
 import androidx.compose.animation.core.tween
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.ui.unit.IntSize
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
 import com.android.compose.animation.scene.UserActionDistance
-import com.android.compose.animation.scene.UserActionDistanceScope
 import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.ui.composable.ShadeHeader
 import kotlin.time.Duration.Companion.milliseconds
 
-fun TransitionBuilder.shadeToQuickSettingsTransition(
-    durationScale: Double = 1.0,
-) {
+fun TransitionBuilder.shadeToQuickSettingsTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
-    distance =
-        object : UserActionDistance {
-            override fun UserActionDistanceScope.absoluteDistance(
-                fromSceneSize: IntSize,
-                orientation: Orientation,
-            ): Float {
-                val distance =
-                    Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y
-                        ?: return 0f
-                return fromSceneSize.height - distance
-            }
-        }
+    distance = UserActionDistance { fromContent, _, _ ->
+        val distance =
+            Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y
+                ?: return@UserActionDistance 0f
+        val fromContentSize = checkNotNull(fromContent.targetSize())
+        fromContentSize.height - distance
+    }
 
     translate(Notifications.Elements.NotificationScrim, Edge.Bottom)
     timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) }
 
     translate(
         ShadeHeader.Elements.CollapsedContentStart,
-        y = ShadeHeader.Dimensions.CollapsedHeight
+        y = ShadeHeader.Dimensions.CollapsedHeight,
     )
     translate(ShadeHeader.Elements.CollapsedContentEnd, y = ShadeHeader.Dimensions.CollapsedHeight)
     translate(
         ShadeHeader.Elements.ExpandedContent,
-        y = -(ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight)
+        y = -(ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight),
     )
     translate(ShadeHeader.Elements.ShadeCarrierGroup, y = -ShadeHeader.Dimensions.CollapsedHeight)
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt
index de76f70..d35537a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt
@@ -28,8 +28,9 @@
 fun TransitionBuilder.toBouncerTransition() {
     spec = tween(durationMillis = 500)
 
-    distance = UserActionDistance { fromSceneSize, _ ->
-        fromSceneSize.height * TO_BOUNCER_SWIPE_DISTANCE_FRACTION
+    distance = UserActionDistance { fromContent, _, _ ->
+        val fromContentSize = checkNotNull(fromContent.targetSize())
+        fromContentSize.height * TO_BOUNCER_SWIPE_DISTANCE_FRACTION
     }
 
     translate(Bouncer.Elements.Content, y = 300.dp)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
index 55fa6ad..e78bc6a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
@@ -33,7 +33,10 @@
             stiffness = Spring.StiffnessMediumLow,
             visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
         )
-    distance = UserActionDistance { fromSceneSize, _ -> fromSceneSize.height.toFloat() * 2 / 3f }
+    distance = UserActionDistance { fromContent, _, _ ->
+        val fromContentSize = checkNotNull(fromContent.targetSize())
+        fromContentSize.height.toFloat() * 2 / 3f
+    }
 
     translate(OverlayShade.Elements.Panel, Edge.Top)
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
index b677dff..bfae489 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
@@ -19,12 +19,9 @@
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.ui.unit.IntSize
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
 import com.android.compose.animation.scene.UserActionDistance
-import com.android.compose.animation.scene.UserActionDistanceScope
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
 import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.qs.ui.composable.QuickSettings
@@ -33,24 +30,16 @@
 import com.android.systemui.shade.ui.composable.ShadeHeader
 import kotlin.time.Duration.Companion.milliseconds
 
-fun TransitionBuilder.toShadeTransition(
-    durationScale: Double = 1.0,
-) {
+fun TransitionBuilder.toShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
     swipeSpec =
         spring(
             stiffness = Spring.StiffnessMediumLow,
             visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
         )
-    distance =
-        object : UserActionDistance {
-            override fun UserActionDistanceScope.absoluteDistance(
-                fromSceneSize: IntSize,
-                orientation: Orientation,
-            ): Float {
-                return Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y ?: 0f
-            }
-        }
+    distance = UserActionDistance { _, _, _ ->
+        Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y ?: 0f
+    }
 
     fractionRange(start = .58f) {
         fade(ShadeHeader.Elements.Clock)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index dbf7d7b..d3ddb50 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -637,8 +637,8 @@
 
 fun interface UserActionDistance {
     /**
-     * Return the **absolute** distance of the user action given the size of the scene we are
-     * animating from and the [orientation].
+     * Return the **absolute** distance of the user action when going from [fromContent] to
+     * [toContent] in the given [orientation].
      *
      * Note: This function will be called for each drag event until it returns a value > 0f. This
      * for instance allows you to return 0f or a negative value until the first layout pass of a
@@ -646,7 +646,8 @@
      * transitioning to when computing this absolute distance.
      */
     fun UserActionDistanceScope.absoluteDistance(
-        fromSceneSize: IntSize,
+        fromContent: ContentKey,
+        toContent: ContentKey,
         orientation: Orientation,
     ): Float
 }
@@ -656,7 +657,8 @@
 /** The user action has a fixed [absoluteDistance]. */
 class FixedDistance(private val distance: Dp) : UserActionDistance {
     override fun UserActionDistanceScope.absoluteDistance(
-        fromSceneSize: IntSize,
+        fromContent: ContentKey,
+        toContent: ContentKey,
         orientation: Orientation,
     ): Float = distance.toPx()
 }
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 7eb5a3f..3bf2ed5 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
@@ -267,11 +267,12 @@
         private set
 
     /**
-     * The flattened list of [SharedElementTransformation] within all the transitions in
+     * The flattened list of [SharedElementTransformation.Factory] within all the transitions in
      * [transitionStates].
      */
-    private val transformationsWithElevation: List<SharedElementTransformation> by derivedStateOf {
-        transformationsWithElevation(transitionStates)
+    private val transformationFactoriesWithElevation:
+        List<SharedElementTransformation.Factory> by derivedStateOf {
+        transformationFactoriesWithElevation(transitionStates)
     }
 
     override val currentScene: SceneKey
@@ -355,6 +356,12 @@
     override suspend fun startTransition(transition: TransitionState.Transition, chain: Boolean) {
         checkThread()
 
+        // Prepare the transition before starting it. This is outside of the try/finally block on
+        // purpose because preparing a transition might throw an exception (e.g. if we find multiple
+        // specs matching this transition), in which case we want to throw that exception here
+        // before even starting the transition.
+        prepareTransitionBeforeStarting(transition)
+
         try {
             // Start the transition.
             startTransitionInternal(transition, chain)
@@ -366,7 +373,7 @@
         }
     }
 
-    private fun startTransitionInternal(transition: TransitionState.Transition, chain: Boolean) {
+    private fun prepareTransitionBeforeStarting(transition: TransitionState.Transition) {
         // Set the current scene and overlays on the transition.
         val currentState = transitionState
         transition.currentSceneWhenTransitionStarted = currentState.currentScene
@@ -394,7 +401,9 @@
         } else {
             transition.updateOverscrollSpecs(fromSpec = null, toSpec = null)
         }
+    }
 
+    private fun startTransitionInternal(transition: TransitionState.Transition, chain: Boolean) {
         when (val currentState = transitionStates.last()) {
             is TransitionState.Idle -> {
                 // Replace [Idle] by [transition].
@@ -692,22 +701,23 @@
         animate()
     }
 
-    private fun transformationsWithElevation(
+    private fun transformationFactoriesWithElevation(
         transitionStates: List<TransitionState>
-    ): List<SharedElementTransformation> {
+    ): List<SharedElementTransformation.Factory> {
         return buildList {
             transitionStates.fastForEach { state ->
                 if (state !is TransitionState.Transition) {
                     return@fastForEach
                 }
 
-                state.transformationSpec.transformations.fastForEach { transformationWithRange ->
-                    val transformation = transformationWithRange.transformation
+                state.transformationSpec.transformationMatchers.fastForEach { transformationMatcher
+                    ->
+                    val factory = transformationMatcher.factory
                     if (
-                        transformation is SharedElementTransformation &&
-                            transformation.elevateInContent != null
+                        factory is SharedElementTransformation.Factory &&
+                            factory.elevateInContent != null
                     ) {
-                        add(transformation)
+                        add(factory)
                     }
                 }
             }
@@ -722,10 +732,10 @@
      * necessary, for performance.
      */
     internal fun isElevationPossible(content: ContentKey, element: ElementKey?): Boolean {
-        if (transformationsWithElevation.isEmpty()) return false
-        return transformationsWithElevation.fastAny { transformation ->
-            transformation.elevateInContent == content &&
-                (element == null || transformation.matcher.matches(element, content))
+        if (transformationFactoriesWithElevation.isEmpty()) return false
+        return transformationFactoriesWithElevation.fastAny { factory ->
+            factory.elevateInContent == content &&
+                (element == null || factory.matcher.matches(element, content))
         }
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 569593c..8df3f2d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -26,17 +26,9 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.fastForEach
 import com.android.compose.animation.scene.content.state.TransitionState
-import com.android.compose.animation.scene.transformation.CustomAlphaTransformation
-import com.android.compose.animation.scene.transformation.CustomOffsetTransformation
-import com.android.compose.animation.scene.transformation.CustomScaleTransformation
-import com.android.compose.animation.scene.transformation.CustomSizeTransformation
-import com.android.compose.animation.scene.transformation.InterpolatedAlphaTransformation
-import com.android.compose.animation.scene.transformation.InterpolatedOffsetTransformation
-import com.android.compose.animation.scene.transformation.InterpolatedScaleTransformation
-import com.android.compose.animation.scene.transformation.InterpolatedSizeTransformation
 import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.SharedElementTransformation
-import com.android.compose.animation.scene.transformation.Transformation
+import com.android.compose.animation.scene.transformation.TransformationMatcher
 import com.android.compose.animation.scene.transformation.TransformationWithRange
 
 /** The transitions configuration of a [SceneTransitionLayout]. */
@@ -169,7 +161,7 @@
 }
 
 /** The definition of a transition between [from] and [to]. */
-interface TransitionSpec {
+internal interface TransitionSpec {
     /** The key of this [TransitionSpec]. */
     val key: TransitionKey?
 
@@ -209,7 +201,7 @@
     fun previewTransformationSpec(transition: TransitionState.Transition): TransformationSpec?
 }
 
-interface TransformationSpec {
+internal interface TransformationSpec {
     /**
      * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
      * the transition is triggered (i.e. it is not gesture-based).
@@ -232,8 +224,8 @@
      */
     val distance: UserActionDistance?
 
-    /** The list of [Transformation] applied to elements during this transition. */
-    val transformations: List<TransformationWithRange<*>>
+    /** The list of [TransformationMatcher] applied to elements during this transformation. */
+    val transformationMatchers: List<TransformationMatcher>
 
     companion object {
         internal val Empty =
@@ -241,7 +233,7 @@
                 progressSpec = snap(),
                 swipeSpec = null,
                 distance = null,
-                transformations = emptyList(),
+                transformationMatchers = emptyList(),
             )
         internal val EmptyProvider = { _: TransitionState.Transition -> Empty }
     }
@@ -272,7 +264,14 @@
                     progressSpec = reverse.progressSpec,
                     swipeSpec = reverse.swipeSpec,
                     distance = reverse.distance,
-                    transformations = reverse.transformations.map { it.reversed() },
+                    transformationMatchers =
+                        reverse.transformationMatchers.map {
+                            TransformationMatcher(
+                                matcher = it.matcher,
+                                factory = it.factory,
+                                range = it.range?.reversed(),
+                            )
+                        },
                 )
             },
         )
@@ -288,7 +287,7 @@
 }
 
 /** The definition of the overscroll behavior of the [content]. */
-interface OverscrollSpec {
+internal interface OverscrollSpec {
     /** The scene we are over scrolling. */
     val content: ContentKey
 
@@ -325,7 +324,7 @@
     override val progressSpec: AnimationSpec<Float>,
     override val swipeSpec: SpringSpec<Float>?,
     override val distance: UserActionDistance?,
-    override val transformations: List<TransformationWithRange<*>>,
+    override val transformationMatchers: List<TransformationMatcher>,
 ) : TransformationSpec {
     private val cache = mutableMapOf<ElementKey, MutableMap<ContentKey, ElementTransformations>>()
 
@@ -335,7 +334,7 @@
             .getOrPut(content) { computeTransformations(element, content) }
     }
 
-    /** Filter [transformations] to compute the [ElementTransformations] of [element]. */
+    /** Filter [transformationMatchers] to compute the [ElementTransformations] of [element]. */
     private fun computeTransformations(
         element: ElementKey,
         content: ContentKey,
@@ -346,46 +345,55 @@
         var drawScale: TransformationWithRange<PropertyTransformation<Scale>>? = null
         var alpha: TransformationWithRange<PropertyTransformation<Float>>? = null
 
-        transformations.fastForEach { transformationWithRange ->
-            val transformation = transformationWithRange.transformation
-            if (!transformation.matcher.matches(element, content)) {
+        transformationMatchers.fastForEach { transformationMatcher ->
+            if (!transformationMatcher.matcher.matches(element, content)) {
                 return@fastForEach
             }
 
-            when (transformation) {
-                is SharedElementTransformation -> {
-                    throwIfNotNull(shared, element, name = "shared")
-                    shared =
-                        transformationWithRange
-                            as TransformationWithRange<SharedElementTransformation>
+            val transformation = transformationMatcher.factory.create()
+            val property =
+                when (transformation) {
+                    is SharedElementTransformation -> {
+                        throwIfNotNull(shared, element, name = "shared")
+                        shared =
+                            TransformationWithRange(transformation, transformationMatcher.range)
+                        return@fastForEach
+                    }
+                    is PropertyTransformation<*> -> transformation.property
                 }
-                is InterpolatedOffsetTransformation,
-                is CustomOffsetTransformation -> {
+
+            when (property) {
+                is PropertyTransformation.Property.Offset -> {
                     throwIfNotNull(offset, element, name = "offset")
                     offset =
-                        transformationWithRange
-                            as TransformationWithRange<PropertyTransformation<Offset>>
+                        TransformationWithRange(
+                            transformation as PropertyTransformation<Offset>,
+                            transformationMatcher.range,
+                        )
                 }
-                is InterpolatedSizeTransformation,
-                is CustomSizeTransformation -> {
+                is PropertyTransformation.Property.Size -> {
                     throwIfNotNull(size, element, name = "size")
                     size =
-                        transformationWithRange
-                            as TransformationWithRange<PropertyTransformation<IntSize>>
+                        TransformationWithRange(
+                            transformation as PropertyTransformation<IntSize>,
+                            transformationMatcher.range,
+                        )
                 }
-                is InterpolatedScaleTransformation,
-                is CustomScaleTransformation -> {
+                is PropertyTransformation.Property.Scale -> {
                     throwIfNotNull(drawScale, element, name = "drawScale")
                     drawScale =
-                        transformationWithRange
-                            as TransformationWithRange<PropertyTransformation<Scale>>
+                        TransformationWithRange(
+                            transformation as PropertyTransformation<Scale>,
+                            transformationMatcher.range,
+                        )
                 }
-                is InterpolatedAlphaTransformation,
-                is CustomAlphaTransformation -> {
+                is PropertyTransformation.Property.Alpha -> {
                     throwIfNotNull(alpha, element, name = "alpha")
                     alpha =
-                        transformationWithRange
-                            as TransformationWithRange<PropertyTransformation<Float>>
+                        TransformationWithRange(
+                            transformation as PropertyTransformation<Float>,
+                            transformationMatcher.range,
+                        )
                 }
             }
         }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index f0043e1..dbfeb5c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -24,7 +24,6 @@
 import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.unit.IntSize
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
 import kotlin.math.absoluteValue
@@ -66,8 +65,9 @@
         val absoluteDistance =
             with(animation.contentTransition.transformationSpec.distance ?: DefaultSwipeDistance) {
                 layoutImpl.userActionDistanceScope.absoluteDistance(
-                    layoutImpl.content(animation.fromContent).targetSize,
-                    orientation,
+                    fromContent = animation.fromContent,
+                    toContent = animation.toContent,
+                    orientation = orientation,
                 )
             }
 
@@ -475,12 +475,14 @@
 
 private object DefaultSwipeDistance : UserActionDistance {
     override fun UserActionDistanceScope.absoluteDistance(
-        fromSceneSize: IntSize,
+        fromContent: ContentKey,
+        toContent: ContentKey,
         orientation: Orientation,
     ): Float {
+        val fromContentSize = checkNotNull(fromContent.targetSize())
         return when (orientation) {
-            Orientation.Horizontal -> fromSceneSize.width
-            Orientation.Vertical -> fromSceneSize.height
+            Orientation.Horizontal -> fromContentSize.width
+            Orientation.Vertical -> fromContentSize.height
         }.toFloat()
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 1fdfca9..48f08a7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -26,7 +26,7 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.content.state.TransitionState
-import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
+import com.android.compose.animation.scene.transformation.Transformation
 import kotlin.math.tanh
 
 /** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */
@@ -76,7 +76,7 @@
         preview: (TransitionBuilder.() -> Unit)? = null,
         reversePreview: (TransitionBuilder.() -> Unit)? = null,
         builder: TransitionBuilder.() -> Unit = {},
-    ): TransitionSpec
+    )
 
     /**
      * Define the animation to be played when transitioning [from] the specified content. For the
@@ -102,7 +102,7 @@
         preview: (TransitionBuilder.() -> Unit)? = null,
         reversePreview: (TransitionBuilder.() -> Unit)? = null,
         builder: TransitionBuilder.() -> Unit = {},
-    ): TransitionSpec
+    )
 
     /**
      * Define the animation to be played when the [content] is overscrolled in the given
@@ -115,13 +115,13 @@
         content: ContentKey,
         orientation: Orientation,
         builder: OverscrollBuilder.() -> Unit,
-    ): OverscrollSpec
+    )
 
     /**
      * Prevents overscroll the [content] in the given [orientation], allowing ancestors to
      * eventually consume the remaining gesture.
      */
-    fun overscrollDisabled(content: ContentKey, orientation: Orientation): OverscrollSpec
+    fun overscrollDisabled(content: ContentKey, orientation: Orientation)
 }
 
 interface BaseTransitionBuilder : PropertyTransformationBuilder {
@@ -529,15 +529,8 @@
         anchorHeight: Boolean = true,
     )
 
-    /**
-     * Apply a [CustomPropertyTransformation] to one or more elements.
-     *
-     * @see com.android.compose.animation.scene.transformation.CustomSizeTransformation
-     * @see com.android.compose.animation.scene.transformation.CustomOffsetTransformation
-     * @see com.android.compose.animation.scene.transformation.CustomAlphaTransformation
-     * @see com.android.compose.animation.scene.transformation.CustomScaleTransformation
-     */
-    fun transformation(transformation: CustomPropertyTransformation<*>)
+    /** Apply a [transformation] to the element(s) matching [matcher]. */
+    fun transformation(matcher: ElementMatcher, transformation: Transformation.Factory)
 }
 
 /** This converter lets you change a linear progress into a function of your choice. */
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 79f8cd4..6742b32 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -30,7 +30,6 @@
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.transformation.AnchoredSize
 import com.android.compose.animation.scene.transformation.AnchoredTranslate
-import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
 import com.android.compose.animation.scene.transformation.DrawScale
 import com.android.compose.animation.scene.transformation.EdgeTranslate
 import com.android.compose.animation.scene.transformation.Fade
@@ -38,8 +37,8 @@
 import com.android.compose.animation.scene.transformation.ScaleSize
 import com.android.compose.animation.scene.transformation.SharedElementTransformation
 import com.android.compose.animation.scene.transformation.Transformation
+import com.android.compose.animation.scene.transformation.TransformationMatcher
 import com.android.compose.animation.scene.transformation.TransformationRange
-import com.android.compose.animation.scene.transformation.TransformationWithRange
 import com.android.compose.animation.scene.transformation.Translate
 
 internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
@@ -67,8 +66,8 @@
         preview: (TransitionBuilder.() -> Unit)?,
         reversePreview: (TransitionBuilder.() -> Unit)?,
         builder: TransitionBuilder.() -> Unit,
-    ): TransitionSpec {
-        return transition(from = null, to = to, key = key, preview, reversePreview, builder)
+    ) {
+        transition(from = null, to = to, key = key, preview, reversePreview, builder)
     }
 
     override fun from(
@@ -78,25 +77,25 @@
         preview: (TransitionBuilder.() -> Unit)?,
         reversePreview: (TransitionBuilder.() -> Unit)?,
         builder: TransitionBuilder.() -> Unit,
-    ): TransitionSpec {
-        return transition(from = from, to = to, key = key, preview, reversePreview, builder)
+    ) {
+        transition(from = from, to = to, key = key, preview, reversePreview, builder)
     }
 
     override fun overscroll(
         content: ContentKey,
         orientation: Orientation,
         builder: OverscrollBuilder.() -> Unit,
-    ): OverscrollSpec {
+    ) {
         val impl = OverscrollBuilderImpl().apply(builder)
-        check(impl.transformations.isNotEmpty()) {
+        check(impl.transformationMatchers.isNotEmpty()) {
             "This method does not allow empty transformations. " +
                 "Use overscrollDisabled($content, $orientation) instead."
         }
-        return overscrollSpec(content, orientation, impl)
+        overscrollSpec(content, orientation, impl)
     }
 
-    override fun overscrollDisabled(content: ContentKey, orientation: Orientation): OverscrollSpec {
-        return overscrollSpec(content, orientation, OverscrollBuilderImpl())
+    override fun overscrollDisabled(content: ContentKey, orientation: Orientation) {
+        overscrollSpec(content, orientation, OverscrollBuilderImpl())
     }
 
     private fun overscrollSpec(
@@ -113,7 +112,7 @@
                         progressSpec = snap(),
                         swipeSpec = null,
                         distance = impl.distance,
-                        transformations = impl.transformations,
+                        transformationMatchers = impl.transformationMatchers,
                     ),
                 progressConverter = impl.progressConverter,
             )
@@ -138,7 +137,7 @@
                 progressSpec = impl.spec,
                 swipeSpec = impl.swipeSpec,
                 distance = impl.distance,
-                transformations = impl.transformations,
+                transformationMatchers = impl.transformationMatchers,
             )
         }
 
@@ -158,7 +157,7 @@
 }
 
 internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
-    val transformations = mutableListOf<TransformationWithRange<*>>()
+    val transformationMatchers = mutableListOf<TransformationMatcher>()
     private var range: TransformationRange? = null
     protected var reversed = false
     override var distance: UserActionDistance? = null
@@ -174,23 +173,31 @@
         range = null
     }
 
-    protected fun addTransformation(transformation: Transformation) {
-        val transformationWithRange = TransformationWithRange(transformation, range)
-        transformations.add(
-            if (reversed) {
-                transformationWithRange.reversed()
-            } else {
-                transformationWithRange
-            }
+    protected fun addTransformation(
+        matcher: ElementMatcher,
+        transformation: Transformation.Factory,
+    ) {
+        transformationMatchers.add(
+            TransformationMatcher(
+                matcher,
+                transformation,
+                range?.let { range ->
+                    if (reversed) {
+                        range.reversed()
+                    } else {
+                        range
+                    }
+                },
+            )
         )
     }
 
     override fun fade(matcher: ElementMatcher) {
-        addTransformation(Fade(matcher))
+        addTransformation(matcher, Fade.Factory)
     }
 
     override fun translate(matcher: ElementMatcher, x: Dp, y: Dp) {
-        addTransformation(Translate(matcher, x, y))
+        addTransformation(matcher, Translate.Factory(x, y))
     }
 
     override fun translate(
@@ -198,19 +205,19 @@
         edge: Edge,
         startsOutsideLayoutBounds: Boolean,
     ) {
-        addTransformation(EdgeTranslate(matcher, edge, startsOutsideLayoutBounds))
+        addTransformation(matcher, EdgeTranslate.Factory(edge, startsOutsideLayoutBounds))
     }
 
     override fun anchoredTranslate(matcher: ElementMatcher, anchor: ElementKey) {
-        addTransformation(AnchoredTranslate(matcher, anchor))
+        addTransformation(matcher, AnchoredTranslate.Factory(anchor))
     }
 
     override fun scaleSize(matcher: ElementMatcher, width: Float, height: Float) {
-        addTransformation(ScaleSize(matcher, width, height))
+        addTransformation(matcher, ScaleSize.Factory(width, height))
     }
 
     override fun scaleDraw(matcher: ElementMatcher, scaleX: Float, scaleY: Float, pivot: Offset) {
-        addTransformation(DrawScale(matcher, scaleX, scaleY, pivot))
+        addTransformation(matcher, DrawScale.Factory(scaleX, scaleY, pivot))
     }
 
     override fun anchoredSize(
@@ -219,12 +226,12 @@
         anchorWidth: Boolean,
         anchorHeight: Boolean,
     ) {
-        addTransformation(AnchoredSize(matcher, anchor, anchorWidth, anchorHeight))
+        addTransformation(matcher, AnchoredSize.Factory(anchor, anchorWidth, anchorHeight))
     }
 
-    override fun transformation(transformation: CustomPropertyTransformation<*>) {
+    override fun transformation(matcher: ElementMatcher, transformation: Transformation.Factory) {
         check(range == null) { "Custom transformations can not be applied inside a range" }
-        addTransformation(transformation)
+        addTransformation(matcher, transformation)
     }
 }
 
@@ -263,7 +270,10 @@
                 "(${transition.toContent.debugName})"
         }
 
-        addTransformation(SharedElementTransformation(matcher, enabled, elevateInContent))
+        addTransformation(
+            matcher,
+            SharedElementTransformation.Factory(matcher, enabled, elevateInContent),
+        )
     }
 
     override fun timestampRange(
@@ -294,6 +304,6 @@
         x: OverscrollScope.() -> Float,
         y: OverscrollScope.() -> Float,
     ) {
-        addTransformation(OverscrollTranslate(matcher, x, y))
+        addTransformation(matcher, OverscrollTranslate.Factory(x, y))
     }
 }
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 38b1aaa..33f015f 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
@@ -401,7 +401,7 @@
                     else -> null
                 } ?: return true
 
-            return specForCurrentScene.transformationSpec.transformations.isNotEmpty()
+            return specForCurrentScene.transformationSpec.transformationMatchers.isNotEmpty()
         }
 
         internal open fun interruptionProgress(layoutImpl: SceneTransitionLayoutImpl): Float {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
new file mode 100644
index 0000000..bfb5ca7
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
@@ -0,0 +1,289 @@
+/*
+ * 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.reveal
+
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.DeferredTargetAnimation
+import androidx.compose.animation.core.ExperimentalAnimatableApi
+import androidx.compose.animation.core.FiniteAnimationSpec
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.spring
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastCoerceAtLeast
+import androidx.compose.ui.util.fastCoerceAtMost
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.OverlayKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
+import com.android.compose.animation.scene.transformation.PropertyTransformation
+import com.android.compose.animation.scene.transformation.PropertyTransformationScope
+import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineScope
+
+interface ContainerRevealHaptics {
+    /**
+     * Called when the reveal threshold is crossed while the user was dragging on screen.
+     *
+     * Important: This callback is called during layout and its implementation should therefore be
+     * very fast or posted to a different thread.
+     *
+     * @param revealed whether we go from hidden to revealed, i.e. whether the container size is
+     *   going to jump from a smaller size to a bigger size.
+     */
+    fun onRevealThresholdCrossed(revealed: Boolean)
+}
+
+/** Animate the reveal of [container] by animating its size. */
+fun TransitionBuilder.verticalContainerReveal(
+    container: ElementKey,
+    haptics: ContainerRevealHaptics,
+) {
+    // Make the swipe distance be exactly the target height of the container.
+    // TODO(b/376438969): Make sure that this works correctly when the target size of the element
+    // is changing during the transition (e.g. a notification was added). At the moment, the user
+    // action distance is only called until it returns a value > 0f, which is then cached.
+    distance = UserActionDistance { fromContent, toContent, _ ->
+        val targetSizeInFromContent = container.targetSize(fromContent)
+        val targetSizeInToContent = container.targetSize(toContent)
+        if (targetSizeInFromContent != null && targetSizeInToContent != null) {
+            error(
+                "verticalContainerReveal should not be used with shared elements, but " +
+                    "${container.debugName} is in both ${fromContent.debugName} and " +
+                    toContent.debugName
+            )
+        }
+
+        (targetSizeInToContent?.height ?: targetSizeInFromContent?.height)?.toFloat() ?: 0f
+    }
+
+    // TODO(b/376438969): Improve the motion of this gesture using Motion Mechanics.
+
+    // The min distance to swipe before triggering the reveal spring.
+    val distanceThreshold = 80.dp
+
+    // The minimum height of the container.
+    val minHeight = 10.dp
+
+    // The amount removed from the container width at 0% progress.
+    val widthDelta = 140.dp
+
+    // The ratio at which the distance is tracked before reaching the threshold, e.g. if the user
+    // drags 60dp then the height will be 60dp * 0.25f = 15dp.
+    val trackingRatio = 0.25f
+
+    // The max progress starting from which the container should always be visible, even if we are
+    // animating the container out. This is used so that we don't immediately fade out the container
+    // when triggering a one-off animation that hides it.
+    val alphaProgressThreshold = 0.05f
+
+    // The spring animating the size of the container.
+    val sizeSpec = spring<Float>(stiffness = 380f, dampingRatio = 0.9f)
+
+    // The spring animating the alpha of the container.
+    val alphaSpec = spring<Float>(stiffness = 1200f, dampingRatio = 0.99f)
+
+    // The spring animating the progress when releasing the finger.
+    swipeSpec =
+        spring(
+            stiffness = Spring.StiffnessMediumLow,
+            dampingRatio = Spring.DampingRatioNoBouncy,
+            visibilityThreshold = 0.5f,
+        )
+
+    // Size transformation.
+    transformation(container) {
+        VerticalContainerRevealSizeTransformation(
+            haptics,
+            distanceThreshold,
+            trackingRatio,
+            minHeight,
+            widthDelta,
+            sizeSpec,
+        )
+    }
+
+    // Alpha transformation.
+    transformation(container) {
+        ContainerRevealAlphaTransformation(alphaSpec, alphaProgressThreshold)
+    }
+}
+
+@OptIn(ExperimentalAnimatableApi::class)
+private class VerticalContainerRevealSizeTransformation(
+    private val haptics: ContainerRevealHaptics,
+    private val distanceThreshold: Dp,
+    private val trackingRatio: Float,
+    private val minHeight: Dp,
+    private val widthDelta: Dp,
+    private val spec: FiniteAnimationSpec<Float>,
+) : CustomPropertyTransformation<IntSize> {
+    override val property = PropertyTransformation.Property.Size
+
+    private val widthAnimation = DeferredTargetAnimation(Float.VectorConverter)
+    private val heightAnimation = DeferredTargetAnimation(Float.VectorConverter)
+
+    private var previousHasReachedThreshold: Boolean? = null
+
+    override fun PropertyTransformationScope.transform(
+        content: ContentKey,
+        element: ElementKey,
+        transition: TransitionState.Transition,
+        transitionScope: CoroutineScope,
+    ): IntSize {
+        // The distance to go to 100%. Note that we don't use
+        // TransitionState.HasOverscrollProperties.absoluteDistance because the transition will not
+        // implement HasOverscrollProperties if the transition is triggered and not gesture based.
+        val idleSize = checkNotNull(element.targetSize(content))
+        val userActionDistance = idleSize.height
+        val progress =
+            when ((transition as? TransitionState.HasOverscrollProperties)?.bouncingContent) {
+                null -> transition.progressTo(content)
+                content -> 1f
+                else -> 0f
+            }
+        val distance = (progress * userActionDistance).fastCoerceAtLeast(0f)
+        val threshold = distanceThreshold.toPx()
+
+        // Width.
+        val widthDelta = widthDelta.toPx()
+        val width =
+            (idleSize.width - widthDelta +
+                    animateSize(
+                        size = widthDelta,
+                        distance = distance,
+                        threshold = threshold,
+                        transitionScope = transitionScope,
+                        animation = widthAnimation,
+                    ))
+                .roundToInt()
+
+        // Height.
+        val minHeight = minHeight.toPx()
+        val height =
+            (
+                // 1) The minimum size of the container.
+                minHeight +
+
+                    // 2) The animated size between the minimum size and the threshold.
+                    animateSize(
+                        size = threshold - minHeight,
+                        distance = distance,
+                        threshold = threshold,
+                        transitionScope = transitionScope,
+                        animation = heightAnimation,
+                    ) +
+
+                    // 3) The remaining height after the threshold, tracking the finger.
+                    (distance - threshold).fastCoerceAtLeast(0f))
+                .roundToInt()
+                .fastCoerceAtMost(idleSize.height)
+
+        // Haptics.
+        val hasReachedThreshold = distance >= threshold
+        if (
+            previousHasReachedThreshold != null &&
+                hasReachedThreshold != previousHasReachedThreshold &&
+                transition.isUserInputOngoing
+        ) {
+            haptics.onRevealThresholdCrossed(revealed = hasReachedThreshold)
+        }
+        previousHasReachedThreshold = hasReachedThreshold
+
+        return IntSize(width = width, height = height)
+    }
+
+    /**
+     * Animate a size up to [size], so that it is equal to 0f when distance is 0f and equal to
+     * [size] when `distance >= threshold`, taking the [trackingRatio] into account.
+     */
+    @OptIn(ExperimentalAnimatableApi::class)
+    private fun animateSize(
+        size: Float,
+        distance: Float,
+        threshold: Float,
+        transitionScope: CoroutineScope,
+        animation: DeferredTargetAnimation<Float, AnimationVector1D>,
+    ): Float {
+        val trackingSize = distance.fastCoerceAtMost(threshold) / threshold * size * trackingRatio
+        val springTarget =
+            if (distance >= threshold) {
+                size * (1f - trackingRatio)
+            } else {
+                0f
+            }
+        val springSize = animation.updateTarget(springTarget, transitionScope, spec)
+        return trackingSize + springSize
+    }
+}
+
+@OptIn(ExperimentalAnimatableApi::class)
+private class ContainerRevealAlphaTransformation(
+    private val spec: FiniteAnimationSpec<Float>,
+    private val progressThreshold: Float,
+) : CustomPropertyTransformation<Float> {
+    override val property = PropertyTransformation.Property.Alpha
+    private val alphaAnimation = DeferredTargetAnimation(Float.VectorConverter)
+
+    override fun PropertyTransformationScope.transform(
+        content: ContentKey,
+        element: ElementKey,
+        transition: TransitionState.Transition,
+        transitionScope: CoroutineScope,
+    ): Float {
+        return alphaAnimation.updateTarget(targetAlpha(transition, content), transitionScope, spec)
+    }
+
+    private fun targetAlpha(transition: TransitionState.Transition, content: ContentKey): Float {
+        if (transition.isUserInputOngoing) {
+            if (transition !is TransitionState.HasOverscrollProperties) {
+                error(
+                    "Unsupported transition driven by user input but that does not have " +
+                        "overscroll properties: $transition"
+                )
+            }
+
+            val bouncingContent = transition.bouncingContent
+            return if (bouncingContent != null) {
+                if (bouncingContent == content) 1f else 0f
+            } else {
+                if (transition.progressTo(content) > 0f) 1f else 0f
+            }
+        }
+
+        // The transition was committed (the user released their finger), so the alpha depends on
+        // whether we are animating towards the content (showing the container) or away from it
+        // (hiding the container).
+        val isShowingContainer =
+            when (content) {
+                is SceneKey -> transition.currentScene == content
+                is OverlayKey -> transition.currentOverlays.contains(content)
+            }
+
+        return if (isShowingContainer || transition.progressTo(content) >= progressThreshold) {
+            1f
+        } else {
+            0f
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
index 85bb533..6575068 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
@@ -19,16 +19,17 @@
 import androidx.compose.ui.unit.IntSize
 import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.content.state.TransitionState
 
 /** Anchor the size of an element to the size of another element. */
-internal class AnchoredSize(
-    override val matcher: ElementMatcher,
+internal class AnchoredSize
+private constructor(
     private val anchor: ElementKey,
     private val anchorWidth: Boolean,
     private val anchorHeight: Boolean,
-) : InterpolatedSizeTransformation {
+) : InterpolatedPropertyTransformation<IntSize> {
+    override val property = PropertyTransformation.Property.Size
+
     override fun PropertyTransformationScope.transform(
         content: ContentKey,
         element: ElementKey,
@@ -59,4 +60,12 @@
             anchorSizeIn(transition.fromContent)
         }
     }
+
+    class Factory(
+        private val anchor: ElementKey,
+        private val anchorWidth: Boolean,
+        private val anchorHeight: Boolean,
+    ) : Transformation.Factory {
+        override fun create(): Transformation = AnchoredSize(anchor, anchorWidth, anchorHeight)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
index 04cd683..890902b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
@@ -19,14 +19,13 @@
 import androidx.compose.ui.geometry.Offset
 import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.content.state.TransitionState
 
 /** Anchor the translation of an element to another element. */
-internal class AnchoredTranslate(
-    override val matcher: ElementMatcher,
-    private val anchor: ElementKey,
-) : InterpolatedOffsetTransformation {
+internal class AnchoredTranslate private constructor(private val anchor: ElementKey) :
+    InterpolatedPropertyTransformation<Offset> {
+    override val property = PropertyTransformation.Property.Offset
+
     override fun PropertyTransformationScope.transform(
         content: ContentKey,
         element: ElementKey,
@@ -56,6 +55,10 @@
             Offset(idleValue.x + offset.x, idleValue.y + offset.y)
         }
     }
+
+    class Factory(private val anchor: ElementKey) : Transformation.Factory {
+        override fun create(): Transformation = AnchoredTranslate(anchor)
+    }
 }
 
 internal fun throwMissingAnchorException(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
index 45d6d40..347f1c3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
@@ -19,7 +19,6 @@
 import androidx.compose.ui.geometry.Offset
 import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.Scale
 import com.android.compose.animation.scene.content.state.TransitionState
 
@@ -27,12 +26,14 @@
  * Scales the draw size of an element. Note this will only scale the draw inside of an element,
  * therefore it won't impact layout of elements around it.
  */
-internal class DrawScale(
-    override val matcher: ElementMatcher,
+internal class DrawScale
+private constructor(
     private val scaleX: Float,
     private val scaleY: Float,
-    private val pivot: Offset = Offset.Unspecified,
-) : InterpolatedScaleTransformation {
+    private val pivot: Offset,
+) : InterpolatedPropertyTransformation<Scale> {
+    override val property = PropertyTransformation.Property.Scale
+
     override fun PropertyTransformationScope.transform(
         content: ContentKey,
         element: ElementKey,
@@ -41,4 +42,9 @@
     ): Scale {
         return Scale(scaleX, scaleY, pivot)
     }
+
+    class Factory(private val scaleX: Float, private val scaleY: Float, private val pivot: Offset) :
+        Transformation.Factory {
+        override fun create(): Transformation = DrawScale(scaleX, scaleY, pivot)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
index 21d66d7..f8e6dc0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -20,15 +20,14 @@
 import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.content.state.TransitionState
 
 /** Translate an element from an edge of the layout. */
-internal class EdgeTranslate(
-    override val matcher: ElementMatcher,
-    private val edge: Edge,
-    private val startsOutsideLayoutBounds: Boolean = true,
-) : InterpolatedOffsetTransformation {
+internal class EdgeTranslate
+private constructor(private val edge: Edge, private val startsOutsideLayoutBounds: Boolean) :
+    InterpolatedPropertyTransformation<Offset> {
+    override val property = PropertyTransformation.Property.Offset
+
     override fun PropertyTransformationScope.transform(
         content: ContentKey,
         element: ElementKey,
@@ -67,4 +66,9 @@
                 }
         }
     }
+
+    class Factory(private val edge: Edge, private val startsOutsideLayoutBounds: Boolean) :
+        Transformation.Factory {
+        override fun create(): Transformation = EdgeTranslate(edge, startsOutsideLayoutBounds)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
index d942273..d92419e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
@@ -18,11 +18,12 @@
 
 import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.content.state.TransitionState
 
 /** Fade an element in or out. */
-internal class Fade(override val matcher: ElementMatcher) : InterpolatedAlphaTransformation {
+internal object Fade : InterpolatedPropertyTransformation<Float> {
+    override val property = PropertyTransformation.Property.Alpha
+
     override fun PropertyTransformationScope.transform(
         content: ContentKey,
         element: ElementKey,
@@ -33,4 +34,8 @@
         // fading out, which is `0` in both cases.
         return 0f
     }
+
+    object Factory : Transformation.Factory {
+        override fun create(): Transformation = Fade
+    }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
index 5f3cdab..5d31fd9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
@@ -19,7 +19,6 @@
 import androidx.compose.ui.unit.IntSize
 import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.content.state.TransitionState
 import kotlin.math.roundToInt
 
@@ -27,11 +26,10 @@
  * Scales the size of an element. Note that this makes the element resize every frame and will
  * therefore impact the layout of other elements.
  */
-internal class ScaleSize(
-    override val matcher: ElementMatcher,
-    private val width: Float = 1f,
-    private val height: Float = 1f,
-) : InterpolatedSizeTransformation {
+internal class ScaleSize private constructor(private val width: Float, private val height: Float) :
+    InterpolatedPropertyTransformation<IntSize> {
+    override val property = PropertyTransformation.Property.Size
+
     override fun PropertyTransformationScope.transform(
         content: ContentKey,
         element: ElementKey,
@@ -43,4 +41,9 @@
             height = (idleValue.height * height).roundToInt(),
         )
     }
+
+    class Factory(private val width: Float = 1f, private val height: Float = 1f) :
+        Transformation.Factory {
+        override fun create(): Transformation = ScaleSize(width, height)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index d5143d7..e0b4218 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -35,26 +35,59 @@
 
 /** A transformation applied to one or more elements during a transition. */
 sealed interface Transformation {
-    /**
-     * The matcher that should match the element(s) to which this transformation should be applied.
-     */
-    val matcher: ElementMatcher
+    fun interface Factory {
+        fun create(): Transformation
+    }
 }
 
-internal class SharedElementTransformation(
-    override val matcher: ElementMatcher,
+// Important: SharedElementTransformation must be a data class because we check that we don't
+// provide 2 different transformations for the same element in Element.kt
+internal data class SharedElementTransformation(
     internal val enabled: Boolean,
     internal val elevateInContent: ContentKey?,
-) : Transformation
+) : Transformation {
+    class Factory(
+        internal val matcher: ElementMatcher,
+        internal val enabled: Boolean,
+        internal val elevateInContent: ContentKey?,
+    ) : Transformation.Factory {
+        override fun create(): Transformation {
+            return SharedElementTransformation(enabled, elevateInContent)
+        }
+    }
+}
 
-/** A transformation that changes the value of an element property, like its size or offset. */
-sealed interface PropertyTransformation<T> : Transformation
+/**
+ * A transformation that changes the value of an element [Property], like its [size][Property.Size]
+ * or [offset][Property.Offset].
+ */
+sealed interface PropertyTransformation<T> : Transformation {
+    /** The property to which this transformation is applied. */
+    val property: Property<T>
+
+    sealed class Property<T> {
+        /** The size of an element. */
+        data object Size : Property<IntSize>()
+
+        /** The offset (position) of an element. */
+        data object Offset : Property<androidx.compose.ui.geometry.Offset>()
+
+        /** The alpha of an element. */
+        data object Alpha : Property<Float>()
+
+        /**
+         * The drawing scale of an element. Animating the scale does not have any effect on the
+         * layout.
+         */
+        data object Scale : Property<com.android.compose.animation.scene.Scale>()
+    }
+}
 
 /**
  * A transformation to a target/transformed value that is automatically interpolated using the
  * transition progress and transformation range.
  */
-sealed interface InterpolatedPropertyTransformation<T> : PropertyTransformation<T> {
+interface InterpolatedPropertyTransformation<T> : PropertyTransformation<T> {
     /**
      * Return the transformed value for the given property, i.e.:
      * - the value at progress = 0% for elements that are entering the layout (i.e. elements in the
@@ -73,19 +106,7 @@
     ): T
 }
 
-/** An [InterpolatedPropertyTransformation] applied to the size of one or more elements. */
-interface InterpolatedSizeTransformation : InterpolatedPropertyTransformation<IntSize>
-
-/** An [InterpolatedPropertyTransformation] applied to the offset of one or more elements. */
-interface InterpolatedOffsetTransformation : InterpolatedPropertyTransformation<Offset>
-
-/** An [InterpolatedPropertyTransformation] applied to the alpha of one or more elements. */
-interface InterpolatedAlphaTransformation : InterpolatedPropertyTransformation<Float>
-
-/** An [InterpolatedPropertyTransformation] applied to the scale of one or more elements. */
-interface InterpolatedScaleTransformation : InterpolatedPropertyTransformation<Scale>
-
-sealed interface CustomPropertyTransformation<T> : PropertyTransformation<T> {
+interface CustomPropertyTransformation<T> : PropertyTransformation<T> {
     /**
      * Return the value that the property should have in the current frame for the given [content]
      * and [element].
@@ -105,25 +126,20 @@
     ): T
 }
 
-/** A [CustomPropertyTransformation] applied to the size of one or more elements. */
-interface CustomSizeTransformation : CustomPropertyTransformation<IntSize>
-
-/** A [CustomPropertyTransformation] applied to the offset of one or more elements. */
-interface CustomOffsetTransformation : CustomPropertyTransformation<Offset>
-
-/** A [CustomPropertyTransformation] applied to the alpha of one or more elements. */
-interface CustomAlphaTransformation : CustomPropertyTransformation<Float>
-
-/** A [CustomPropertyTransformation] applied to the scale of one or more elements. */
-interface CustomScaleTransformation : CustomPropertyTransformation<Scale>
-
 interface PropertyTransformationScope : Density, ElementStateScope {
     /** The current [direction][LayoutDirection] of the layout. */
     val layoutDirection: LayoutDirection
 }
 
+/** Defines the transformation-type to be applied to all elements matching [matcher]. */
+internal class TransformationMatcher(
+    val matcher: ElementMatcher,
+    val factory: Transformation.Factory,
+    val range: TransformationRange?,
+)
+
 /** A pair consisting of a [transformation] and optional [range]. */
-class TransformationWithRange<out T : Transformation>(
+internal data class TransformationWithRange<out T : Transformation>(
     val transformation: T,
     val range: TransformationRange?,
 ) {
@@ -135,7 +151,7 @@
 }
 
 /** The progress-based range of a [PropertyTransformation]. */
-data class TransformationRange(val start: Float, val end: Float, val easing: Easing) {
+internal data class TransformationRange(val start: Float, val end: Float, val easing: Easing) {
     constructor(
         start: Float? = null,
         end: Float? = null,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index d756c86..2f4d5bff 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -19,18 +19,15 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.OverscrollScope
 import com.android.compose.animation.scene.content.state.TransitionState
 
-internal class Translate(
-    override val matcher: ElementMatcher,
-    private val x: Dp = 0.dp,
-    private val y: Dp = 0.dp,
-) : InterpolatedOffsetTransformation {
+internal class Translate private constructor(private val x: Dp, private val y: Dp) :
+    InterpolatedPropertyTransformation<Offset> {
+    override val property = PropertyTransformation.Property.Offset
+
     override fun PropertyTransformationScope.transform(
         content: ContentKey,
         element: ElementKey,
@@ -39,13 +36,19 @@
     ): Offset {
         return Offset(idleValue.x + x.toPx(), idleValue.y + y.toPx())
     }
+
+    class Factory(private val x: Dp, private val y: Dp) : Transformation.Factory {
+        override fun create(): Transformation = Translate(x, y)
+    }
 }
 
-internal class OverscrollTranslate(
-    override val matcher: ElementMatcher,
-    val x: OverscrollScope.() -> Float = { 0f },
-    val y: OverscrollScope.() -> Float = { 0f },
-) : InterpolatedOffsetTransformation {
+internal class OverscrollTranslate
+private constructor(
+    private val x: OverscrollScope.() -> Float,
+    private val y: OverscrollScope.() -> Float,
+) : InterpolatedPropertyTransformation<Offset> {
+    override val property = PropertyTransformation.Property.Offset
+
     private val cachedOverscrollScope = CachedOverscrollScope()
 
     override fun PropertyTransformationScope.transform(
@@ -63,6 +66,13 @@
 
         return Offset(x = value.x + overscrollScope.x(), y = value.y + overscrollScope.y())
     }
+
+    class Factory(
+        private val x: OverscrollScope.() -> Float,
+        private val y: OverscrollScope.() -> Float,
+    ) : Transformation.Factory {
+        override fun create(): Transformation = OverscrollTranslate(x, y)
+    }
 }
 
 /**
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 61e9bb0..10057b2 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
@@ -644,7 +644,7 @@
         mutableUserActionsA = emptyMap()
         mutableUserActionsB = emptyMap()
 
-        // start accelaratedScroll and scroll over to B -> null
+        // start acceleratedScroll and scroll over to B -> null
         val dragController2 = onDragStartedImmediately()
         dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f)
         dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f)
@@ -1530,7 +1530,7 @@
     fun interceptingTransitionKeepsDistance() = runGestureTest {
         var swipeDistance = 75f
         layoutState.transitions = transitions {
-            from(SceneA, to = SceneB) { distance = UserActionDistance { _, _ -> swipeDistance } }
+            from(SceneA, to = SceneB) { distance = UserActionDistance { _, _, _ -> swipeDistance } }
         }
 
         // Start transition.
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 57ad81e..79ca891 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
@@ -153,7 +153,7 @@
 
         // Default transition from A to B.
         assertThat(state.setTargetScene(SceneB, animationScope = this)).isNotNull()
-        assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(1)
+        assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(1)
 
         // Go back to A.
         state.setTargetScene(SceneA, animationScope = this)
@@ -166,7 +166,7 @@
                 state.setTargetScene(SceneB, animationScope = this, transitionKey = transitionkey)
             )
             .isNotNull()
-        assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(2)
+        assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(2)
     }
 
     @Test
@@ -686,4 +686,30 @@
         assertThat(state.transitionState).isIdle()
         assertThat(job.isCancelled).isTrue()
     }
+
+    @Test
+    fun badTransitionSpecThrowsMeaningfulMessageWhenStartingTransition() {
+        val state =
+            MutableSceneTransitionLayoutState(
+                SceneA,
+                transitions {
+                    // This transition definition is bad because they both match when transitioning
+                    // from A to B.
+                    from(SceneA) {}
+                    to(SceneB) {}
+                },
+            )
+
+        val exception =
+            assertThrows(IllegalStateException::class.java) {
+                runBlocking { state.startTransition(transition(from = SceneA, to = SceneB)) }
+            }
+
+        assertThat(exception)
+            .hasMessageThat()
+            .isEqualTo(
+                "Found multiple transition specs for transition SceneKey(debugName=SceneA) => " +
+                    "SceneKey(debugName=SceneB)"
+            )
+    }
 }
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 97a96a4..b3a3261 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
@@ -55,7 +55,6 @@
 import androidx.compose.ui.test.swipeUp
 import androidx.compose.ui.test.swipeWithVelocity
 import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -672,12 +671,12 @@
         }
 
         assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue()
-        assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(1)
+        assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(1)
 
         // Move the pointer up to swipe to scene B using the new transition.
         rule.onRoot().performTouchInput { moveBy(Offset(0f, -1.dp.toPx()), delayMillis = 1_000) }
         assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue()
-        assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(2)
+        assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(2)
     }
 
     @Test
@@ -685,7 +684,8 @@
         val swipeDistance =
             object : UserActionDistance {
                 override fun UserActionDistanceScope.absoluteDistance(
-                    fromSceneSize: IntSize,
+                    fromContent: ContentKey,
+                    toContent: ContentKey,
                     orientation: Orientation,
                 ): Float {
                     // Foo is going to have a vertical offset of 50dp. Let's make the swipe distance
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index 1f9ba9e..70f2ff80 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -28,11 +28,12 @@
 import com.android.compose.animation.scene.TestScenes.SceneB
 import com.android.compose.animation.scene.TestScenes.SceneC
 import com.android.compose.animation.scene.content.state.TransitionState
-import com.android.compose.animation.scene.transformation.CustomSizeTransformation
+import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
 import com.android.compose.animation.scene.transformation.OverscrollTranslate
+import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.PropertyTransformationScope
+import com.android.compose.animation.scene.transformation.TransformationMatcher
 import com.android.compose.animation.scene.transformation.TransformationRange
-import com.android.compose.animation.scene.transformation.TransformationWithRange
 import com.android.compose.test.transition
 import com.google.common.truth.Correspondence
 import com.google.common.truth.Truth.assertThat
@@ -103,7 +104,7 @@
         val transitions = transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } }
 
         val transformations =
-            transitions.transitionSpecs.single().transformationSpec(aToB()).transformations
+            transitions.transitionSpecs.single().transformationSpec(aToB()).transformationMatchers
         assertThat(transformations.size).isEqualTo(1)
         assertThat(transformations.single().range).isEqualTo(null)
     }
@@ -126,7 +127,7 @@
         }
 
         val transformations =
-            transitions.transitionSpecs.single().transformationSpec(aToB()).transformations
+            transitions.transitionSpecs.single().transformationSpec(aToB()).transformationMatchers
         assertThat(transformations)
             .comparingElementsUsing(TRANSFORMATION_RANGE)
             .containsExactly(
@@ -157,7 +158,7 @@
         }
 
         val transformations =
-            transitions.transitionSpecs.single().transformationSpec(aToB()).transformations
+            transitions.transitionSpecs.single().transformationSpec(aToB()).transformationMatchers
         assertThat(transformations)
             .comparingElementsUsing(TRANSFORMATION_RANGE)
             .containsExactly(
@@ -185,7 +186,7 @@
         }
 
         val transformations =
-            transitions.transitionSpecs.single().transformationSpec(aToB()).transformations
+            transitions.transitionSpecs.single().transformationSpec(aToB()).transformationMatchers
         assertThat(transformations)
             .comparingElementsUsing(TRANSFORMATION_RANGE)
             .containsExactly(
@@ -215,7 +216,7 @@
         // to B we defined.
         val transitionSpec = transitions.transitionSpec(from = SceneB, to = SceneA, key = null)
 
-        val transformations = transitionSpec.transformationSpec(aToB()).transformations
+        val transformations = transitionSpec.transformationSpec(aToB()).transformationMatchers
 
         assertThat(transformations)
             .comparingElementsUsing(TRANSFORMATION_RANGE)
@@ -225,7 +226,7 @@
             )
 
         val previewTransformations =
-            transitionSpec.previewTransformationSpec(aToB())?.transformations
+            transitionSpec.previewTransformationSpec(aToB())?.transformationMatchers
 
         assertThat(previewTransformations)
             .comparingElementsUsing(TRANSFORMATION_RANGE)
@@ -255,7 +256,7 @@
                 key = TransitionKey.PredictiveBack,
             )
 
-        val transformations = transitionSpec.transformationSpec(aToB()).transformations
+        val transformations = transitionSpec.transformationSpec(aToB()).transformationMatchers
 
         assertThat(transformations)
             .comparingElementsUsing(TRANSFORMATION_RANGE)
@@ -265,7 +266,7 @@
             )
 
         val previewTransformations =
-            transitionSpec.previewTransformationSpec(aToB())?.transformations
+            transitionSpec.previewTransformationSpec(aToB())?.transformationMatchers
 
         assertThat(previewTransformations)
             .comparingElementsUsing(TRANSFORMATION_RANGE)
@@ -316,7 +317,7 @@
 
         val overscrollSpec = transitions.overscrollSpecs.single()
         val transformation =
-            overscrollSpec.transformationSpec.transformations.single().transformation
+            overscrollSpec.transformationSpec.transformationMatchers.single().factory.create()
         assertThat(transformation).isInstanceOf(OverscrollTranslate::class.java)
     }
 
@@ -324,7 +325,7 @@
     fun overscrollSpec_for_overscrollDisabled() {
         val transitions = transitions { overscrollDisabled(SceneA, Orientation.Vertical) }
         val overscrollSpec = transitions.overscrollSpecs.single()
-        assertThat(overscrollSpec.transformationSpec.transformations).isEmpty()
+        assertThat(overscrollSpec.transformationSpec.transformationMatchers).isEmpty()
     }
 
     @Test
@@ -353,9 +354,9 @@
         val transitions = transitions {
             from(SceneA, to = SceneB) {
                 fractionRange {
-                    transformation(
-                        object : CustomSizeTransformation {
-                            override val matcher: ElementMatcher = TestElements.Foo
+                    transformation(TestElements.Foo) {
+                        object : CustomPropertyTransformation<IntSize> {
+                            override val property = PropertyTransformation.Property.Size
 
                             override fun PropertyTransformationScope.transform(
                                 content: ContentKey,
@@ -364,7 +365,7 @@
                                 transitionScope: CoroutineScope,
                             ): IntSize = IntSize.Zero
                         }
-                    )
+                    }
                 }
             }
         }
@@ -377,7 +378,7 @@
 
     companion object {
         private val TRANSFORMATION_RANGE =
-            Correspondence.transforming<TransformationWithRange<*>, TransformationRange?>(
+            Correspondence.transforming<TransformationMatcher, TransformationRange?>(
                 { it?.range },
                 "has range equal to",
             )
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
index 313379f..0adb480 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
@@ -157,7 +157,7 @@
         check("isUserInputOngoing").that(actual.isUserInputOngoing).isEqualTo(isUserInputOngoing)
     }
 
-    fun hasOverscrollSpec(): OverscrollSpec {
+    internal fun hasOverscrollSpec(): OverscrollSpec {
         check("currentOverscrollSpec").that(actual.currentOverscrollSpec).isNotNull()
         return actual.currentOverscrollSpec!!
     }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/CustomTransformationTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/CustomTransformationTest.kt
index 487b099..444ec4e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/CustomTransformationTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/CustomTransformationTest.kt
@@ -30,7 +30,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.TestElements
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.testTransition
@@ -47,8 +46,9 @@
     @Test
     fun customSize() {
         /** A size transformation that adds [add] to the size of the transformed element(s). */
-        class AddSizeTransformation(override val matcher: ElementMatcher, private val add: Dp) :
-            CustomSizeTransformation {
+        class AddSizeTransformation(private val add: Dp) : CustomPropertyTransformation<IntSize> {
+            override val property = PropertyTransformation.Property.Size
+
             override fun PropertyTransformationScope.transform(
                 content: ContentKey,
                 element: ElementKey,
@@ -69,7 +69,7 @@
                 spec = tween(16 * 4, easing = LinearEasing)
 
                 // Add 80dp to the width and height of Foo.
-                transformation(AddSizeTransformation(TestElements.Foo, 80.dp))
+                transformation(TestElements.Foo) { AddSizeTransformation(80.dp) }
             },
         ) {
             before { onElement(TestElements.Foo).assertSizeIsEqualTo(40.dp, 20.dp) }
@@ -84,8 +84,9 @@
     @Test
     fun customOffset() {
         /** An offset transformation that adds [add] to the offset of the transformed element(s). */
-        class AddOffsetTransformation(override val matcher: ElementMatcher, private val add: Dp) :
-            CustomOffsetTransformation {
+        class AddOffsetTransformation(private val add: Dp) : CustomPropertyTransformation<Offset> {
+            override val property = PropertyTransformation.Property.Offset
+
             override fun PropertyTransformationScope.transform(
                 content: ContentKey,
                 element: ElementKey,
@@ -106,7 +107,7 @@
                 spec = tween(16 * 4, easing = LinearEasing)
 
                 // Add 80dp to the offset of Foo.
-                transformation(AddOffsetTransformation(TestElements.Foo, 80.dp))
+                transformation(TestElements.Foo) { AddOffsetTransformation(80.dp) }
             },
         ) {
             before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(0.dp, 0.dp) }
diff --git a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
index 651e401..18073ad 100644
--- a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
+    <dimen name="status_view_margin_horizontal">8dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-sw600dp/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
index 10e630d..37cd590 100644
--- a/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
@@ -17,4 +17,5 @@
 <resources>
     <!-- For portrait direction in unfold foldable device, we don't need keyguard_smartspace_top_offset-->
     <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
+    <dimen name="status_view_margin_horizontal">62dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/customization/res/values-sw720dp-land/dimens.xml
new file mode 100644
index 0000000..c1cf42c
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-sw720dp-land/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <dimen name="status_view_margin_horizontal">24dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/customization/res/values-sw720dp-port/dimens.xml
new file mode 100644
index 0000000..54dbeaa
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-sw720dp-port/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <dimen name="status_view_margin_horizontal">124dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
index 7feea6e..041ae62 100644
--- a/packages/SystemUI/customization/res/values/dimens.xml
+++ b/packages/SystemUI/customization/res/values/dimens.xml
@@ -39,4 +39,5 @@
     <!--Dimens used in both lockscreen  preview and smartspace -->
     <dimen name="date_weather_view_height">24dp</dimen>
     <dimen name="enhanced_smartspace_height">104dp</dimen>
+    <dimen name="status_view_margin_horizontal">0dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 935737c..5317ac1 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -154,7 +154,7 @@
                                     alignment =
                                         DigitalAlignment(
                                             HorizontalAlignment.CENTER,
-                                            VerticalAlignment.CENTER,
+                                            VerticalAlignment.BASELINE,
                                         ),
                                     dateTimeFormat = "hh",
                                 ),
@@ -173,7 +173,7 @@
                                     alignment =
                                         DigitalAlignment(
                                             HorizontalAlignment.CENTER,
-                                            VerticalAlignment.CENTER,
+                                            VerticalAlignment.BASELINE,
                                         ),
                                     dateTimeFormat = "hh",
                                 ),
@@ -192,7 +192,7 @@
                                     alignment =
                                         DigitalAlignment(
                                             HorizontalAlignment.CENTER,
-                                            VerticalAlignment.CENTER,
+                                            VerticalAlignment.BASELINE,
                                         ),
                                     dateTimeFormat = "mm",
                                 ),
@@ -211,7 +211,7 @@
                                     alignment =
                                         DigitalAlignment(
                                             HorizontalAlignment.CENTER,
-                                            VerticalAlignment.CENTER,
+                                            VerticalAlignment.BASELINE,
                                         ),
                                     dateTimeFormat = "mm",
                                 ),
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
index 27ed099..faef18c 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -18,6 +18,7 @@
 
 import android.graphics.Canvas
 import android.graphics.Point
+import android.icu.text.NumberFormat
 import android.util.MathUtils.constrainedMap
 import android.view.View
 import android.view.ViewGroup
@@ -26,6 +27,7 @@
 import com.android.systemui.customization.R
 import com.android.systemui.shared.clocks.ClockContext
 import com.android.systemui.shared.clocks.DigitTranslateAnimator
+import java.util.Locale
 import kotlin.math.abs
 import kotlin.math.max
 import kotlin.math.min
@@ -34,10 +36,14 @@
 
 class FlexClockView(clockCtx: ClockContext) : DigitalClockFaceView(clockCtx) {
     override var digitalClockTextViewMap = mutableMapOf<Int, SimpleDigitalClockTextView>()
-    val digitLeftTopMap = mutableMapOf<Int, Point>()
-    var maxSingleDigitSize = Point(-1, -1)
-    val lockscreenTranslate = Point(0, 0)
-    var aodTranslate = Point(0, 0)
+    private val digitLeftTopMap = mutableMapOf<Int, Point>()
+
+    private var maxSingleDigitSize = Point(-1, -1)
+    private val lockscreenTranslate = Point(0, 0)
+    private var aodTranslate = Point(0, 0)
+
+    // Does the current language have mono vertical size when displaying numerals
+    private var isMonoVerticalNumericLineSpacing = true
 
     init {
         setWillNotDraw(false)
@@ -46,6 +52,7 @@
                 ViewGroup.LayoutParams.WRAP_CONTENT,
                 ViewGroup.LayoutParams.WRAP_CONTENT,
             )
+        updateLocale(Locale.getDefault())
     }
 
     private val digitOffsets = mutableMapOf<Int, Float>()
@@ -58,12 +65,19 @@
 
     protected override fun calculateSize(widthMeasureSpec: Int, heightMeasureSpec: Int): Point {
         maxSingleDigitSize = Point(-1, -1)
+        val bottomLocation: (textView: SimpleDigitalClockTextView) -> Int = { textView ->
+            if (isMonoVerticalNumericLineSpacing) {
+                maxSingleDigitSize.y
+            } else {
+                (textView.paint.fontMetrics.descent - textView.paint.fontMetrics.ascent).toInt()
+            }
+        }
+
         digitalClockTextViewMap.forEach { (_, textView) ->
             textView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
             maxSingleDigitSize.x = max(maxSingleDigitSize.x, textView.measuredWidth)
-            maxSingleDigitSize.y = max(maxSingleDigitSize.y, textView.measuredHeight)
+            maxSingleDigitSize.y = max(bottomLocation(textView), textView.measuredHeight)
         }
-        val textView = digitalClockTextViewMap[R.id.HOUR_FIRST_DIGIT]!!
         aodTranslate = Point(0, 0)
         return Point(
             ((maxSingleDigitSize.x + abs(aodTranslate.x)) * 2),
@@ -103,6 +117,11 @@
         }
     }
 
+    override fun onLocaleChanged(locale: Locale) {
+        updateLocale(locale)
+        requestLayout()
+    }
+
     override fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
         dozeControlState.animateDoze = {
             super.animateDoze(isDozing, isAnimated)
@@ -163,6 +182,18 @@
         }
     }
 
+    private fun updateLocale(locale: Locale) {
+        isMonoVerticalNumericLineSpacing =
+            !NON_MONO_VERTICAL_NUMERIC_LINE_SPACING_LANGUAGES.any {
+                val newLocaleNumberFormat =
+                    NumberFormat.getInstance(locale).format(FORMAT_NUMBER.toLong())
+                val nonMonoVerticalNumericLineSpaceNumberFormat =
+                    NumberFormat.getInstance(Locale.forLanguageTag(it))
+                        .format(FORMAT_NUMBER.toLong())
+                newLocaleNumberFormat == nonMonoVerticalNumericLineSpaceNumberFormat
+            }
+    }
+
     /**
      * Offsets the textViews of the clock for the step clock animation.
      *
@@ -261,10 +292,18 @@
         // Constants for the animation
         private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED
 
+        private const val FORMAT_NUMBER = 1234567890
+
         // Total available transition time for each digit, taking into account the step. If step is
         // 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7.
         private const val AVAILABLE_ANIMATION_TIME = 1.0f - MOVE_DIGIT_STEP * (NUM_DIGITS - 1)
 
+        // Add language tags below that do not have vertically mono spaced numerals
+        private val NON_MONO_VERTICAL_NUMERIC_LINE_SPACING_LANGUAGES =
+            setOf(
+                "my", // Burmese
+            )
+
         // Use the sign of targetTranslation to control the direction of digit translation
         fun updateDirectionalTargetTranslate(id: Int, targetTranslation: Point): Point {
             val outPoint = Point(targetTranslation)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
index bd56dbf..48761c0 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -101,7 +101,7 @@
         }
     }
 
-    override var verticalAlignment: VerticalAlignment = VerticalAlignment.CENTER
+    override var verticalAlignment: VerticalAlignment = VerticalAlignment.BASELINE
     override var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.LEFT
     override var isAnimationEnabled = true
     override var dozeFraction: Float = 0F
@@ -242,7 +242,7 @@
                 -translation.y.toFloat(),
                 (-translation.x + measuredWidth).toFloat(),
                 (-translation.y + measuredHeight).toFloat(),
-                Paint().also { it.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) },
+                PORTER_DUFF_XFER_MODE_PAINT,
             )
             canvas.restore()
             canvas.restore()
@@ -387,7 +387,7 @@
 
     // translation of reference point of text
     // used for translation when calling textInterpolator
-    fun getLocalTranslation(): Point {
+    private fun getLocalTranslation(): Point {
         val viewHeight = if (isVertical) measuredWidth else measuredHeight
         val interpolatedTextBounds = updateInterpolatedTextBounds()
         val localTranslation = Point(0, 0)
@@ -413,7 +413,9 @@
                         correctedBaseline
             }
             VerticalAlignment.BASELINE -> {
-                localTranslation.y = -lockScreenPaint.strokeWidth.toInt()
+                // account for max bottom distance of font, so clock doesn't collide with elements
+                localTranslation.y =
+                    -lockScreenPaint.strokeWidth.toInt() - paint.fontMetrics.descent.toInt()
             }
         }
 
@@ -533,7 +535,9 @@
     }
 
     companion object {
-        val AOD_STROKE_WIDTH = "2dp"
+        private val PORTER_DUFF_XFER_MODE_PAINT =
+            Paint().also { it.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) }
+
         val AOD_COLOR = Color.WHITE
         val OPTICAL_SIZE_AXIS = ClockFontAxisSetting("opsz", 144f)
         val DEFAULT_LS_VARIATION =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt
index 57a6797..85bdf92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt
@@ -28,7 +28,7 @@
 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.shade.data.repository.FakeShadeDisplayRepository
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import java.util.concurrent.Executor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -62,7 +62,7 @@
         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 shadePositionRepository = FakeShadeDisplayRepository()
 
     private val mainExecutor = Executor { it.run() }
     private val backgroundExecutor = Executor { it.run() }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
index baeb2dd..0084e18 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
@@ -16,38 +16,64 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
 
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val communalSceneRepository = kosmos.fakeCommunalSceneRepository
+    private val sceneInteractor = kosmos.sceneInteractor
 
     private val underTest: CommunalTransitionViewModel by lazy {
         kosmos.communalTransitionViewModel
     }
 
+    @DisableFlags(FLAG_SCENE_CONTAINER)
     @Test
     fun testIsUmoOnCommunalDuringTransitionBetweenLockscreenAndGlanceableHub() =
         testScope.runTest {
@@ -61,6 +87,7 @@
             assertThat(isUmoOnCommunal).isFalse()
         }
 
+    @DisableFlags(FLAG_SCENE_CONTAINER)
     @Test
     fun testIsUmoOnCommunalDuringTransitionBetweenDreamingAndGlanceableHub() =
         testScope.runTest {
@@ -74,6 +101,7 @@
             assertThat(isUmoOnCommunal).isFalse()
         }
 
+    @DisableFlags(FLAG_SCENE_CONTAINER)
     @Test
     fun testIsUmoOnCommunalDuringTransitionBetweenOccludedAndGlanceableHub() =
         testScope.runTest {
@@ -87,6 +115,7 @@
             assertThat(isUmoOnCommunal).isFalse()
         }
 
+    @DisableFlags(FLAG_SCENE_CONTAINER)
     @Test
     fun isUmoOnCommunal_noLongerVisible_returnsFalse() =
         testScope.runTest {
@@ -103,6 +132,7 @@
             assertThat(isUmoOnCommunal).isFalse()
         }
 
+    @DisableFlags(FLAG_SCENE_CONTAINER)
     @Test
     fun isUmoOnCommunal_idleOnCommunal_returnsTrue() =
         testScope.runTest {
@@ -116,11 +146,25 @@
             assertThat(isUmoOnCommunal).isTrue()
         }
 
+    @EnableFlags(FLAG_SCENE_CONTAINER)
+    @Test
+    fun isUmoOnCommunal_sceneContainerEnabled_idleOnCommunal_returnsTrue() =
+        testScope.runTest {
+            val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal)
+            assertThat(isUmoOnCommunal).isFalse()
+
+            // Change to communal scene.
+            setIdleScene(Scenes.Communal)
+
+            // isUmoOnCommunal returns true, even without any keyguard transition.
+            assertThat(isUmoOnCommunal).isTrue()
+        }
+
     private suspend fun TestScope.enterCommunal(from: KeyguardState) {
         keyguardTransitionRepository.sendTransitionSteps(
             from = from,
             to = KeyguardState.GLANCEABLE_HUB,
-            testScope
+            testScope,
         )
         communalSceneRepository.changeScene(CommunalScenes.Communal)
         runCurrent()
@@ -130,9 +174,17 @@
         keyguardTransitionRepository.sendTransitionSteps(
             from = KeyguardState.GLANCEABLE_HUB,
             to = to,
-            testScope
+            testScope,
         )
         communalSceneRepository.changeScene(CommunalScenes.Blank)
         runCurrent()
     }
+
+    private fun setIdleScene(scene: SceneKey) {
+        sceneInteractor.changeScene(scene, "test")
+        val transitionState =
+            MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(scene))
+        sceneInteractor.setTransitionState(transitionState)
+        testScope.runCurrent()
+    }
 }
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 6b851cb..5bbd3ff 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
@@ -56,6 +56,7 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.media.controls.ui.controller.mediaCarouselController
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.testKosmos
@@ -133,6 +134,7 @@
                 accessibilityManager,
                 packageManager,
                 WIDGET_PICKER_PACKAGE_NAME,
+                kosmos.mediaCarouselController,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 09daa51..3eba8ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -69,6 +69,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.mediaCarouselController
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
@@ -178,6 +179,7 @@
             mediaHost,
             logcatLogBuffer("CommunalViewModelTest"),
             metricsLogger,
+            kosmos.mediaCarouselController,
         )
     }
 
@@ -627,10 +629,7 @@
             kosmos.setTransition(
                 sceneTransition = Idle(Scenes.Communal),
                 stateTransition =
-                    TransitionStep(
-                        from = KeyguardState.DREAMING,
-                        to = KeyguardState.GLANCEABLE_HUB
-                    ),
+                    TransitionStep(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB),
             )
 
             // Then flow is not frozen
@@ -647,10 +646,7 @@
             kosmos.setTransition(
                 sceneTransition = Idle(Scenes.Lockscreen),
                 stateTransition =
-                    TransitionStep(
-                        from = KeyguardState.GLANCEABLE_HUB,
-                        to = KeyguardState.OCCLUDED
-                    ),
+                    TransitionStep(from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OCCLUDED),
             )
 
             // Then flow is not frozen
@@ -735,10 +731,7 @@
             kosmos.setTransition(
                 sceneTransition = Idle(Scenes.Communal),
                 stateTransition =
-                    TransitionStep(
-                        from = KeyguardState.DREAMING,
-                        to = KeyguardState.GLANCEABLE_HUB
-                    ),
+                    TransitionStep(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB),
             )
 
             // Widgets available
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
similarity index 95%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
index a9a5cac..4e7839e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
@@ -34,13 +34,13 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class ShadePositionRepositoryTest : SysuiTestCase() {
+class ShadeDisplaysRepositoryTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val commandRegistry = kosmos.commandRegistry
     private val pw = PrintWriter(StringWriter())
 
-    private val underTest = ShadePositionRepositoryImpl(commandRegistry)
+    private val underTest = ShadeDisplaysRepositoryImpl(commandRegistry)
 
     @Before
     fun setUp() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
index f192b59..8ef1e56 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
@@ -28,7 +28,7 @@
 import com.android.systemui.display.data.repository.FakeDisplayWindowPropertiesRepository
 import com.android.systemui.display.shared.model.DisplayWindowProperties
 import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.data.repository.FakeShadePositionRepository
+import com.android.systemui.shade.data.repository.FakeShadeDisplayRepository
 import com.android.systemui.statusbar.phone.ConfigurationForwarder
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -50,7 +50,7 @@
 class ShadeDisplaysInteractorTest : SysuiTestCase() {
 
     private val shadeRootview = mock<WindowRootView>()
-    private val positionRepository = FakeShadePositionRepository()
+    private val positionRepository = FakeShadeDisplayRepository()
     private val defaultContext = mock<Context>()
     private val secondaryContext = mock<Context>()
     private val contextStore = FakeDisplayWindowPropertiesRepository()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index d823bf5..4eef308 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -27,10 +27,14 @@
 import android.platform.test.annotations.EnableFlags
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
@@ -40,9 +44,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.user.data.repository.userRepository
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.time.fakeSystemClock
 import com.android.wifitrackerlib.HotspotNetworkEntry
 import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType
@@ -53,31 +55,23 @@
 import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
 import com.android.wifitrackerlib.WifiPickerTracker
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.kotlin.any
-import org.mockito.kotlin.capture
+import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.mock
-import org.mockito.kotlin.times
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
-/**
- * Note: Most of these tests are duplicates of [WifiRepositoryImplTest] tests.
- *
- * Any new tests added here may also need to be added to [WifiRepositoryImplTest].
- */
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-class WifiRepositoryImplTest() : SysuiTestCase() {
-    private val kosmos = testKosmos()
+class WifiRepositoryImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val userRepository = kosmos.fakeUserRepository
 
     // Using lazy means that the class will only be constructed once it's fetched. Because the
@@ -108,13 +102,13 @@
 
     private val callbackCaptor = argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
 
-    private val dispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(dispatcher)
+    private val dispatcher = kosmos.testDispatcher
+    private val testScope = kosmos.testScope
 
     @Before
     fun setUp() {
         userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
-        whenever(wifiPickerTrackerFactory.create(any(), any(), capture(callbackCaptor), any()))
+        whenever(wifiPickerTrackerFactory.create(any(), any(), callbackCaptor.capture(), any()))
             .thenReturn(wifiPickerTracker)
     }
 
@@ -122,7 +116,6 @@
     fun wifiPickerTrackerCreation_scansDisabled() =
         testScope.runTest {
             collectLastValue(underTest.wifiNetwork)
-            testScope.runCurrent()
 
             verify(wifiPickerTracker).disableScanning()
         }
@@ -1001,7 +994,6 @@
                 mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(false) }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
-            testScope.runCurrent()
 
             assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
         }
@@ -1017,7 +1009,6 @@
                 }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
-            testScope.runCurrent()
 
             assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
         }
@@ -1034,7 +1025,6 @@
                 }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
-            testScope.runCurrent()
 
             assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
         }
@@ -1051,7 +1041,6 @@
                 }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
-            testScope.runCurrent()
 
             assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
         }
@@ -1068,7 +1057,6 @@
                 }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
-            testScope.runCurrent()
 
             assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
         }
@@ -1085,7 +1073,6 @@
                 }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
-            testScope.runCurrent()
 
             assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
         }
@@ -1103,14 +1090,12 @@
                 }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
-            testScope.runCurrent()
 
             assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
 
             // WHEN the network is lost
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
             getCallback().onWifiEntriesChanged()
-            testScope.runCurrent()
 
             // THEN the isWifiConnected updates
             assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
@@ -1216,9 +1201,6 @@
             assertThat(latest).isEmpty()
         }
 
-    // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
-    // this needs to
-    // be updated to capture the argument instead so currentUserContext can be private.
     @Test
     @EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
     fun oneUserVerifyCreatingWifiPickerTracker_multiuserFlagEnabled() =
@@ -1230,16 +1212,16 @@
             )
 
             userRepository.setSelectedUserInfo(PRIMARY_USER)
-            runCurrent()
-            val currentUserContext by collectLastValue(underTest.selectedUserContext)
 
-            assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
-            verify(wifiPickerTrackerFactory).create(any(), any(), any(), any())
+            collectLastValue(underTest.wifiNetwork)
+
+            val contextCaptor = argumentCaptor<Context>()
+            verify(wifiPickerTrackerFactory).create(contextCaptor.capture(), any(), any(), any())
+            // If the flag is on, verify that we use the context from #createContextAsUser and we
+            // do NOT use the top-level context
+            assertThat(contextCaptor.firstValue).isEqualTo(primaryUserMockContext)
         }
 
-    // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
-    // this needs to
-    // be updated to capture the argument instead so currentUserContext can be private.
     @Test
     @EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
     fun changeUserVerifyCreatingWifiPickerTracker_multiuserEnabled() =
@@ -1249,32 +1231,30 @@
                 UserHandle.of(PRIMARY_USER_ID),
                 primaryUserMockContext,
             )
-
-            runCurrent()
             userRepository.setSelectedUserInfo(PRIMARY_USER)
-            runCurrent()
-            val currentUserContext by collectLastValue(underTest.selectedUserContext)
 
-            assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
+            collectLastValue(underTest.wifiNetwork)
 
+            val contextCaptor = argumentCaptor<Context>()
+            verify(wifiPickerTrackerFactory).create(contextCaptor.capture(), any(), any(), any())
+            assertThat(contextCaptor.firstValue).isEqualTo(primaryUserMockContext)
+
+            reset(wifiPickerTrackerFactory)
+
+            // WHEN we switch to a different user
             val otherUserMockContext = mock<Context>()
             mContext.prepareCreateContextAsUser(
                 UserHandle.of(ANOTHER_USER_ID),
                 otherUserMockContext,
             )
-
-            runCurrent()
             userRepository.setSelectedUserInfo(ANOTHER_USER)
-            runCurrent()
-            val otherUserContext by collectLastValue(underTest.selectedUserContext)
 
-            assertThat(otherUserContext).isEqualTo(otherUserMockContext)
-            verify(wifiPickerTrackerFactory, times(2)).create(any(), any(), any(), any())
+            // THEN we use the different user's context to create WifiPickerTracker
+            val newCaptor = argumentCaptor<Context>()
+            verify(wifiPickerTrackerFactory).create(newCaptor.capture(), any(), any(), any())
+            assertThat(newCaptor.firstValue).isEqualTo(otherUserMockContext)
         }
 
-    // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
-    // this needs to
-    // be updated to capture the argument instead so currentUserContext can be private.
     @Test
     @DisableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
     fun changeUserVerifyCreatingWifiPickerTracker_multiuserDisabled() =
@@ -1285,36 +1265,39 @@
                 primaryUserMockContext,
             )
 
-            runCurrent()
             userRepository.setSelectedUserInfo(PRIMARY_USER)
-            runCurrent()
-            val currentUserContext by collectLastValue(underTest.selectedUserContext)
 
-            assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
+            collectLastValue(underTest.wifiNetwork)
 
+            val contextCaptor = argumentCaptor<Context>()
+            verify(wifiPickerTrackerFactory).create(contextCaptor.capture(), any(), any(), any())
+            // If the flag is off, verify that we do NOT use the context from #createContextAsUser
+            // and we instead use the top-level context
+            assertThat(contextCaptor.firstValue).isNotEqualTo(primaryUserMockContext)
+            assertThat(contextCaptor.firstValue).isEqualTo(mContext)
+
+            reset(wifiPickerTrackerFactory)
+
+            // WHEN we switch to a different user
             val otherUserMockContext = mock<Context>()
             mContext.prepareCreateContextAsUser(
                 UserHandle.of(ANOTHER_USER_ID),
                 otherUserMockContext,
             )
-
-            runCurrent()
             userRepository.setSelectedUserInfo(ANOTHER_USER)
-            runCurrent()
 
-            verify(wifiPickerTrackerFactory, times(1)).create(any(), any(), any(), any())
+            // THEN we do NOT re-create WifiPickerTracker because the multiuser flag is off
+            verify(wifiPickerTrackerFactory, never()).create(any(), any(), any(), any())
         }
 
     private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback {
-        testScope.runCurrent()
-        return callbackCaptor.value
+        return callbackCaptor.firstValue
     }
 
     private fun getTrafficStateCallback(): WifiManager.TrafficStateCallback {
-        testScope.runCurrent()
         val callbackCaptor = argumentCaptor<WifiManager.TrafficStateCallback>()
         verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
-        return callbackCaptor.value!!
+        return callbackCaptor.firstValue
     }
 
     private fun createHotspotWithType(@DeviceType type: Int): HotspotNetworkEntry {
@@ -1325,10 +1308,9 @@
     }
 
     private fun getScanResultsCallback(): WifiManager.ScanResultsCallback {
-        testScope.runCurrent()
         val callbackCaptor = argumentCaptor<WifiManager.ScanResultsCallback>()
         verify(wifiManager).registerScanResultsCallback(any(), callbackCaptor.capture())
-        return callbackCaptor.value!!
+        return callbackCaptor.firstValue
     }
 
     private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index 0e32c95..e686ede 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -24,6 +24,7 @@
 import android.provider.Settings
 import android.service.notification.SystemZenRules
 import android.service.notification.ZenModeConfig
+import android.service.notification.ZenModeConfig.ScheduleInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.notification.modes.TestModeBuilder
@@ -65,6 +66,7 @@
     private lateinit var underTest: ModesDialogViewModel
 
     private lateinit var timeScheduleMode: ZenMode
+    private lateinit var timeScheduleInfo: ScheduleInfo
 
     @Before
     fun setUp() {
@@ -78,18 +80,18 @@
                 kosmos.mockModesDialogEventLogger,
             )
 
-        val scheduleInfo = ZenModeConfig.ScheduleInfo()
-        scheduleInfo.days = intArrayOf(Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY)
-        scheduleInfo.startHour = 11
-        scheduleInfo.endHour = 15
+        timeScheduleInfo = ZenModeConfig.ScheduleInfo()
+        timeScheduleInfo.days = intArrayOf(Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY)
+        timeScheduleInfo.startHour = 11
+        timeScheduleInfo.endHour = 15
         timeScheduleMode =
             TestModeBuilder()
                 .setPackage(SystemZenRules.PACKAGE_ANDROID)
                 .setType(AutomaticZenRule.TYPE_SCHEDULE_TIME)
                 .setManualInvocationAllowed(true)
-                .setConditionId(ZenModeConfig.toScheduleConditionId(scheduleInfo))
+                .setConditionId(ZenModeConfig.toScheduleConditionId(timeScheduleInfo))
                 .setTriggerDescription(
-                    SystemZenRules.getTriggerDescriptionForScheduleTime(mContext, scheduleInfo)
+                    SystemZenRules.getTriggerDescriptionForScheduleTime(mContext, timeScheduleInfo)
                 )
                 .build()
     }
@@ -358,7 +360,7 @@
             assertThat(tiles!![3].subtext).isEqualTo("Off")
             assertThat(tiles!![4].subtext).isEqualTo("On")
             assertThat(tiles!![5].subtext).isEqualTo("Not set")
-            assertThat(tiles!![6].subtext).isEqualTo("Mon - Wed, 11:00 AM - 3:00 PM")
+            assertThat(tiles!![6].subtext).isEqualTo(timeScheduleMode.triggerDescription)
         }
 
     @Test
@@ -437,7 +439,8 @@
             with(tiles?.elementAt(6)!!) {
                 assertThat(this.stateDescription).isEqualTo("Off")
                 assertThat(this.subtextDescription)
-                    .isEqualTo("Monday to Wednesday, 11:00 AM - 3:00 PM")
+                    .isEqualTo(SystemZenRules.getDaysOfWeekFull(context, timeScheduleInfo)
+                    + ", " + SystemZenRules.getTimeSummary(context, timeScheduleInfo))
             }
 
             // All tiles have the same long click info
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
new file mode 100644
index 0000000..7a9d017
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.window
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val fakeDisplayRepository = kosmos.displayRepository
+    private val testScope = kosmos.testScope
+
+    private val underTest by lazy { kosmos.multiDisplayStatusBarWindowControllerStore }
+
+    @Before
+    fun start() {
+        underTest.start()
+    }
+
+    @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+    @Test
+    fun beforeDisplayRemoved_doesNotStopInstances() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            verify(instance, never()).stop()
+        }
+
+    @Test
+    fun displayRemoved_stopsInstance() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+            verify(instance).stop()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt
index 6e66287..61c7193 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt
@@ -17,12 +17,17 @@
 
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
+import android.view.fakeWindowManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.fragments.fragmentService
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.core.StatusBarRootModernization
 import com.android.systemui.statusbar.policy.statusBarConfigurationController
 import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
@@ -37,6 +42,9 @@
         testKosmos().also { it.statusBarWindowViewInflater = it.fakeStatusBarWindowViewInflater }
 
     private val underTest = kosmos.statusBarWindowControllerImpl
+    private val fakeExecutor = kosmos.fakeExecutor
+    private val fakeWindowManager = kosmos.fakeWindowManager
+    private val mockFragmentService = kosmos.fragmentService
     private val fakeStatusBarWindowViewInflater = kosmos.fakeStatusBarWindowViewInflater
     private val statusBarConfigurationController = kosmos.statusBarConfigurationController
 
@@ -59,4 +67,64 @@
 
         verify(mockWindowView, never()).setStatusBarConfigurationController(any())
     }
+
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarConnectedDisplays.FLAG_NAME)
+    fun stop_statusBarModernizationFlagEnabled_doesNotRemoveFragment() {
+        val windowView = fakeStatusBarWindowViewInflater.inflatedMockViews.first()
+
+        underTest.stop()
+        fakeExecutor.runAllReady()
+
+        verify(mockFragmentService, never()).removeAndDestroy(windowView)
+    }
+
+    @Test
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
+    @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+    fun stop_statusBarModernizationFlagDisabled_removesFragment() {
+        val windowView = fakeStatusBarWindowViewInflater.inflatedMockViews.first()
+
+        underTest.stop()
+        fakeExecutor.runAllReady()
+
+        verify(mockFragmentService).removeAndDestroy(windowView)
+    }
+
+    @Test
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
+    @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+    fun stop_statusBarModernizationFlagDisabled_removesFragmentOnExecutor() {
+        val windowView = fakeStatusBarWindowViewInflater.inflatedMockViews.first()
+
+        underTest.stop()
+
+        verify(mockFragmentService, never()).removeAndDestroy(windowView)
+        fakeExecutor.runAllReady()
+        verify(mockFragmentService).removeAndDestroy(windowView)
+    }
+
+    @Test
+    @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+    fun stop_removesWindowViewFromWindowManager() {
+        underTest.attach()
+        underTest.stop()
+
+        assertThat(fakeWindowManager.addedViews).isEmpty()
+    }
+
+    @Test(expected = IllegalStateException::class)
+    @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+    fun stop_connectedDisplaysFlagDisabled_crashes() {
+        underTest.stop()
+    }
+
+    @Test
+    fun attach_windowViewAddedToWindowManager() {
+        val windowView = fakeStatusBarWindowViewInflater.inflatedMockViews.first()
+
+        underTest.attach()
+
+        assertThat(fakeWindowManager.addedViews.keys).containsExactly(windowView)
+    }
 }
diff --git a/packages/SystemUI/res/drawable/volume_background_top.xml b/packages/SystemUI/res/drawable/volume_background_top.xml
index 75849be..132572a 100644
--- a/packages/SystemUI/res/drawable/volume_background_top.xml
+++ b/packages/SystemUI/res/drawable/volume_background_top.xml
@@ -17,7 +17,6 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
     <item>
         <shape>
-            <size android:width="@dimen/volume_dialog_width" />
             <solid android:color="?androidprv:attr/materialColorSurface" />
             <corners android:topLeftRadius="@dimen/volume_dialog_background_corner_radius"
                 android:topRightRadius="@dimen/volume_dialog_background_corner_radius"/>
diff --git a/packages/SystemUI/res/drawable/volume_dialog_spacer.xml b/packages/SystemUI/res/drawable/volume_dialog_spacer.xml
deleted file mode 100644
index 3c60784..0000000
--- a/packages/SystemUI/res/drawable/volume_dialog_spacer.xml
+++ /dev/null
@@ -1,23 +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.
--->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-    <size
-        android:width="@dimen/volume_dialog_spacing"
-        android:height="@dimen/volume_dialog_spacing" />
-    <solid android:color="@color/transparent" />
-</shape>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index f187ce6..694357d 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -13,58 +13,70 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/volume_dialog_root"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="match_parent"
+    app:layoutDescription="@xml/volume_dialog_scene">
 
-    <LinearLayout
-        android:id="@+id/volume_dialog_container"
+    <View
+        android:id="@+id/volume_dialog_background"
+        android:layout_width="@dimen/volume_dialog_width"
+        android:layout_height="0dp"
+        android:layout_marginTop="@dimen/volume_dialog_background_vertical_margin"
+        android:layout_marginBottom="@dimen/volume_dialog_background_vertical_margin"
+        android:background="@drawable/volume_dialog_background"
+        app:layout_constraintBottom_toBottomOf="@id/volume_dialog_settings"
+        app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
+        app:layout_constraintStart_toStartOf="@id/volume_dialog_main_slider_container"
+        app:layout_constraintTop_toTopOf="@id/volume_ringer_and_drawer_container" />
+
+    <include
+        android:id="@id/volume_ringer_and_drawer_container"
+        layout="@layout/volume_ringer_drawer"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical|end"
+        android:layout_marginBottom="@dimen/volume_dialog_components_spacing"
+        app:layout_constraintBottom_toTopOf="@id/volume_dialog_main_slider_container"
+        app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
+        app:layout_constraintStart_toStartOf="@id/volume_dialog_main_slider_container"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_bias="1" />
+
+    <include
+        android:id="@+id/volume_dialog_main_slider_container"
+        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:layout_marginTop="@dimen/volume_dialog_components_spacing"
+        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"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
+        app:layout_constraintStart_toStartOf="@id/volume_dialog_main_slider_container"
+        app:layout_constraintTop_toBottomOf="@id/volume_dialog_main_slider_container"
+        app:layout_constraintVertical_bias="0" />
+
+    <LinearLayout
+        android:id="@+id/volume_dialog_floating_sliders_container"
+        android:layout_width="wrap_content"
+        android:layout_height="0dp"
+        android:layout_marginTop="@dimen/volume_dialog_floating_sliders_vertical_padding_negative"
+        android:layout_marginBottom="@dimen/volume_dialog_floating_sliders_vertical_padding_negative"
         android:divider="@drawable/volume_dialog_floating_sliders_spacer"
+        android:gravity="bottom"
         android:orientation="horizontal"
-        android:showDividers="middle|end|beginning">
+        android:showDividers="middle|beginning|end"
+        app:layout_constraintBottom_toBottomOf="@id/volume_dialog_main_slider_container"
+        app:layout_constraintEnd_toStartOf="@id/volume_dialog_background"
+        app:layout_constraintTop_toTopOf="@id/volume_dialog_main_slider_container" />
 
-        <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" />
-
-        <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">
-
-            <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>
-</FrameLayout>
\ No newline at end of file
+</androidx.constraintlayout.motion.widget.MotionLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
index 7c266e6..b71c470 100644
--- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
@@ -19,13 +19,10 @@
     android:id="@+id/volume_ringer_and_drawer_container"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:gravity="center"
-    android:paddingLeft="@dimen/volume_dialog_ringer_horizontal_padding"
-    android:paddingRight="@dimen/volume_dialog_ringer_horizontal_padding"
-    android:layoutDirection="ltr"
-    android:clipToPadding="false"
     android:clipChildren="false"
-    android:background="@drawable/volume_background_top">
+    android:clipToPadding="false"
+    android:gravity="center"
+    android:layoutDirection="ltr">
 
     <!-- Drawer view, invisible by default. -->
     <FrameLayout
@@ -37,10 +34,10 @@
         <!-- View that is animated to a tapped ringer selection, so it appears selected. -->
         <FrameLayout
             android:id="@+id/volume_drawer_selection_background"
-            android:alpha="0.0"
             android:layout_width="@dimen/volume_dialog_ringer_drawer_button_size"
             android:layout_height="@dimen/volume_dialog_ringer_drawer_button_size"
             android:layout_gravity="bottom|right"
+            android:alpha="0.0"
             android:background="@drawable/volume_drawer_selection_bg" />
 
         <LinearLayout
@@ -65,7 +62,6 @@
         android:background="@drawable/volume_drawer_selection_bg"
         android:contentDescription="@string/volume_ringer_change"
         android:gravity="center"
-        android:padding="@dimen/volume_dialog_ringer_horizontal_padding"
         android:src="@drawable/ic_volume_media"
         android:tint="?androidprv:attr/materialColorOnPrimary" />
 
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 4a53df9..75bee9f 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -46,8 +46,6 @@
 
     <dimen name="lockscreen_shade_max_over_scroll_amount">32dp</dimen>
 
-    <dimen name="status_view_margin_horizontal">8dp</dimen>
-
     <!-- Lockscreen shade transition values -->
     <dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>
     <dimen name="lockscreen_shade_full_transition_distance">200dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
index 707bc9e..f73e91a 100644
--- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
@@ -16,8 +16,6 @@
   -->
 <resources>
     <dimen name="notification_panel_margin_horizontal">48dp</dimen>
-    <dimen name="status_view_margin_horizontal">62dp</dimen>
-
     <!-- qs_tiles_page_horizontal_margin should be margin / 2, otherwise full space between two
          pages is margin * 2, and that makes tiles page not appear immediately after user swipes to
          the side -->
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 8583f05..41bb37e 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -22,8 +22,6 @@
 
     <dimen name="keyguard_split_shade_top_margin">72dp</dimen>
 
-    <dimen name="status_view_margin_horizontal">24dp</dimen>
-
     <dimen name="qs_media_session_height_expanded">184dp</dimen>
     <dimen name="qs_content_horizontal_padding">40dp</dimen>
     <dimen name="qs_horizontal_margin">40dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
index 9248d58..eb570b8 100644
--- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
@@ -20,8 +20,6 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds. -->
 <resources>
-    <dimen name="status_view_margin_horizontal">124dp</dimen>
-
     <dimen name="large_screen_shade_header_left_padding">24dp</dimen>
     <dimen name="qqs_layout_padding_bottom">40dp</dimen>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7af0057..4954f91 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1955,8 +1955,6 @@
     <dimen name="dream_overlay_entry_y_offset">40dp</dimen>
     <dimen name="dream_overlay_exit_y_offset">40dp</dimen>
 
-    <dimen name="status_view_margin_horizontal">0dp</dimen>
-
     <!-- Media output broadcast dialog QR code picture size -->
     <dimen name="media_output_qrcode_size">216dp</dimen>
     <dimen name="media_output_broadcast_info">21dp</dimen>
@@ -2060,26 +2058,29 @@
     <dimen name="contextual_edu_dialog_elevation">2dp</dimen>
 
     <!-- Volume start -->
-    <dimen name="volume_dialog_background_corner_radius">30dp</dimen>
     <dimen name="volume_dialog_width">60dp</dimen>
-    <dimen name="volume_dialog_vertical_padding">10dp</dimen>
+
+    <dimen name="volume_dialog_background_corner_radius">30dp</dimen>
+    <dimen name="volume_dialog_background_vertical_margin">-10dp</dimen>
+
     <dimen name="volume_dialog_components_spacing">8dp</dimen>
     <dimen name="volume_dialog_floating_sliders_spacing">8dp</dimen>
     <dimen name="volume_dialog_floating_sliders_vertical_padding">10dp</dimen>
+    <dimen name="volume_dialog_floating_sliders_vertical_padding_negative">-10dp</dimen>
     <dimen name="volume_dialog_floating_sliders_horizontal_padding">4dp</dimen>
-    <dimen name="volume_dialog_spacing">4dp</dimen>
     <dimen name="volume_dialog_button_size">48dp</dimen>
-    <dimen name="volume_dialog_floating_sliders_bottom_padding">48dp</dimen>
     <dimen name="volume_dialog_slider_width">52dp</dimen>
     <dimen name="volume_dialog_slider_height">254dp</dimen>
 
-    <dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
-    <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
+    <fraction name="volume_dialog_half_opened_bias">0.2</fraction>
 
-    <dimen name="volume_dialog_ringer_horizontal_padding">10dp</dimen>
+    <dimen name="volume_dialog_background_square_corner_radius">12dp</dimen>
+
     <dimen name="volume_dialog_ringer_drawer_button_size">40dp</dimen>
     <dimen name="volume_dialog_ringer_drawer_button_icon_radius">10dp</dimen>
-    <dimen name="volume_dialog_background_square_corner_radius">12dp</dimen>
     <dimen name="volume_dialog_ringer_selected_button_background_radius">20dp</dimen>
+
+    <dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
+    <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
     <!-- Volume end -->
 </resources>
diff --git a/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml b/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml
new file mode 100644
index 0000000..9018e5b
--- /dev/null
+++ b/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ConstraintSet xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/volume_dialog_constraint_set">
+
+    <Constraint
+        android:id="@id/volume_dialog_main_slider_container"
+        android:layout_width="@dimen/volume_dialog_slider_width"
+        android:layout_height="@dimen/volume_dialog_slider_height"
+        android:layout_marginEnd="@dimen/volume_dialog_components_spacing"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_bias="0.5" />
+</ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml b/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml
new file mode 100644
index 0000000..297c388
--- /dev/null
+++ b/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ConstraintSet xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/volume_dialog_half_folded_constraint_set">
+
+    <Constraint
+        android:id="@id/volume_dialog_main_slider_container"
+        android:layout_width="@dimen/volume_dialog_slider_width"
+        android:layout_height="@dimen/volume_dialog_slider_height"
+        android:layout_marginEnd="@dimen/volume_dialog_components_spacing"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_bias="@fraction/volume_dialog_half_opened_bias" />
+</ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/res/xml/volume_dialog_scene.xml b/packages/SystemUI/res/xml/volume_dialog_scene.xml
new file mode 100644
index 0000000..b813474
--- /dev/null
+++ b/packages/SystemUI/res/xml/volume_dialog_scene.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<MotionScene xmlns:motion="http://schemas.android.com/apk/res-auto">
+
+    <Transition
+        motion:autoTransition="none"
+        motion:constraintSetEnd="@id/volume_dialog_half_folded_constraint_set"
+        motion:constraintSetStart="@id/volume_dialog_constraint_set"
+        motion:duration="150" />
+
+    <Include motion:constraintSet="@xml/volume_dialog_constraint_set" />
+    <Include motion:constraintSet="@xml/volume_dialog_half_folded_constraint_set" />
+</MotionScene>
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 95830b5..add459b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -44,7 +44,7 @@
 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.data.repository.ShadeDisplaysRepository;
 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -66,7 +66,7 @@
     private final DisplayManager mDisplayService;
     private final DisplayTracker mDisplayTracker;
     private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
-    private final Provider<ShadePositionRepository> mShadePositionRepositoryProvider;
+    private final Provider<ShadeDisplaysRepository> mShadePositionRepositoryProvider;
     private final ConnectedDisplayKeyguardPresentation.Factory
             mConnectedDisplayKeyguardPresentationFactory;
     private final Context mContext;
@@ -112,7 +112,7 @@
             KeyguardStateController keyguardStateController,
             ConnectedDisplayKeyguardPresentation.Factory
                     connectedDisplayKeyguardPresentationFactory,
-            Provider<ShadePositionRepository> shadePositionRepositoryProvider,
+            Provider<ShadeDisplaysRepository> shadePositionRepositoryProvider,
             @Application CoroutineScope appScope) {
         mContext = context;
         mNavigationBarControllerLazy = navigationBarControllerLazy;
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index fb00d6e..db4d613 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -52,6 +52,8 @@
 
 import com.google.common.util.concurrent.ListenableFuture;
 
+import kotlin.Unit;
+
 import kotlinx.coroutines.Job;
 
 import java.util.Collection;
@@ -87,7 +89,7 @@
     private final ConfigurationInteractor mConfigurationInteractor;
 
     private final Lifecycle mLifecycle;
-    private Rect mExclusionRect = null;
+    private Rect mExclusionRect = new Rect();
 
     private ISystemGestureExclusionListener mGestureExclusionListener;
 
@@ -298,9 +300,18 @@
                         public void onSystemGestureExclusionChanged(int displayId,
                                 Region systemGestureExclusion,
                                 Region systemGestureExclusionUnrestricted) {
-                            mExclusionRect = systemGestureExclusion.getBounds();
+                            final Rect bounds = systemGestureExclusion.getBounds();
+                            if (!mExclusionRect.equals(bounds)) {
+                                mExclusionRect = bounds;
+                                mLogger.i(msg -> "Exclusion rect updated to " + msg.getStr1(),
+                                        msg -> {
+                                            msg.setStr1(bounds.toString());
+                                            return Unit.INSTANCE;
+                                        });
+                            }
                         }
                     };
+                    mLogger.i("Registering system gesture exclusion listener");
                     mWindowManagerService.registerSystemGestureExclusionListener(
                             mGestureExclusionListener, mDisplayId);
                 } catch (RemoteException e) {
@@ -320,11 +331,12 @@
      * Destroys any active {@link InputSession}.
      */
     private void stopMonitoring(boolean force) {
-        mExclusionRect = null;
+        mExclusionRect = new Rect();
         if (bouncerAreaExclusion()) {
             mBackgroundExecutor.execute(() -> {
                 try {
                     if (mGestureExclusionListener != null) {
+                        mLogger.i("Unregistering system gesture exclusion listener");
                         mWindowManagerService.unregisterSystemGestureExclusionListener(
                                 mGestureExclusionListener, mDisplayId);
                         mGestureExclusionListener = null;
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 4e0e112..5ecf2e6b 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
@@ -29,6 +29,7 @@
 import com.android.systemui.communal.shared.model.EditModeState
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
@@ -43,6 +44,7 @@
     val communalSceneInteractor: CommunalSceneInteractor,
     private val communalInteractor: CommunalInteractor,
     val mediaHost: MediaHost,
+    val mediaCarouselController: MediaCarouselController,
 ) {
     val currentScene: Flow<SceneKey> = communalSceneInteractor.currentScene
 
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 ccff230..736ed5c 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
@@ -46,6 +46,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.media.dagger.MediaModule
 import com.android.systemui.res.R
@@ -82,7 +83,14 @@
     private val accessibilityManager: AccessibilityManager,
     private val packageManager: PackageManager,
     @Named(LAUNCHER_PACKAGE) private val launcherPackage: String,
-) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
+    mediaCarouselController: MediaCarouselController,
+) :
+    BaseCommunalViewModel(
+        communalSceneInteractor,
+        communalInteractor,
+        mediaHost,
+        mediaCarouselController,
+    ) {
 
     private val logger = Logger(logBuffer, "CommunalEditModeViewModel")
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index 6239373..3496230 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
@@ -41,6 +42,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
@@ -60,7 +62,7 @@
     glanceableHubToDreamTransitionViewModel: GlanceableHubToDreamingTransitionViewModel,
     communalInteractor: CommunalInteractor,
     private val communalSceneInteractor: CommunalSceneInteractor,
-    keyguardTransitionInteractor: KeyguardTransitionInteractor
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
 ) {
     /**
      * Snaps to [CommunalScenes.Communal], showing the glanceable hub immediately without any
@@ -89,7 +91,7 @@
         keyguardTransitionInteractor
             .transition(
                 edge = Edge.create(from = Scenes.Communal),
-                edgeWithoutSceneContainer = Edge.create(from = KeyguardState.GLANCEABLE_HUB)
+                edgeWithoutSceneContainer = Edge.create(from = KeyguardState.GLANCEABLE_HUB),
             )
             .filter {
                 it.to == KeyguardState.OCCLUDED &&
@@ -114,21 +116,24 @@
                     // from
                     // the hub, then pressing power twice to go back to the lock screen.
                     communalSceneInteractor.isCommunalVisible,
-                    merge(
-                            lockscreenToGlanceableHubTransitionViewModel.showUmo,
-                            glanceableHubToLockscreenTransitionViewModel.showUmo,
-                            dreamToGlanceableHubTransitionViewModel.showUmo,
-                            glanceableHubToDreamTransitionViewModel.showUmo,
-                            showUmoFromOccludedToGlanceableHub,
-                            showUmoFromGlanceableHubToOccluded,
-                        )
-                        .onStart { emit(false) }
-                )
+                    // TODO(b/378942852): polish UMO transitions when scene container is enabled
+                    if (SceneContainerFlag.isEnabled) flowOf(true)
+                    else
+                        merge(
+                                lockscreenToGlanceableHubTransitionViewModel.showUmo,
+                                glanceableHubToLockscreenTransitionViewModel.showUmo,
+                                dreamToGlanceableHubTransitionViewModel.showUmo,
+                                glanceableHubToDreamTransitionViewModel.showUmo,
+                                showUmoFromOccludedToGlanceableHub,
+                                showUmoFromGlanceableHubToOccluded,
+                            )
+                            .onStart { emit(false) },
+                ),
             )
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = false
+                initialValue = false,
             )
 
     /** Whether to show communal when exiting the occluded state. */
@@ -145,8 +150,7 @@
     val recentsBackgroundColor: Flow<Color?> =
         combine(showCommunalFromOccluded, communalColors.backgroundColor) {
             showCommunalFromOccluded,
-            backgroundColor,
-            ->
+            backgroundColor ->
             if (showCommunalFromOccluded) {
                 backgroundColor
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 7990450..9cd6465 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -21,6 +21,7 @@
 import android.os.Bundle
 import android.view.View
 import android.view.accessibility.AccessibilityNodeInfo
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -38,6 +39,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.media.controls.ui.view.MediaHostState
@@ -72,7 +74,6 @@
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** The default view model used for showing the communal hub. */
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -95,7 +96,14 @@
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
     @CommunalLog logBuffer: LogBuffer,
     private val metricsLogger: CommunalMetricsLogger,
-) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
+    mediaCarouselController: MediaCarouselController,
+) :
+    BaseCommunalViewModel(
+        communalSceneInteractor,
+        communalInteractor,
+        mediaHost,
+        mediaCarouselController,
+    ) {
 
     private val logger = Logger(logBuffer, "CommunalViewModel")
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2ee9ddb..01ec4d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2875,7 +2875,7 @@
             }
 
             if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
-                mKeyguardTransitions.startKeyguardTransition(showing, aodShowing);
+                startKeyguardTransition(showing, aodShowing);
             } else {
                 try {
 
@@ -3019,7 +3019,7 @@
                 final int keyguardFlag = flags;
                 mUiBgExecutor.execute(() -> {
                     if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
-                        mKeyguardTransitions.startKeyguardTransition(
+                        startKeyguardTransition(
                                 false /* keyguardShowing */, false /* aodShowing */);
                         return;
                     }
@@ -3035,6 +3035,10 @@
         }
     };
 
+    private void startKeyguardTransition(boolean keyguardShowing, boolean aodShowing) {
+        mKeyguardTransitions.startKeyguardTransition(keyguardShowing, aodShowing);
+    }
+
     private final Runnable mHideAnimationFinishedRunnable = () -> {
         Log.e(TAG, "mHideAnimationFinishedRunnable#run");
         mHideAnimationRunning = false;
@@ -3490,8 +3494,7 @@
         mSurfaceBehindRemoteAnimationRequested = true;
 
         if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
-            mKeyguardTransitions.startKeyguardTransition(
-                    false /* keyguardShowing */, false /* aodShowing */);
+            startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */);
             return;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 2914cb9..a137d6c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -122,10 +122,7 @@
 
         if (visible) {
             if (enableNewKeyguardShellTransitions) {
-                keyguardTransitions.startKeyguardTransition(
-                    false /* keyguardShowing */,
-                    false, /* aodShowing */
-                )
+                startKeyguardTransition(false, /* keyguardShowing */ false /* aodShowing */)
                 isKeyguardGoingAway = true
                 return
             }
@@ -233,7 +230,7 @@
                 "aodVisible=$aodVisible).",
         )
         if (enableNewKeyguardShellTransitions) {
-            keyguardTransitions.startKeyguardTransition(lockscreenShowing, aodVisible)
+            startKeyguardTransition(lockscreenShowing, aodVisible)
         } else {
             activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible)
         }
@@ -241,6 +238,10 @@
         this.isAodVisible = aodVisible
     }
 
+    private fun startKeyguardTransition(keyguardShowing: Boolean, aodShowing: Boolean) {
+        keyguardTransitions.startKeyguardTransition(keyguardShowing, aodShowing)
+    }
+
     private fun endKeyguardGoingAwayAnimation() {
         if (!isKeyguardGoingAway) {
             Log.d(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 914fdd2..6c03b24 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -151,7 +151,5 @@
         cs.applyTo(rootView)
     }
 
-    private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
-    private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
     private const val TAG = "KeyguardPreviewClockViewBinder"
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index 08c3f15..4c23adf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -29,6 +29,7 @@
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.customization.R as customR
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -114,14 +115,14 @@
                 START,
                 PARENT_ID,
                 START,
-                context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
+                context.resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal),
             )
             connect(
                 nicId,
                 END,
                 PARENT_ID,
                 END,
-                context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
+                context.resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal),
             )
             constrainHeight(
                 nicId,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index c009720..6c98d5b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -221,7 +221,9 @@
                 PARENT_ID,
                 START,
                 context.resources.getDimensionPixelSize(customR.dimen.clock_padding_start) +
-                    context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
+                    context.resources.getDimensionPixelSize(
+                        customR.dimen.status_view_margin_horizontal
+                    ),
             )
             val smallClockTopMargin = keyguardClockViewModel.getSmallClockTopMargin()
             create(R.id.small_clock_guideline_top, ConstraintSet.HORIZONTAL_GUIDELINE)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
index 0782846..f0d21f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
@@ -28,6 +28,7 @@
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.systemui.customization.R as customR
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
@@ -42,7 +43,7 @@
 constructor(
     @ShadeDisplayAware private val context: Context,
     private val notificationPanelView: NotificationPanelView,
-    private val keyguardMediaController: KeyguardMediaController
+    private val keyguardMediaController: KeyguardMediaController,
 ) : KeyguardSection() {
     private val mediaContainerId = R.id.status_view_media_container
 
@@ -62,7 +63,7 @@
                 val horizontalPadding =
                     padding +
                         context.resources.getDimensionPixelSize(
-                            R.dimen.status_view_margin_horizontal
+                            customR.dimen.status_view_margin_horizontal
                         )
 
                 setPaddingRelative(horizontalPadding, padding, horizontalPadding, padding)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index e30ddc6..de0927e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.content.Context
+import com.android.systemui.customization.R as customR
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
@@ -59,10 +60,9 @@
 
     /** Whether the weather area should be visible. */
     val isWeatherVisible: StateFlow<Boolean> =
-        combine(
+        combine(isWeatherEnabled, keyguardClockViewModel.hasCustomWeatherDataDisplay) {
                 isWeatherEnabled,
-                keyguardClockViewModel.hasCustomWeatherDataDisplay,
-            ) { isWeatherEnabled, clockIncludesCustomWeatherDisplay ->
+                clockIncludesCustomWeatherDisplay ->
                 isWeatherVisible(
                     clockIncludesCustomWeatherDisplay = clockIncludesCustomWeatherDisplay,
                     isWeatherEnabled = isWeatherEnabled,
@@ -76,7 +76,7 @@
                         clockIncludesCustomWeatherDisplay =
                             keyguardClockViewModel.hasCustomWeatherDataDisplay.value,
                         isWeatherEnabled = smartspaceInteractor.isWeatherEnabled.value,
-                    )
+                    ),
             )
 
     private fun isWeatherVisible(
@@ -92,12 +92,12 @@
     companion object {
         fun getSmartspaceStartMargin(context: Context): Int {
             return context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) +
-                context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+                context.resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal)
         }
 
         fun getSmartspaceEndMargin(context: Context): Int {
             return context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end) +
-                context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+                context.resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal)
         }
     }
 }
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 0e09ad2..dbad602 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.AnimatedVectorDrawable
 import android.graphics.drawable.Drawable
 import android.text.TextUtils
 import androidx.compose.animation.animateColorAsState
@@ -228,7 +229,14 @@
                         }
                     }
                 }
-                is Icon.Loaded -> rememberDrawablePainter(loadedDrawable)
+                is Icon.Loaded -> {
+                    LaunchedEffect(loadedDrawable) {
+                        if (loadedDrawable is AnimatedVectorDrawable) {
+                            loadedDrawable.forceAnimationOnUI()
+                        }
+                    }
+                    rememberDrawablePainter(loadedDrawable)
+                }
             }
 
         Image(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 0e82bf8..4ccd2b9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3109,12 +3109,18 @@
         if (isTracking()) {
             onTrackingStopped(true);
         }
-        if (isExpanded() && !mQsController.getExpanded()) {
+        if (isExpanded() && mBarState != KEYGUARD && !mQsController.getExpanded()) {
             mShadeLog.d("Status Bar was long pressed. Expanding to QS.");
             expandToQs();
         } else {
-            mShadeLog.d("Status Bar was long pressed. Expanding to Notifications.");
-            expandToNotifications();
+            if (mBarState == KEYGUARD) {
+                mShadeLog.d("Lockscreen Status Bar was long pressed. Expanding to Notifications.");
+                mLockscreenShadeTransitionController.goToLockedShade(
+                        /* expandedView= */null, /* needsQSAnimation= */false);
+            } else {
+                mShadeLog.d("Status Bar was long pressed. Expanding to Notifications.");
+                expandToNotifications();
+            }
         }
     }
 
@@ -5091,13 +5097,6 @@
             }
             boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
 
-            if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
-                    event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
-                if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
-                    mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
-                }
-                return true;
-            }
             // This touch session has already resulted in shade expansion. Ignore everything else.
             if (ShadeExpandsOnStatusBarLongPress.isEnabled()
                     && event.getActionMasked() != MotionEvent.ACTION_DOWN
@@ -5105,6 +5104,13 @@
                 mShadeLog.d("Touch has same down time as Status Bar long press. Ignoring.");
                 return false;
             }
+            if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
+                    event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
+                if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
+                    mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
+                }
+                return true;
+            }
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
                 mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
                 handled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 7a18d7c..207439e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -28,6 +28,7 @@
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import androidx.lifecycle.lifecycleScope
 import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.customization.R as customR
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.fragments.FragmentService
@@ -314,7 +315,7 @@
 
     private fun setKeyguardStatusViewConstraints(constraintSet: ConstraintSet) {
         val statusViewMarginHorizontal =
-            resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+            resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal)
         constraintSet.apply {
             setMargin(R.id.keyguard_status_view, START, statusViewMarginHorizontal)
             setMargin(R.id.keyguard_status_view, END, statusViewMarginHorizontal)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index e15830e..fed4a26 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -30,8 +30,8 @@
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.ShadePositionRepository
-import com.android.systemui.shade.data.repository.ShadePositionRepositoryImpl
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepositoryImpl
 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
 import com.android.systemui.statusbar.phone.ConfigurationForwarder
@@ -157,16 +157,16 @@
 
     @SysUISingleton
     @Provides
-    fun provideShadePositionRepository(impl: ShadePositionRepositoryImpl): ShadePositionRepository {
+    fun provideShadePositionRepository(impl: ShadeDisplaysRepositoryImpl): ShadeDisplaysRepository {
         ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
         return impl
     }
 
     @Provides
     @IntoMap
-    @ClassKey(ShadePositionRepositoryImpl::class)
+    @ClassKey(ShadeDisplaysRepositoryImpl::class)
     fun provideShadePositionRepositoryAsCoreStartable(
-        impl: ShadePositionRepositoryImpl
+        impl: ShadeDisplaysRepositoryImpl
     ): CoreStartable {
         return if (ShadeWindowGoesAround.isEnabled) {
             impl
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
index 802fc0e..506b4e9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
@@ -17,11 +17,11 @@
 package com.android.systemui.shade
 
 import android.view.Display
-import com.android.systemui.shade.data.repository.ShadePositionRepository
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
 import com.android.systemui.statusbar.commandline.Command
 import java.io.PrintWriter
 
-class ShadePrimaryDisplayCommand(private val positionRepository: ShadePositionRepository) :
+class ShadePrimaryDisplayCommand(private val positionRepository: ShadeDisplaysRepository) :
     Command {
 
     override fun execute(pw: PrintWriter, args: List<String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
rename to packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
index 6fb3ca5..ae36e81 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
@@ -25,7 +25,7 @@
 
 /** Accepts touch events, detects long press, and calls ShadeViewController#onStatusBarLongPress. */
 @SysUISingleton
-class LongPressGestureDetector
+class StatusBarLongPressGestureDetector
 @Inject
 constructor(context: Context, val shadeViewController: ShadeViewController) {
     val gestureDetector =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt
rename to packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
index 37210b9..71c5658 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
@@ -20,7 +20,7 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 
-class FakeShadePositionRepository : ShadePositionRepository {
+class FakeShadeDisplayRepository : ShadeDisplaysRepository {
     private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
 
     override fun setDisplayId(displayId: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt
rename to packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
index 24c067a..e920aba 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
@@ -25,7 +25,7 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 
-interface ShadePositionRepository {
+interface ShadeDisplaysRepository {
     /** ID of the display which currently hosts the shade */
     val displayId: StateFlow<Int>
 
@@ -41,9 +41,9 @@
 
 /** Source of truth for the display currently holding the shade. */
 @SysUISingleton
-class ShadePositionRepositoryImpl
+class ShadeDisplaysRepositoryImpl
 @Inject
-constructor(private val commandRegistry: CommandRegistry) : ShadePositionRepository, CoreStartable {
+constructor(private val commandRegistry: CommandRegistry) : ShadeDisplaysRepository, CoreStartable {
     private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
 
     override val displayId: StateFlow<Int>
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
index 4e7898d..1055dcb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shade.ShadeWindowLayoutParams
-import com.android.systemui.shade.data.repository.ShadePositionRepository
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
 import com.android.systemui.statusbar.phone.ConfigurationForwarder
 import javax.inject.Inject
@@ -38,13 +38,13 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.withContext
 
-/** Handles Shade window display change when [ShadePositionRepository.displayId] changes. */
+/** Handles Shade window display change when [ShadeDisplaysRepository.displayId] changes. */
 @SysUISingleton
 class ShadeDisplaysInteractor
 @Inject
 constructor(
     private val shadeRootView: WindowRootView,
-    private val shadePositionRepository: ShadePositionRepository,
+    private val shadePositionRepository: ShadeDisplaysRepository,
     @ShadeDisplayAware private val shadeContext: Context,
     private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
     @Background private val bgScope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 33f0c64..6bec86a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -177,8 +177,6 @@
     private float[] mMatrix;
     private ColorMatrixColorFilter mMatrixColorFilter;
     private Runnable mLayoutRunnable;
-    private boolean mDismissed;
-    private Runnable mOnDismissListener;
     private boolean mIncreasedSize;
     private boolean mShowsConversation;
     private float mDozeAmount;
@@ -956,21 +954,6 @@
         mLayoutRunnable = runnable;
     }
 
-    public void setDismissed() {
-        mDismissed = true;
-        if (mOnDismissListener != null) {
-            mOnDismissListener.run();
-        }
-    }
-
-    public boolean isDismissed() {
-        return mDismissed;
-    }
-
-    public void setOnDismissListener(Runnable onDismissListener) {
-        mOnDismissListener = onDismissListener;
-    }
-
     @Override
     public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
         int areaTint = getTint(areas, this, tint);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 08d177f..d1de6be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1543,7 +1543,6 @@
         setDragController(null);
         mGroupParentWhenDismissed = mNotificationParent;
         mChildAfterViewWhenDismissed = null;
-        mEntry.getIcons().getStatusBarIcon().setDismissed();
         if (isChildInGroup()) {
             List<ExpandableNotificationRow> notificationChildren =
                     mNotificationParent.getAttachedChildren();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index db29493..80c8e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -171,12 +171,14 @@
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionListener;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeLogger;
 import com.android.systemui.shade.ShadeSurface;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.statusbar.phone.BarTransitions;
 import com.android.systemui.statusbar.AutoHideUiElement;
@@ -366,6 +368,7 @@
 
     private PhoneStatusBarViewController mPhoneStatusBarViewController;
     private PhoneStatusBarTransitions mStatusBarTransitions;
+    private final Provider<StatusBarLongPressGestureDetector> mStatusBarLongPressGestureDetector;
     private final AuthRippleController mAuthRippleController;
     @WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -671,6 +674,7 @@
             ShadeController shadeController,
             WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            Provider<StatusBarLongPressGestureDetector> statusBarLongPressGestureDetector,
             ViewMediatorCallback viewMediatorCallback,
             InitController initController,
             @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
@@ -778,6 +782,7 @@
         mShadeController = shadeController;
         mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
         mKeyguardViewMediatorCallback = viewMediatorCallback;
         mInitController = initController;
         mPluginDependencyProvider = pluginDependencyProvider;
@@ -1527,6 +1532,11 @@
                 // to touch outside the customizer to close it, such as on the status or nav bar.
                 mShadeController.onStatusBarTouch(event);
             }
+            if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+                    && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+                mStatusBarLongPressGestureDetector.get().handleTouch(event);
+            }
+
             return getNotificationShadeWindowView().onTouchEvent(event);
         };
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 91c43dd..176dd8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -39,8 +39,8 @@
 import com.android.systemui.Flags;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.res.R;
-import com.android.systemui.shade.LongPressGestureDetector;
 import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
 import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
@@ -69,7 +69,7 @@
     private InsetsFetcher mInsetsFetcher;
     private int mDensity;
     private float mFontScale;
-    private LongPressGestureDetector mLongPressGestureDetector;
+    private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
 
     /**
      * Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -81,9 +81,10 @@
         mStatusBarWindowControllerStore = Dependency.get(StatusBarWindowControllerStore.class);
     }
 
-    void setLongPressGestureDetector(LongPressGestureDetector longPressGestureDetector) {
+    void setLongPressGestureDetector(
+            StatusBarLongPressGestureDetector statusBarLongPressGestureDetector) {
         if (ShadeExpandsOnStatusBarLongPress.isEnabled()) {
-            mLongPressGestureDetector = longPressGestureDetector;
+            mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
         }
     }
 
@@ -207,8 +208,9 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        if (ShadeExpandsOnStatusBarLongPress.isEnabled() && mLongPressGestureDetector != null) {
-            mLongPressGestureDetector.handleTouch(event);
+        if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+                && mStatusBarLongPressGestureDetector != null) {
+            mStatusBarLongPressGestureDetector.handleTouch(event);
         }
         if (mTouchEventHandler == null) {
             Log.w(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index a94db49..16e023c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -34,11 +34,11 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
 import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
@@ -69,7 +69,7 @@
     private val shadeController: ShadeController,
     private val shadeViewController: ShadeViewController,
     private val panelExpansionInteractor: PanelExpansionInteractor,
-    private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+    private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
     private val windowRootView: Provider<WindowRootView>,
     private val shadeLogger: ShadeLogger,
     private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
@@ -119,7 +119,7 @@
         addCursorSupportToIconContainers()
 
         if (ShadeExpandsOnStatusBarLongPress.isEnabled) {
-            mView.setLongPressGestureDetector(longPressGestureDetector.get())
+            mView.setLongPressGestureDetector(statusBarLongPressGestureDetector.get())
         }
 
         progressProvider?.setReadyToHandleTransition(true)
@@ -336,7 +336,7 @@
         private val shadeController: ShadeController,
         private val shadeViewController: ShadeViewController,
         private val panelExpansionInteractor: PanelExpansionInteractor,
-        private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+        private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
         private val windowRootView: Provider<WindowRootView>,
         private val shadeLogger: ShadeLogger,
         private val viewUtil: ViewUtil,
@@ -361,7 +361,7 @@
                 shadeController,
                 shadeViewController,
                 panelExpansionInteractor,
-                longPressGestureDetector,
+                statusBarLongPressGestureDetector,
                 windowRootView,
                 shadeLogger,
                 statusBarMoveFromCenterAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index a1b56d6..a72c83e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -92,8 +92,7 @@
 
     private var wifiPickerTracker: WifiPickerTracker? = null
 
-    @VisibleForTesting
-    val selectedUserContext: Flow<Context> =
+    private val selectedUserContext: Flow<Context> =
         userRepository.selectedUserInfo.map {
             applicationContext.createContextAsUser(UserHandle.of(it.id), /* flags= */ 0)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
index 584cd3b..1e043ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
@@ -36,6 +36,9 @@
     /** Adds the status bar view to the window manager. */
     fun attach()
 
+    /** Called when work should stop and resources should be released. */
+    fun stop()
+
     /** Adds the given view to the status bar window view. */
     fun addViewToWindow(view: View, layoutParams: ViewGroup.LayoutParams)
 
@@ -78,7 +81,7 @@
      */
     fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean)
 
-    interface Factory {
+    fun interface Factory {
         fun create(
             context: Context,
             viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
index 6953bbf..811a2ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
@@ -51,10 +51,12 @@
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.animation.DelegateTransitionAnimatorController;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
+import com.android.systemui.statusbar.core.StatusBarRootModernization;
 import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController;
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
 import com.android.systemui.statusbar.window.StatusBarWindowModule.InternalWindowViewInflater;
@@ -66,6 +68,7 @@
 import dagger.assisted.AssistedInject;
 
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 /**
  * Encapsulates all logic for the status bar window state management.
@@ -79,6 +82,7 @@
     private final StatusBarConfigurationController mStatusBarConfigurationController;
     private final IWindowManager mIWindowManager;
     private final StatusBarContentInsetsProvider mContentInsetsProvider;
+    private final Executor mMainExecutor;
     private int mBarHeight = -1;
     private final State mCurrentState = new State();
     private boolean mIsAttached;
@@ -101,12 +105,14 @@
             IWindowManager iWindowManager,
             @Assisted StatusBarContentInsetsProvider contentInsetsProvider,
             FragmentService fragmentService,
-            Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) {
+            Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider,
+            @Main Executor mainExecutor) {
         mContext = context;
         mWindowManager = viewCaptureAwareWindowManager;
         mStatusBarConfigurationController = statusBarConfigurationController;
         mIWindowManager = iWindowManager;
         mContentInsetsProvider = contentInsetsProvider;
+        mMainExecutor = mainExecutor;
         mStatusBarWindowView = statusBarWindowViewInflater.inflate(context);
         mFragmentService = fragmentService;
         mLaunchAnimationContainer = mStatusBarWindowView.findViewById(
@@ -167,6 +173,19 @@
     }
 
     @Override
+    public void stop() {
+        StatusBarConnectedDisplays.assertInNewMode();
+
+        mWindowManager.removeView(mStatusBarWindowView);
+
+        if (StatusBarRootModernization.isEnabled()) {
+            return;
+        }
+        // Fragment transactions need to happen on the main thread.
+        mMainExecutor.execute(() -> mFragmentService.removeAndDestroy(mStatusBarWindowView));
+    }
+
+    @Override
     public void addViewToWindow(@NonNull View view, @NonNull ViewGroup.LayoutParams layoutParams) {
         mStatusBarWindowView.addView(view, layoutParams);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
index 051d463..7403161 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
@@ -70,6 +70,10 @@
         )
     }
 
+    override suspend fun onDisplayRemovalAction(instance: StatusBarWindowController) {
+        instance.stop()
+    }
+
     override val instanceClass = StatusBarWindowController::class.java
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index 6816d35..c4b028d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -49,7 +49,7 @@
             val drawerContainer = requireViewById<View>(R.id.volume_drawer_container)
             val selectedButtonView =
                 requireViewById<ImageButton>(R.id.volume_new_ringer_active_button)
-            val volumeDialogView = requireViewById<ViewGroup>(R.id.volume_dialog)
+            val volumeDialogBackgroundView = requireViewById<View>(R.id.volume_dialog_background)
             repeatWhenAttached {
                 viewModel(
                     traceName = "VolumeDialogRingerViewBinder",
@@ -71,19 +71,17 @@
                                         is RingerDrawerState.Initial -> {
                                             drawerContainer.visibility = View.GONE
                                             selectedButtonView.visibility = View.VISIBLE
-                                            volumeDialogView.setBackgroundResource(
+                                            volumeDialogBackgroundView.setBackgroundResource(
                                                 R.drawable.volume_dialog_background
                                             )
                                         }
-
                                         is RingerDrawerState.Closed -> {
                                             drawerContainer.visibility = View.GONE
                                             selectedButtonView.visibility = View.VISIBLE
-                                            volumeDialogView.setBackgroundResource(
+                                            volumeDialogBackgroundView.setBackgroundResource(
                                                 R.drawable.volume_dialog_background
                                             )
                                         }
-
                                         is RingerDrawerState.Open -> {
                                             drawerContainer.visibility = View.VISIBLE
                                             selectedButtonView.visibility = View.GONE
@@ -91,17 +89,16 @@
                                                 uiModel.currentButtonIndex !=
                                                     uiModel.availableButtons.size - 1
                                             ) {
-                                                volumeDialogView.setBackgroundResource(
+                                                volumeDialogBackgroundView.setBackgroundResource(
                                                     R.drawable.volume_dialog_background_small_radius
                                                 )
                                             }
                                         }
                                     }
                                 }
-
                                 is RingerViewModelState.Unavailable -> {
                                     drawerAndRingerContainer.visibility = View.GONE
-                                    volumeDialogView.setBackgroundResource(
+                                    volumeDialogBackgroundView.setBackgroundResource(
                                         R.drawable.volume_dialog_background
                                     )
                                 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index a17c1e5..9078f82 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -38,9 +38,10 @@
 
     fun bind(view: View) {
         with(view) {
-            val volumeDialog: View = requireViewById(R.id.volume_dialog)
             val floatingSlidersContainer: ViewGroup =
                 requireViewById(R.id.volume_dialog_floating_sliders_container)
+            val mainSliderContainer: View =
+                requireViewById(R.id.volume_dialog_main_slider_container)
             repeatWhenAttached {
                 viewModel(
                     traceName = "VolumeDialogSlidersViewBinder",
@@ -49,7 +50,7 @@
                 ) { viewModel ->
                     viewModel.sliders
                         .onEach { uiModel ->
-                            uiModel.sliderComponent.sliderViewBinder().bind(volumeDialog)
+                            uiModel.sliderComponent.sliderViewBinder().bind(mainSliderContainer)
 
                             val floatingSliderViewBinders = uiModel.floatingSliderComponent
                             floatingSlidersContainer.ensureChildCount(
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 d9a945c..f6c1743 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
@@ -19,13 +19,11 @@
 import android.app.Dialog
 import android.graphics.Rect
 import android.graphics.Region
-import android.view.Gravity
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
 import android.view.ViewTreeObserver.InternalInsetsInfo
-import android.widget.FrameLayout
-import androidx.annotation.GravityInt
+import androidx.constraintlayout.motion.widget.MotionLayout
 import com.android.internal.view.RotationPolicy
 import com.android.systemui.lifecycle.WindowLifecycleState
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -41,7 +39,6 @@
 import com.android.systemui.volume.dialog.ui.VolumeDialogResources
 import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
 import com.android.systemui.volume.dialog.ui.utils.suspendAnimate
-import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogGravityViewModel
 import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogViewModel
 import com.android.systemui.volume.dialog.utils.VolumeTracer
 import javax.inject.Inject
@@ -53,6 +50,7 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.scan
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.suspendCancellableCoroutine
 
@@ -63,7 +61,6 @@
 @Inject
 constructor(
     private val volumeResources: VolumeDialogResources,
-    private val gravityViewModel: VolumeDialogGravityViewModel,
     private val dialogViewModelFactory: VolumeDialogViewModel.Factory,
     private val jankListenerFactory: JankListenerFactory,
     private val tracer: VolumeTracer,
@@ -74,21 +71,23 @@
 
     fun bind(dialog: Dialog) {
         // 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 {
+        val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog_root)
+        root.alpha = 0f
+        root.repeatWhenAttached {
             root.viewModel(
                 traceName = "VolumeDialogViewBinder",
                 minWindowLifecycleState = WindowLifecycleState.ATTACHED,
                 factory = { dialogViewModelFactory.create() },
             ) { viewModel ->
-                animateVisibility(container, dialog, viewModel.dialogVisibilityModel)
+                animateVisibility(root, dialog, viewModel.dialogVisibilityModel)
 
                 viewModel.dialogTitle.onEach { dialog.window?.setTitle(it) }.launchIn(this)
-                gravityViewModel.dialogGravity
-                    .onEach { container.setLayoutGravity(it) }
+                viewModel.motionState
+                    .scan(0) { acc, motionState ->
+                        // don't animate the initial state
+                        root.transitionToState(motionState, animate = acc != 0)
+                        acc + 1
+                    }
                     .launchIn(this)
 
                 launch { root.viewTreeObserver.computeInternalInsetsListener(root) }
@@ -130,15 +129,13 @@
             .launchIn(this)
     }
 
-    private suspend fun calculateTranslationX(view: View): Float? {
+    private fun calculateTranslationX(view: View): Float? {
         return if (view.display.rotation == RotationPolicy.NATURAL_ROTATION) {
-            val dialogGravity = gravityViewModel.dialogGravity.first()
-            val isGravityLeft = (dialogGravity and Gravity.LEFT) == Gravity.LEFT
-            if (isGravityLeft) {
+            if (view.isLayoutRtl) {
                 -1
             } else {
                 1
-            } * view.width / 2.0f
+            } * view.width / 2f
         } else {
             null
         }
@@ -211,10 +208,11 @@
         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 }
+    private fun MotionLayout.transitionToState(newState: Int, animate: Boolean) {
+        if (animate) {
+            transitionToState(newState)
+        } else {
+            jumpToState(newState)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt
deleted file mode 100644
index 112afb1..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.dialog.ui.viewmodel
-
-import android.content.Context
-import android.content.res.Configuration
-import android.view.Gravity
-import androidx.annotation.GravityInt
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.UiBackground
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.DevicePostureController
-import com.android.systemui.statusbar.policy.devicePosture
-import com.android.systemui.statusbar.policy.onConfigChanged
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
-import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.withContext
-
-/** Exposes dialog [GravityInt] for use in the UI layer. */
-@VolumeDialogScope
-class VolumeDialogGravityViewModel
-@Inject
-constructor(
-    @Application private val context: Context,
-    @VolumeDialog private val coroutineScope: CoroutineScope,
-    @UiBackground private val uiBackgroundCoroutineContext: CoroutineContext,
-    configurationController: ConfigurationController,
-    private val devicePostureController: DevicePostureController,
-) {
-
-    @GravityInt private var originalGravity: Int = context.getAbsoluteGravity()
-
-    val dialogGravity: Flow<Int> =
-        combine(
-                devicePostureController.devicePosture(),
-                configurationController.onConfigChanged.onEach { onConfigurationChanged() },
-            ) { devicePosture, configuration ->
-                context.calculateGravity(devicePosture, configuration)
-            }
-            .stateIn(
-                scope = coroutineScope,
-                started = SharingStarted.Eagerly,
-                context.calculateGravity(),
-            )
-
-    private suspend fun onConfigurationChanged() {
-        withContext(uiBackgroundCoroutineContext) { originalGravity = context.getAbsoluteGravity() }
-    }
-
-    @GravityInt
-    private fun Context.calculateGravity(
-        devicePosture: Int = devicePostureController.devicePosture,
-        config: Configuration = resources.configuration,
-    ): Int {
-        val isLandscape = config.orientation == Configuration.ORIENTATION_LANDSCAPE
-        val isHalfOpen = devicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED
-        val gravity =
-            if (isLandscape && isHalfOpen) {
-                originalGravity or Gravity.TOP
-            } else {
-                originalGravity
-            }
-        return getAbsoluteGravity(gravity)
-    }
-}
-
-@GravityInt
-private fun Context.getAbsoluteGravity(
-    gravity: Int = resources.getInteger(R.integer.volume_dialog_gravity)
-): Int = with(resources) { Gravity.getAbsoluteGravity(gravity, configuration.layoutDirection) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
index 869a6a2..0352799 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
@@ -17,7 +17,12 @@
 package com.android.systemui.volume.dialog.ui.viewmodel
 
 import android.content.Context
+import android.content.res.Configuration
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.devicePosture
+import com.android.systemui.statusbar.policy.onConfigChanged
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
@@ -32,6 +37,7 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 
 /** Provides a state for the Volume Dialog. */
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -42,8 +48,23 @@
     dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
     volumeDialogSlidersInteractor: VolumeDialogSlidersInteractor,
     volumeDialogStateInteractor: VolumeDialogStateInteractor,
+    devicePostureController: DevicePostureController,
+    configurationController: ConfigurationController,
 ) {
 
+    val motionState: Flow<Int> =
+        combine(
+            devicePostureController.devicePosture(),
+            configurationController.onConfigChanged.onStart {
+                emit(context.resources.configuration)
+            },
+        ) { devicePosture, configuration ->
+            if (shouldOffsetVolumeDialog(devicePosture, configuration)) {
+                R.id.volume_dialog_half_folded_constraint_set
+            } else {
+                R.id.volume_dialog_constraint_set
+            }
+        }
     val dialogVisibilityModel: Flow<VolumeDialogVisibilityModel> =
         dialogVisibilityInteractor.dialogVisibility
     val dialogTitle: Flow<String> =
@@ -57,6 +78,13 @@
             }
             .filterNotNull()
 
+    /** @return true when the foldable device screen curve is in the way of the volume dialog */
+    private fun shouldOffsetVolumeDialog(devicePosture: Int, config: Configuration): Boolean {
+        val isLandscape = config.orientation == Configuration.ORIENTATION_LANDSCAPE
+        val isHalfOpen = devicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED
+        return isLandscape && isHalfOpen
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(): VolumeDialogViewModel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index a940bc9..425aad2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -16,10 +16,12 @@
 import android.view.RemoteAnimationTarget
 import android.view.SurfaceControl
 import android.view.ViewGroup
+import android.view.WindowManager.TRANSIT_NONE
 import android.widget.FrameLayout
 import android.widget.LinearLayout
 import android.window.RemoteTransition
 import android.window.TransitionFilter
+import android.window.WindowAnimationState
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -34,6 +36,10 @@
 import junit.framework.AssertionFailedError
 import kotlin.concurrent.thread
 import kotlin.test.assertEquals
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
 import org.junit.After
 import org.junit.Assert.assertThrows
 import org.junit.Before
@@ -258,7 +264,6 @@
     @DisableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
     @Test
     fun doesNotRegisterLongLivedTransitionIfFlagIsDisabled() {
-
         val controller =
             object : DelegateTransitionAnimatorController(controller) {
                 override val transitionCookie =
@@ -273,7 +278,6 @@
     @EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
     @Test
     fun doesNotRegisterLongLivedTransitionIfMissingRequiredProperties() {
-
         // No TransitionCookie
         val controllerWithoutCookie =
             object : DelegateTransitionAnimatorController(controller) {
@@ -348,7 +352,7 @@
     fun doesNotStartIfAnimationIsCancelled() {
         val runner = activityTransitionAnimator.createRunner(controller)
         runner.onAnimationCancelled()
-        runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback)
+        runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback)
 
         waitForIdleSync()
         verify(controller).onTransitionAnimationCancelled()
@@ -361,7 +365,7 @@
     @Test
     fun cancelsIfNoOpeningWindowIsFound() {
         val runner = activityTransitionAnimator.createRunner(controller)
-        runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback)
+        runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback)
 
         waitForIdleSync()
         verify(controller).onTransitionAnimationCancelled()
@@ -374,7 +378,13 @@
     @Test
     fun startsAnimationIfWindowIsOpening() {
         val runner = activityTransitionAnimator.createRunner(controller)
-        runner.onAnimationStart(0, arrayOf(fakeWindow()), emptyArray(), emptyArray(), iCallback)
+        runner.onAnimationStart(
+            TRANSIT_NONE,
+            arrayOf(fakeWindow()),
+            emptyArray(),
+            emptyArray(),
+            iCallback,
+        )
         waitForIdleSync()
         verify(listener).onTransitionAnimationStart()
         verify(controller).onTransitionAnimationStart(anyBoolean())
@@ -387,6 +397,113 @@
         }
     }
 
+    @DisableFlags(
+        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+    )
+    @Test
+    fun creatingRunnerWithLazyInitializationThrows_whenTheFlagsAreDisabled() {
+        assertThrows(IllegalStateException::class.java) {
+            activityTransitionAnimator.createRunner(controller, initializeLazily = true)
+        }
+    }
+
+    @EnableFlags(
+        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+    )
+    @Test
+    fun runnerCreatesDelegateLazily_whenPostingTimeouts() {
+        val runner = activityTransitionAnimator.createRunner(controller, initializeLazily = true)
+        assertNull(runner.delegate)
+        runner.postTimeouts()
+        assertNotNull(runner.delegate)
+    }
+
+    @EnableFlags(
+        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+    )
+    @Test
+    fun runnerCreatesDelegateLazily_onAnimationStart() {
+        val runner = activityTransitionAnimator.createRunner(controller, initializeLazily = true)
+        assertNull(runner.delegate)
+
+        // The delegate is cleaned up after execution (which happens in another thread), so what we
+        // do instead is check if it becomes non-null at any point with a 1 second timeout. This
+        // will tell us that takeOverWithAnimation() triggered the lazy initialization.
+        var delegateInitialized = false
+        runBlocking {
+            val initChecker = launch {
+                withTimeout(1.seconds) {
+                    while (runner.delegate == null) continue
+                    delegateInitialized = true
+                }
+            }
+            runner.onAnimationStart(
+                TRANSIT_NONE,
+                arrayOf(fakeWindow()),
+                emptyArray(),
+                emptyArray(),
+                iCallback,
+            )
+            initChecker.join()
+        }
+        assertTrue(delegateInitialized)
+    }
+
+    @EnableFlags(
+        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+    )
+    @Test
+    fun runnerCreatesDelegateLazily_onAnimationTakeover() {
+        val runner = activityTransitionAnimator.createRunner(controller, initializeLazily = true)
+        assertNull(runner.delegate)
+
+        // The delegate is cleaned up after execution (which happens in another thread), so what we
+        // do instead is check if it becomes non-null at any point with a 1 second timeout. This
+        // will tell us that takeOverWithAnimation() triggered the lazy initialization.
+        var delegateInitialized = false
+        runBlocking {
+            val initChecker = launch {
+                withTimeout(1.seconds) {
+                    while (runner.delegate == null) continue
+                    delegateInitialized = true
+                }
+            }
+            runner.takeOverAnimation(
+                arrayOf(fakeWindow()),
+                arrayOf(WindowAnimationState()),
+                SurfaceControl.Transaction(),
+                iCallback,
+            )
+            initChecker.join()
+        }
+        assertTrue(delegateInitialized)
+    }
+
+    @DisableFlags(
+        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+    )
+    @Test
+    fun animationTakeoverThrows_whenTheFlagsAreDisabled() {
+        val runner = activityTransitionAnimator.createRunner(controller, initializeLazily = false)
+        assertThrows(IllegalStateException::class.java) {
+            runner.takeOverAnimation(
+                arrayOf(fakeWindow()),
+                emptyArray(),
+                SurfaceControl.Transaction(),
+                iCallback,
+            )
+        }
+    }
+
+    @DisableFlags(
+        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+    )
     @Test
     fun disposeRunner_delegateDereferenced() {
         val runner = activityTransitionAnimator.createRunner(controller)
@@ -409,7 +526,7 @@
             false,
             Rect(),
             Rect(),
-            0,
+            1,
             Point(),
             Rect(),
             bounds,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index c9ada7e..b142fc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -155,6 +155,7 @@
 import com.android.systemui.shade.ShadeControllerImpl;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeLogger;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
 import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyboardShortcutListSearch;
@@ -174,7 +175,6 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
 import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
-import com.android.systemui.statusbar.core.StatusBarOrchestrator;
 import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
 import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
@@ -372,7 +372,7 @@
     @Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
     @Mock private NotificationSettingsInteractor mNotificationSettingsInteractor;
     @Mock private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
-    @Mock private StatusBarOrchestrator mStatusBarOrchestrator;
+    @Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
@@ -607,6 +607,7 @@
                 mShadeController,
                 mWindowRootViewVisibilityInteractor,
                 mStatusBarKeyguardViewManager,
+                () -> mStatusBarLongPressGestureDetector,
                 mViewMediatorCallback,
                 mInitController,
                 new Handler(TestableLooper.get(this).getLooper()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 638f195..69efa87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -40,14 +40,13 @@
 import com.android.systemui.plugins.fakeDarkIconDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
 import com.android.systemui.shade.ShadeControllerImpl
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore
-import com.android.systemui.statusbar.data.repository.statusBarContentInsetsProviderStore
 import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -98,7 +97,7 @@
     @Mock private lateinit var windowRootView: Provider<WindowRootView>
     @Mock private lateinit var shadeLogger: ShadeLogger
     @Mock private lateinit var viewUtil: ViewUtil
-    @Mock private lateinit var longPressGestureDetector: LongPressGestureDetector
+    @Mock private lateinit var mStatusBarLongPressGestureDetector: StatusBarLongPressGestureDetector
     private lateinit var statusBarWindowStateController: StatusBarWindowStateController
 
     private lateinit var view: PhoneStatusBarView
@@ -395,7 +394,7 @@
                 shadeControllerImpl,
                 shadeViewController,
                 panelExpansionInteractor,
-                { longPressGestureDetector },
+                { mStatusBarLongPressGestureDetector },
                 windowRootView,
                 shadeLogger,
                 viewUtil,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt
index 528c9d9..a110a49 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt
@@ -27,6 +27,9 @@
     var isAttached = false
         private set
 
+    var isStopped = false
+        private set
+
     override val statusBarHeight: Int = 0
 
     override fun refreshStatusBarHeight() {}
@@ -35,6 +38,10 @@
         isAttached = true
     }
 
+    override fun stop() {
+        isStopped = true
+    }
+
     override fun addViewToWindow(view: View, layoutParams: ViewGroup.LayoutParams) {}
 
     override val backgroundView: View
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
index 173e909..23f2b42 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
@@ -18,7 +18,8 @@
 
 import android.content.testableContext
 import android.view.windowManagerService
-import com.android.app.viewcapture.viewCaptureAwareWindowManager
+import com.android.app.viewcapture.realCaptureAwareWindowManager
+import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.fragments.fragmentService
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.phone.statusBarContentInsetsProvider
@@ -32,12 +33,13 @@
         StatusBarWindowControllerImpl(
             testableContext,
             statusBarWindowViewInflater,
-            viewCaptureAwareWindowManager,
+            realCaptureAwareWindowManager,
             statusBarConfigurationController,
             windowManagerService,
             statusBarContentInsetsProvider,
             fragmentService,
             Optional.empty(),
+            fakeExecutor,
         )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStoreKosmos.kt
new file mode 100644
index 0000000..4941ceb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStoreKosmos.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.window
+
+import android.view.WindowManager
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.app.viewcapture.realCaptureAwareWindowManager
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayWindowPropertiesRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.data.repository.statusBarConfigurationControllerStore
+import com.android.systemui.statusbar.data.repository.statusBarContentInsetsProviderStore
+import org.mockito.kotlin.mock
+
+val Kosmos.multiDisplayStatusBarWindowControllerStore by
+    Kosmos.Fixture {
+        MultiDisplayStatusBarWindowControllerStore(
+            backgroundApplicationScope = applicationCoroutineScope,
+            controllerFactory = { _, _, _, _ -> mock() },
+            displayWindowPropertiesRepository = displayWindowPropertiesRepository,
+            viewCaptureAwareWindowManagerFactory =
+                object : ViewCaptureAwareWindowManager.Factory {
+                    override fun create(
+                        windowManager: WindowManager
+                    ): ViewCaptureAwareWindowManager {
+                        return realCaptureAwareWindowManager
+                    }
+                },
+            statusBarConfigurationControllerStore = statusBarConfigurationControllerStore,
+            statusBarContentInsetsProviderStore = statusBarContentInsetsProviderStore,
+            displayRepository = displayRepository,
+        )
+    }
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index bd46deb..b3fe5f2 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -2,6 +2,13 @@
 container: "system"
 
 flag {
+  name: "autofill_w_metrics"
+  namespace: "autofill"
+  description: "Guards against new metrics definitions introduced in W"
+  bug: "342676602"
+}
+
+flag {
   name: "autofill_credman_integration"
   namespace: "autofill"
   description: "Guards Autofill Framework against Autofill-Credman integration"
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index d53f949..fcb7934 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -60,3 +60,12 @@
     bug: "331749778"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "enable_restricted_mode_changes"
+    namespace: "onboarding"
+    description: "Enables the new framework behavior of not putting apps in restricted mode for "
+            "B&R operations in certain cases."
+    bug: "376661510"
+    is_fixed_read_only: true
+}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 466d477..5de2fb3 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -43,6 +43,7 @@
 import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
 import android.app.AppGlobals;
+import android.app.ApplicationThreadConstants;
 import android.app.IActivityManager;
 import android.app.IBackupAgent;
 import android.app.PendingIntent;
@@ -59,6 +60,9 @@
 import android.app.backup.IFullBackupRestoreObserver;
 import android.app.backup.IRestoreSession;
 import android.app.backup.ISelectBackupTransportCallback;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -298,6 +302,15 @@
     private static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
     private static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
 
+    /**
+     * Enables the OS making a decision on whether backup restricted mode should be used for apps
+     * that haven't explicitly opted in or out. See
+     * {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE} for details.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
+    public static final long OS_DECIDES_BACKUP_RESTRICTED_MODE = 376661510;
+
     // Time delay for initialization operations that can be delayed so as not to consume too much
     // CPU on bring-up and increase time-to-UI.
     private static final long INITIALIZATION_DELAY_MILLIS = 3000;
@@ -352,6 +365,9 @@
     // Backups that we haven't started yet.  Keys are package names.
     private final HashMap<String, BackupRequest> mPendingBackups = new HashMap<>();
 
+    private final ArraySet<String> mRestoreNoRestrictedModePackages = new ArraySet<>();
+    private final ArraySet<String> mBackupNoRestrictedModePackages = new ArraySet<>();
+
     // locking around the pending-backup management
     private final Object mQueueLock = new Object();
 
@@ -523,7 +539,8 @@
     @VisibleForTesting
     UserBackupManagerService(Context context, PackageManager packageManager,
             LifecycleOperationStorage operationStorage, TransportManager transportManager,
-            BackupHandler backupHandler, BackupManagerConstants backupManagerConstants) {
+            BackupHandler backupHandler, BackupManagerConstants backupManagerConstants,
+            IActivityManager activityManager, ActivityManagerInternal activityManagerInternal) {
         mContext = context;
 
         mUserId = 0;
@@ -534,6 +551,8 @@
         mFullBackupQueue = new ArrayList<>();
         mBackupHandler = backupHandler;
         mConstants = backupManagerConstants;
+        mActivityManager = activityManager;
+        mActivityManagerInternal = activityManagerInternal;
 
         mBaseStateDir = null;
         mDataDir = null;
@@ -543,13 +562,11 @@
         mRunInitReceiver = null;
         mRunInitIntent = null;
         mAgentTimeoutParameters = null;
-        mActivityManagerInternal = null;
         mAlarmManager = null;
         mWakelock = null;
         mBackupPreferences = null;
         mBackupPasswordManager = null;
         mPackageManagerBinder = null;
-        mActivityManager = null;
         mBackupManagerBinder = null;
         mScheduledBackupEligibility = null;
     }
@@ -1651,9 +1668,11 @@
         synchronized (mAgentConnectLock) {
             mConnecting = true;
             mConnectedAgent = null;
+            boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(mode,
+                    app.packageName);
             try {
                 if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId,
-                        backupDestination)) {
+                        backupDestination, useRestrictedMode)) {
                     Slog.d(TAG, addUserIdToLogMessage(mUserId, "awaiting agent for " + app));
 
                     // success; wait for the agent to arrive
@@ -3103,6 +3122,91 @@
         }
     }
 
+    /**
+     * Marks the given set of packages as packages that should not be put into restricted mode if
+     * they are started for the given {@link BackupAnnotations.OperationType}.
+     */
+    public void setNoRestrictedModePackages(Set<String> packageNames,
+            @BackupAnnotations.OperationType int opType) {
+        if (opType == BackupAnnotations.OperationType.BACKUP) {
+            mBackupNoRestrictedModePackages.clear();
+            mBackupNoRestrictedModePackages.addAll(packageNames);
+        } else if (opType == BackupAnnotations.OperationType.RESTORE) {
+            mRestoreNoRestrictedModePackages.clear();
+            mRestoreNoRestrictedModePackages.addAll(packageNames);
+        } else {
+            throw new IllegalArgumentException("opType must be BACKUP or RESTORE");
+        }
+    }
+
+    /**
+     * Clears the list of packages that should not be put into restricted mode for either backup or
+     * restore.
+     */
+    public void clearNoRestrictedModePackages() {
+        mBackupNoRestrictedModePackages.clear();
+        mRestoreNoRestrictedModePackages.clear();
+    }
+
+    /**
+     * If the app has specified {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE}, then
+     * its value is returned. If it hasn't and it targets an SDK below
+     * {@link Build.VERSION_CODES#BAKLAVA} then returns true. If it targets a newer SDK, then
+     * returns the decision made by the {@link android.app.backup.BackupTransport}.
+     *
+     * <p>When this method is called, we should have already asked the transport and cached its
+     * response in {@link #mBackupNoRestrictedModePackages} or
+     * {@link #mRestoreNoRestrictedModePackages} so this method will immediately return without
+     * any IPC to the transport.
+     */
+    private boolean shouldUseRestrictedBackupModeForPackage(
+            @BackupAnnotations.OperationType int mode, String packageName) {
+        if (!Flags.enableRestrictedModeChanges()) {
+            return true;
+        }
+
+        // Key/Value apps are never put in restricted mode.
+        if (mode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
+                || mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE) {
+            return false;
+        }
+
+        try {
+            PackageManager.Property property = mPackageManager.getPropertyAsUser(
+                    PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE,
+                    packageName, /* className= */ null,
+                    mUserId);
+            if (property.isBoolean()) {
+                // If the package has explicitly specified, we won't ask the transport.
+                return property.getBoolean();
+            } else {
+                Slog.w(TAG, PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE
+                        + "must be a boolean.");
+            }
+        } catch (NameNotFoundException e) {
+            // This is expected when the package has not defined the property in its manifest.
+        }
+
+        // The package has not specified the property. The behavior depends on the package's
+        // targetSdk.
+        // <36 gets the old behavior of always using restricted mode.
+        if (!CompatChanges.isChangeEnabled(OS_DECIDES_BACKUP_RESTRICTED_MODE, packageName,
+                UserHandle.of(mUserId))) {
+            return true;
+        }
+
+        // Apps targeting >=36 get the behavior decided by the transport.
+        // By this point, we should have asked the transport and cached its decision.
+        if ((mode == ApplicationThreadConstants.BACKUP_MODE_FULL
+                && mBackupNoRestrictedModePackages.contains(packageName))
+                || (mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL
+                && mRestoreNoRestrictedModePackages.contains(packageName))) {
+            Slog.d(TAG, "Transport requested no restricted mode for: " + packageName);
+            return false;
+        }
+        return true;
+    }
+
     private boolean startConfirmationUi(int token, String action) {
         try {
             Intent confIntent = new Intent(action);
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index cca166b..be9cdc8 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -16,6 +16,8 @@
 
 package com.android.server.backup.fullbackup;
 
+import static android.app.backup.BackupAnnotations.OperationType.BACKUP;
+
 import static com.android.server.backup.BackupManagerService.DEBUG;
 import static com.android.server.backup.BackupManagerService.DEBUG_SCHEDULING;
 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
@@ -34,6 +36,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
@@ -388,6 +391,10 @@
                 }
             }
 
+            // We ask the transport which packages should not be put in restricted mode and cache
+            // the result in UBMS to be used later when the apps are started for backup.
+            setNoRestrictedModePackages(transport, mPackages);
+
             // Set up to send data to the transport
             final int N = mPackages.size();
             int chunkSizeInBytes = 8 * 1024; // 8KB
@@ -694,6 +701,9 @@
                 mUserBackupManagerService.scheduleNextFullBackupJob(backoff);
             }
 
+            // Clear this to avoid using the memory until reboot.
+            mUserBackupManagerService.clearNoRestrictedModePackages();
+
             Slog.i(TAG, "Full data backup pass finished.");
             mUserBackupManagerService.getWakelock().release();
         }
@@ -722,6 +732,21 @@
         }
     }
 
+    private void setNoRestrictedModePackages(BackupTransportClient transport,
+            List<PackageInfo> packages) {
+        try {
+            Set<String> packageNames = new ArraySet<>();
+            for (int i = 0; i < packages.size(); i++) {
+                packageNames.add(packages.get(i).packageName);
+            }
+            packageNames = transport.getPackagesThatShouldNotUseRestrictedMode(packageNames,
+                    BACKUP);
+            mUserBackupManagerService.setNoRestrictedModePackages(packageNames, BACKUP);
+        } catch (RemoteException e) {
+            Slog.i(TAG, "Failed to retrieve no restricted mode packages from transport");
+        }
+    }
+
     // Run the backup and pipe it back to the given socket -- expects to run on
     // a standalone thread.  The  runner owns this half of the pipe, and closes
     // it to indicate EOD to the other end.
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index e536876..5ee51a5 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -53,6 +53,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.Slog;
 
@@ -482,6 +483,10 @@
                 return;
             }
 
+            // We ask the transport which packages should not be put in restricted mode and cache
+            // the result in UBMS to be used later when the apps are started for restore.
+            setNoRestrictedModePackages(transport, packages);
+
             RestoreDescription desc = transport.nextRestorePackage();
             if (desc == null) {
                 Slog.e(TAG, "No restore metadata available; halting");
@@ -1358,6 +1363,9 @@
         // Clear any ongoing session timeout.
         backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
 
+        // Clear this to avoid using the memory until reboot.
+        backupManagerService.clearNoRestrictedModePackages();
+
         // If we have a PM token, we must under all circumstances be sure to
         // handshake when we've finished.
         if (mPmToken > 0) {
@@ -1819,4 +1827,20 @@
 
         return packageInfo;
     }
+
+    @VisibleForTesting
+    void setNoRestrictedModePackages(BackupTransportClient transport,
+            PackageInfo[] packages) {
+        try {
+            Set<String> packageNames = new ArraySet<>();
+            for (int i = 0; i < packages.length; i++) {
+                packageNames.add(packages[i].packageName);
+            }
+            packageNames = transport.getPackagesThatShouldNotUseRestrictedMode(packageNames,
+                    RESTORE);
+            backupManagerService.setNoRestrictedModePackages(packageNames, RESTORE);
+        } catch (RemoteException e) {
+            Slog.i(TAG, "Failed to retrieve restricted mode packages from transport");
+        }
+    }
 }
diff --git a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
index daf34152..373811f 100644
--- a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -17,6 +17,7 @@
 package com.android.server.backup.transport;
 
 import android.annotation.Nullable;
+import android.app.backup.BackupAnnotations;
 import android.app.backup.BackupTransport;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.RestoreDescription;
@@ -26,6 +27,7 @@
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.backup.IBackupTransport;
@@ -375,6 +377,26 @@
     }
 
     /**
+     * See
+     * {@link IBackupTransport#getPackagesThatShouldNotUseRestrictedMode(List, int, AndroidFuture)}.
+     */
+    public Set<String> getPackagesThatShouldNotUseRestrictedMode(Set<String> packageNames,
+            @BackupAnnotations.OperationType
+            int operationType) throws RemoteException {
+        AndroidFuture<List<String>> resultFuture = mTransportFutures.newFuture();
+        mTransportBinder.getPackagesThatShouldNotUseRestrictedMode(List.copyOf(packageNames),
+                operationType,
+                resultFuture);
+        List<String> resultList = getFutureResult(resultFuture);
+        Set<String> set = new ArraySet<>();
+        if (resultList == null) {
+            return set;
+        }
+        set.addAll(resultList);
+        return set;
+    }
+
+    /**
      * Allows the {@link TransportConnection} to notify this client
      * if the underlying transport has become unusable.  If that happens
      * we want to cancel all active futures or callbacks.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index dfddc08..d3e5942 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -478,7 +478,6 @@
 
 import dalvik.annotation.optimization.NeverCompile;
 import dalvik.system.VMRuntime;
-
 import libcore.util.EmptyArray;
 
 import java.io.File;
@@ -4493,16 +4492,11 @@
                 Slog.w(TAG, "Unattached app died before backup, skipping");
                 final int userId = app.userId;
                 final String packageName = app.info.packageName;
-                mHandler.post(new Runnable() {
-                @Override
-                    public void run() {
-                        try {
-                            IBackupManager bm = IBackupManager.Stub.asInterface(
-                                    ServiceManager.getService(Context.BACKUP_SERVICE));
-                            bm.agentDisconnectedForUser(userId, packageName);
-                        } catch (RemoteException e) {
-                            // Can't happen; the backup manager is local
-                        }
+                mHandler.post(() -> {
+                    try {
+                        getBackupManager().agentDisconnectedForUser(userId, packageName);
+                    } catch (RemoteException e) {
+                        // Can't happen; the backup manager is local
                     }
                 });
             }
@@ -4673,7 +4667,8 @@
             if (backupTarget != null && backupTarget.appInfo.packageName.equals(processName)) {
                 isRestrictedBackupMode = backupTarget.appInfo.uid >= FIRST_APPLICATION_UID
                         && ((backupTarget.backupMode == BackupRecord.RESTORE_FULL)
-                                || (backupTarget.backupMode == BackupRecord.BACKUP_FULL));
+                        || (backupTarget.backupMode == BackupRecord.BACKUP_FULL))
+                        && backupTarget.useRestrictedMode;
             }
 
             final ActiveInstrumentation instr = app.getActiveInstrumentation();
@@ -13499,16 +13494,11 @@
         if (backupTarget != null && pid == backupTarget.app.getPid()) {
             if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG_CLEANUP, "App "
                     + backupTarget.appInfo + " died during backup");
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        IBackupManager bm = IBackupManager.Stub.asInterface(
-                                ServiceManager.getService(Context.BACKUP_SERVICE));
-                        bm.agentDisconnectedForUser(app.userId, app.info.packageName);
-                    } catch (RemoteException e) {
-                        // can't happen; backup manager is local
-                    }
+            mHandler.post(() -> {
+                try {
+                    getBackupManager().agentDisconnectedForUser(app.userId, app.info.packageName);
+                } catch (RemoteException e) {
+                    // can't happen; backup manager is local
                 }
             });
         }
@@ -14011,7 +14001,7 @@
     // instantiated.  The backup agent will invoke backupAgentCreated() on the
     // activity manager to announce its creation.
     public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId,
-            @BackupDestination int backupDestination) {
+            @BackupDestination int backupDestination, boolean useRestrictedMode) {
         long startTimeNs = SystemClock.uptimeNanos();
         if (DEBUG_BACKUP) {
             Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode
@@ -14096,7 +14086,8 @@
                         + app.packageName + ": " + e);
             }
 
-            BackupRecord r = new BackupRecord(app, backupMode, targetUserId, backupDestination);
+            BackupRecord r = new BackupRecord(app, backupMode, targetUserId, backupDestination,
+                    useRestrictedMode);
             ComponentName hostingName =
                     (backupMode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
                             || backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE)
@@ -14122,8 +14113,9 @@
             // process, etc, then mark it as being in full backup so that certain calls to the
             // process can be blocked. This is not reset to false anywhere because we kill the
             // process after the full backup is done and the ProcessRecord will vaporize anyway.
-            if (UserHandle.isApp(app.uid) &&
-                    backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL) {
+            if (UserHandle.isApp(app.uid)
+                    && backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL
+                    && r.useRestrictedMode) {
                 proc.setInFullBackup(true);
             }
             r.app = proc;
@@ -14221,9 +14213,7 @@
 
         final long oldIdent = Binder.clearCallingIdentity();
         try {
-            IBackupManager bm = IBackupManager.Stub.asInterface(
-                    ServiceManager.getService(Context.BACKUP_SERVICE));
-            bm.agentConnectedForUser(userId, agentPackageName, agent);
+            getBackupManager().agentConnectedForUser(userId, agentPackageName, agent);
         } catch (RemoteException e) {
             // can't happen; the backup manager service is local
         } catch (Exception e) {
@@ -19353,4 +19343,8 @@
         }
         return token;
     }
+
+    private IBackupManager getBackupManager() {
+        return IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE));
+    }
 }
diff --git a/services/core/java/com/android/server/am/BackupRecord.java b/services/core/java/com/android/server/am/BackupRecord.java
index 0b056d7..64cc6f0 100644
--- a/services/core/java/com/android/server/am/BackupRecord.java
+++ b/services/core/java/com/android/server/am/BackupRecord.java
@@ -32,15 +32,18 @@
     final int userId;                      // user for which backup is performed
     final int backupMode;                  // full backup / incremental / restore
     @BackupDestination final int backupDestination; // see BackupAnnotations#BackupDestination
+    final boolean useRestrictedMode; // whether the app should be put into restricted backup mode
     ProcessRecord app;                     // where this agent is running or null
 
     // ----- Implementation -----
 
-    BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _backupDestination) {
+    BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _backupDestination,
+            boolean _useRestrictedMode) {
         appInfo = _appInfo;
         backupMode = _backupMode;
         userId = _userId;
         backupDestination = _backupDestination;
+        useRestrictedMode = _useRestrictedMode;
     }
 
     public String toString() {
diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java
index e20c46c..a32d3cb 100644
--- a/services/core/java/com/android/server/am/BroadcastFilter.java
+++ b/services/core/java/com/android/server/am/BroadcastFilter.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
@@ -56,11 +57,12 @@
     final boolean visibleToInstantApp;
     public final boolean exported;
     final int initialPriority;
+    final ApplicationInfo applicationInfo;
 
     BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList,
             String _packageName, String _featureId, String _receiverId, String _requiredPermission,
             int _owningUid, int _userId, boolean _instantApp, boolean _visibleToInstantApp,
-            boolean _exported, ApplicationInfo applicationInfo, PlatformCompat platformCompat) {
+            boolean _exported, ApplicationInfo _applicationInfo, PlatformCompat platformCompat) {
         super(_filter);
         receiverList = _receiverList;
         packageName = _packageName;
@@ -72,6 +74,7 @@
         instantApp = _instantApp;
         visibleToInstantApp = _visibleToInstantApp;
         exported = _exported;
+        applicationInfo = _applicationInfo;
         initialPriority = getPriority();
         setPriority(calculateAdjustedPriority(owningUid, initialPriority,
                 applicationInfo, platformCompat));
@@ -87,6 +90,10 @@
         return null;
     }
 
+    public @NonNull ApplicationInfo getApplicationInfo() {
+        return applicationInfo;
+    }
+
     @NeverCompile
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         long token = proto.start(fieldId);
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index e8ce173..a1ab1eea 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -49,6 +49,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
 import android.os.Bundle;
@@ -57,7 +58,6 @@
 import android.util.ArrayMap;
 import android.util.IntArray;
 import android.util.PrintWriterPrinter;
-import android.util.SparseBooleanArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
@@ -865,25 +865,35 @@
     @VisibleForTesting
     static @NonNull boolean[] calculateChangeStateForReceivers(@NonNull List<Object> receivers,
             long changeId, PlatformCompat platformCompat) {
-        final SparseBooleanArray changeStateForUids = new SparseBooleanArray();
+        // TODO: b/371307720 - Remove this method as we are already avoiding the packagemanager
+        // calls by checking the changeId state using ApplicationInfos.
+        final ArrayMap<String, Boolean> changeStates = new ArrayMap<>();
         final int count = receivers.size();
         final boolean[] changeStateForReceivers = new boolean[count];
         for (int i = 0; i < count; ++i) {
-            final int receiverUid = getReceiverUid(receivers.get(i));
+            final ApplicationInfo receiverAppInfo = getReceiverAppInfo(receivers.get(i));
             final boolean isChangeEnabled;
-            final int idx = changeStateForUids.indexOfKey(receiverUid);
+            final int idx = changeStates.indexOfKey(receiverAppInfo.packageName);
             if (idx >= 0) {
-                isChangeEnabled = changeStateForUids.valueAt(idx);
+                isChangeEnabled = changeStates.valueAt(idx);
             } else {
-                isChangeEnabled = platformCompat.isChangeEnabledByUidInternalNoLogging(
-                        changeId, receiverUid);
-                changeStateForUids.put(receiverUid, isChangeEnabled);
+                isChangeEnabled = platformCompat.isChangeEnabledInternalNoLogging(
+                        changeId, receiverAppInfo);
+                changeStates.put(receiverAppInfo.packageName, isChangeEnabled);
             }
             changeStateForReceivers[i] = isChangeEnabled;
         }
         return changeStateForReceivers;
     }
 
+    static ApplicationInfo getReceiverAppInfo(@NonNull Object receiver) {
+        if (receiver instanceof BroadcastFilter) {
+            return ((BroadcastFilter) receiver).getApplicationInfo();
+        } else {
+            return ((ResolveInfo) receiver).activityInfo.applicationInfo;
+        }
+    }
+
     static int getReceiverUid(@NonNull Object receiver) {
         if (receiver instanceof BroadcastFilter) {
             return ((BroadcastFilter) receiver).owningUid;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index b51db13..98f738c 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -351,7 +351,8 @@
     private String[] mIsolatedEntryPointArgs;
 
     /**
-     * Process is currently hosting a backup agent for backup or restore.
+     * Process is currently hosting a backup agent for backup or restore. Note that this is only set
+     * when the process is put into restricted backup mode.
      */
     @GuardedBy("mService")
     private boolean mInFullBackup;
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index afdc0c0..6ed1ac85 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -226,10 +226,6 @@
             return BIOMETRIC_NO_HARDWARE;
         }
 
-        if (sensor.modality == TYPE_FACE && biometricCameraManager.isAnyCameraUnavailable()) {
-            return BIOMETRIC_HARDWARE_NOT_DETECTED;
-        }
-
         final boolean wasStrongEnough =
                 Utils.isAtLeastStrength(sensor.oemStrength, requestedStrength);
         final boolean isStrongEnough =
@@ -241,6 +237,10 @@
             return BIOMETRIC_INSUFFICIENT_STRENGTH;
         }
 
+        if (sensor.modality == TYPE_FACE && biometricCameraManager.isAnyCameraUnavailable()) {
+            return BIOMETRIC_HARDWARE_NOT_DETECTED;
+        }
+
         try {
             if (!sensor.impl.isHardwareDetected(opPackageName)) {
                 return BIOMETRIC_HARDWARE_NOT_DETECTED;
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index eeac260..6feae34 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -522,7 +522,7 @@
         // b/282922910 - we don't want apps sharing system uid and targeting
         // older target sdk to impact all system uid apps
         if (Flags.systemUidTargetSystemSdk() && !mIsWear &&
-                uid == Process.SYSTEM_UID) {
+                uid == Process.SYSTEM_UID && appInfo != null) {
             appInfo.targetSdkVersion = Build.VERSION.SDK_INT;
         }
         return appInfo;
diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java
index 716661d..68dc80f 100644
--- a/services/core/java/com/android/server/display/color/TintController.java
+++ b/services/core/java/com/android/server/display/color/TintController.java
@@ -19,6 +19,7 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
 
@@ -29,23 +30,33 @@
      */
     private static final long TRANSITION_DURATION = 3000L;
 
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
     private ValueAnimator mAnimator;
+    @GuardedBy("mLock")
     private Boolean mIsActivated;
 
     public ValueAnimator getAnimator() {
-        return mAnimator;
+        synchronized (mLock) {
+            return mAnimator;
+        }
     }
 
     public void setAnimator(ValueAnimator animator) {
-        mAnimator = animator;
+        synchronized (mLock) {
+            mAnimator = animator;
+        }
     }
 
     /**
      * Cancel the animator if it's still running.
      */
     public void cancelAnimator() {
-        if (mAnimator != null) {
-            mAnimator.cancel();
+        synchronized (mLock) {
+            if (mAnimator != null) {
+                mAnimator.cancel();
+            }
         }
     }
 
@@ -53,22 +64,30 @@
      * End the animator if it's still running, jumping to the end state.
      */
     public void endAnimator() {
-        if (mAnimator != null) {
-            mAnimator.end();
-            mAnimator = null;
+        synchronized (mLock) {
+            if (mAnimator != null) {
+                mAnimator.end();
+                mAnimator = null;
+            }
         }
     }
 
     public void setActivated(Boolean isActivated) {
-        mIsActivated = isActivated;
+        synchronized (mLock) {
+            mIsActivated = isActivated;
+        }
     }
 
     public boolean isActivated() {
-        return mIsActivated != null && mIsActivated;
+        synchronized (mLock) {
+            return mIsActivated != null && mIsActivated;
+        }
     }
 
     public boolean isActivatedStateNotSet() {
-        return mIsActivated == null;
+        synchronized (mLock) {
+            return mIsActivated == null;
+        }
     }
 
     public long getTransitionDurationMilliseconds() {
diff --git a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
index aef207f..f3820e5 100644
--- a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
+++ b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
@@ -21,6 +21,7 @@
 import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.hardware.input.AppLaunchData;
 import android.hardware.input.InputGestureData;
@@ -137,11 +138,19 @@
                 String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
                 String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
                 String roleName = parser.getAttributeValue(null, ATTRIBUTE_ROLE);
-
-                // TODO(b/358569822): Shift bookmarks to use keycode instead of shortcutChar
-                int keycode = KeyEvent.KEYCODE_UNKNOWN;
                 String shortcut = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
-                if (!TextUtils.isEmpty(shortcut)) {
+                int keycode;
+                int modifierState;
+                TypedArray a = mContext.getResources().obtainAttributes(parser,
+                        R.styleable.Bookmark);
+                try {
+                    keycode = a.getInt(R.styleable.Bookmark_keycode, KeyEvent.KEYCODE_UNKNOWN);
+                    modifierState = a.getInt(R.styleable.Bookmark_modifierState, 0);
+                } finally {
+                    a.recycle();
+                }
+                if (keycode == KeyEvent.KEYCODE_UNKNOWN && !TextUtils.isEmpty(shortcut)) {
+                    // Fetch keycode using shortcut char
                     KeyEvent[] events = virtualKcm.getEvents(new char[]{shortcut.toLowerCase(
                             Locale.ROOT).charAt(0)});
                     // Single key press can generate the character
@@ -153,12 +162,17 @@
                     Log.w(TAG, "Keycode required for bookmark with category=" + categoryName
                             + " packageName=" + packageName + " className=" + className
                             + " role=" + roleName + " shiftName=" + shiftName
-                            + " shortcut=" + shortcut);
+                            + " shortcut=" + shortcut + " modifierState=" + modifierState);
                     continue;
                 }
 
-                final boolean isShiftShortcut = (shiftName != null && shiftName.toLowerCase(
-                        Locale.ROOT).equals("true"));
+                if (modifierState == 0) {
+                    // Fetch modifierState using shiftName
+                    boolean isShiftShortcut = shiftName != null && shiftName.toLowerCase(
+                            Locale.ROOT).equals("true");
+                    modifierState =
+                            KeyEvent.META_META_ON | (isShiftShortcut ? KeyEvent.META_SHIFT_ON : 0);
+                }
                 AppLaunchData launchData = null;
                 if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
                     launchData = AppLaunchData.createLaunchDataForComponent(packageName, className);
@@ -168,11 +182,9 @@
                     launchData = AppLaunchData.createLaunchDataForRole(roleName);
                 }
                 if (launchData != null) {
-                    Log.d(TAG, "adding shortcut " + launchData + "shift="
-                            + isShiftShortcut + " keycode=" + keycode);
+                    Log.d(TAG, "adding shortcut " + launchData + " modifierState="
+                            + modifierState + " keycode=" + keycode);
                     // All bookmarks are based on Action key
-                    int modifierState =
-                            KeyEvent.META_META_ON | (isShiftShortcut ? KeyEvent.META_SHIFT_ON : 0);
                     InputGestureData bookmark = new InputGestureData.Builder()
                             .setTrigger(InputGestureData.createKeyTrigger(keycode, modifierState))
                             .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 82449ce..edad247 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -193,7 +193,6 @@
     private DisplayManagerInternal mDisplayManagerInternal;
 
     private WindowManagerInternal mWindowManagerInternal;
-    private PackageManagerInternal mPackageManagerInternal;
 
     private final File mDoubleTouchGestureEnableFile;
 
@@ -573,7 +572,6 @@
 
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
-        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
 
         mSettingsObserver.registerAndUpdate();
 
@@ -2937,10 +2935,11 @@
     private void enforceManageKeyGesturePermission() {
         // TODO(b/361567988): Use @EnforcePermission to enforce permission once flag guarding the
         //  permission is rolled out
-        if (mSystemReady) {
-            String systemUIPackage = mContext.getString(R.string.config_systemUi);
-            int systemUIAppId = UserHandle.getAppId(mPackageManagerInternal
-                    .getPackageUid(systemUIPackage, PackageManager.MATCH_SYSTEM_ONLY,
+        String systemUIPackage = mContext.getString(R.string.config_systemUi);
+        PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
+        if (pm != null) {
+            int systemUIAppId = UserHandle.getAppId(
+                    pm.getPackageUid(systemUIPackage, PackageManager.MATCH_SYSTEM_ONLY,
                             UserHandle.USER_SYSTEM));
             if (UserHandle.getCallingAppId() == systemUIAppId) {
                 return;
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
new file mode 100644
index 0000000..c05f7a0
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
@@ -0,0 +1,93 @@
+/*
+ * 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.location.contexthub;
+
+import android.hardware.contexthub.EndpointId;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IEndpointCallback;
+import android.hardware.contexthub.Message;
+import android.hardware.contexthub.MessageDeliveryStatus;
+import android.os.RemoteException;
+
+/** IEndpointCallback implementation. */
+public class ContextHubHalEndpointCallback
+        extends android.hardware.contexthub.IEndpointCallback.Stub {
+    private final IEndpointLifecycleCallback mEndpointLifecycleCallback;
+
+    /** Interface for listening for endpoint start and stop events. */
+    public interface IEndpointLifecycleCallback {
+        /** Called when a batch of endpoints started. */
+        void onEndpointStarted(HubEndpointInfo[] endpointInfos);
+
+        /** Called when a batch of endpoints stopped. */
+        void onEndpointStopped(HubEndpointInfo.HubEndpointIdentifier[] endpointIds, byte reason);
+    }
+
+    ContextHubHalEndpointCallback(IEndpointLifecycleCallback endpointLifecycleCallback) {
+        mEndpointLifecycleCallback = endpointLifecycleCallback;
+    }
+
+    @Override
+    public void onEndpointStarted(android.hardware.contexthub.EndpointInfo[] halEndpointInfos)
+            throws RemoteException {
+        if (halEndpointInfos.length == 0) {
+            return;
+        }
+        HubEndpointInfo[] endpointInfos = new HubEndpointInfo[halEndpointInfos.length];
+        for (int i = 0; i < halEndpointInfos.length; i++) {
+            endpointInfos[i++] = new HubEndpointInfo(halEndpointInfos[i]);
+        }
+        mEndpointLifecycleCallback.onEndpointStarted(endpointInfos);
+    }
+
+    @Override
+    public void onEndpointStopped(EndpointId[] halEndpointIds, byte reason) throws RemoteException {
+        HubEndpointInfo.HubEndpointIdentifier[] endpointIds =
+                new HubEndpointInfo.HubEndpointIdentifier[halEndpointIds.length];
+        for (int i = 0; i < halEndpointIds.length; i++) {
+            endpointIds[i] = new HubEndpointInfo.HubEndpointIdentifier(halEndpointIds[i]);
+        }
+        mEndpointLifecycleCallback.onEndpointStopped(endpointIds, reason);
+    }
+
+    @Override
+    public void onMessageReceived(int i, Message message) throws RemoteException {}
+
+    @Override
+    public void onMessageDeliveryStatusReceived(int i, MessageDeliveryStatus messageDeliveryStatus)
+            throws RemoteException {}
+
+    @Override
+    public void onEndpointSessionOpenRequest(
+            int i, EndpointId endpointId, EndpointId endpointId1, String s)
+            throws RemoteException {}
+
+    @Override
+    public void onCloseEndpointSession(int i, byte b) throws RemoteException {}
+
+    @Override
+    public void onEndpointSessionOpenComplete(int i) throws RemoteException {}
+
+    @Override
+    public int getInterfaceVersion() throws RemoteException {
+        return IEndpointCallback.VERSION;
+    }
+
+    @Override
+    public String getInterfaceHash() throws RemoteException {
+        return IEndpointCallback.HASH;
+    }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 88a7d08..8cf0578 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -32,6 +32,8 @@
 import android.hardware.SensorPrivacyManagerInternal;
 import android.hardware.contexthub.ErrorCode;
 import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IContextHubEndpoint;
+import android.hardware.contexthub.IContextHubEndpointCallback;
 import android.hardware.contexthub.MessageDeliveryStatus;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubMessage;
@@ -250,6 +252,7 @@
         public void handleServiceRestart() {
             Log.i(TAG, "Recovering from Context Hub HAL restart...");
             initExistingCallbacks();
+            mHubInfoRegistry.onHalRestart();
             resetSettings();
             if (Flags.reconnectHostEndpointsAfterHalRestart()) {
                 mClientManager.forEachClientOfHub(mContextHubId,
@@ -331,6 +334,7 @@
         }
 
         initDefaultClientMap();
+        initEndpointCallback();
 
         initLocationSettingNotifications();
         initWifiSettingNotifications();
@@ -509,6 +513,18 @@
         mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
     }
 
+    private void initEndpointCallback() {
+        if (mHubInfoRegistry == null) {
+            return;
+        }
+        try {
+            mContextHubWrapper.registerEndpointCallback(
+                    new ContextHubHalEndpointCallback(mHubInfoRegistry));
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException while registering IEndpointCallback", e);
+        }
+    }
+
     /**
      * Initializes existing callbacks with the mContextHubWrapper for every context hub
      */
@@ -744,8 +760,20 @@
     @Override
     public List<HubEndpointInfo> findEndpoints(long endpointId) {
         super.findEndpoints_enforcePermission();
-        // TODO(b/375487784): connect this with mHubInfoRegistry
-        return Collections.emptyList();
+        if (mHubInfoRegistry == null) {
+            return Collections.emptyList();
+        }
+        return mHubInfoRegistry.findEndpoints(endpointId);
+    }
+
+    @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @Override
+    public IContextHubEndpoint registerEndpoint(
+            HubEndpointInfo pendingHubEndpointInfo, IContextHubEndpointCallback callback)
+            throws RemoteException {
+        super.registerEndpoint_enforcePermission();
+        // TODO(b/375487784): Implement this
+        return null;
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
index 68de9db..4d1000f 100644
--- a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
+++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
@@ -16,45 +16,144 @@
 
 package com.android.server.location.contexthub;
 
+import android.hardware.contexthub.HubEndpointInfo;
 import android.hardware.location.HubInfo;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-class HubInfoRegistry {
+class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycleCallback {
     private static final String TAG = "HubInfoRegistry";
+    private final Object mLock = new Object();
 
     private final IContextHubWrapper mContextHubWrapper;
 
-    private final List<HubInfo> mHubsInfo;
+    @GuardedBy("mLock")
+    private List<HubInfo> mHubsInfo;
+
+    @GuardedBy("mLock")
+    private final ArrayMap<HubEndpointInfo.HubEndpointIdentifier, HubEndpointInfo>
+            mHubEndpointInfos = new ArrayMap<>();
 
     HubInfoRegistry(IContextHubWrapper contextHubWrapper) {
-        List<HubInfo> hubInfos;
         mContextHubWrapper = contextHubWrapper;
+        refreshCachedHubs();
+        refreshCachedEndpoints();
+    }
+
+    /** Retrieve the list of hubs available. */
+    List<HubInfo> getHubs() {
+        synchronized (mLock) {
+            return mHubsInfo;
+        }
+    }
+
+    private void refreshCachedHubs() {
+        List<HubInfo> hubInfos;
         try {
             hubInfos = mContextHubWrapper.getHubs();
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException while getting Hub info", e);
             hubInfos = Collections.emptyList();
         }
-        mHubsInfo = hubInfos;
+
+        synchronized (mLock) {
+            mHubsInfo = hubInfos;
+        }
     }
 
-    /** Retrieve the list of hubs available. */
-    List<HubInfo> getHubs() {
-        return mHubsInfo;
+    private void refreshCachedEndpoints() {
+        List<HubEndpointInfo> endpointInfos;
+        try {
+            endpointInfos = mContextHubWrapper.getEndpoints();
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException while getting Hub info", e);
+            endpointInfos = Collections.emptyList();
+        }
+
+        synchronized (mLock) {
+            mHubEndpointInfos.clear();
+            for (HubEndpointInfo endpointInfo : endpointInfos) {
+                mHubEndpointInfos.put(endpointInfo.getIdentifier(), endpointInfo);
+            }
+        }
+    }
+
+    /** Invoked when HAL restarts */
+    public void onHalRestart() {
+        synchronized (mLock) {
+            refreshCachedHubs();
+            refreshCachedEndpoints();
+        }
+    }
+
+    @Override
+    public void onEndpointStarted(HubEndpointInfo[] endpointInfos) {
+        synchronized (mLock) {
+            for (HubEndpointInfo endpointInfo : endpointInfos) {
+                mHubEndpointInfos.remove(endpointInfo.getIdentifier());
+                mHubEndpointInfos.put(endpointInfo.getIdentifier(), endpointInfo);
+            }
+        }
+    }
+
+    @Override
+    public void onEndpointStopped(
+            HubEndpointInfo.HubEndpointIdentifier[] endpointIds, byte reason) {
+        synchronized (mLock) {
+            for (HubEndpointInfo.HubEndpointIdentifier endpointId : endpointIds) {
+                mHubEndpointInfos.remove(endpointId);
+            }
+        }
+    }
+
+    /** Return a list of {@link HubEndpointInfo} that represents endpoints with the matching id. */
+    public List<HubEndpointInfo> findEndpoints(long endpointIdQuery) {
+        List<HubEndpointInfo> searchResult = new ArrayList<>();
+        synchronized (mLock) {
+            for (HubEndpointInfo.HubEndpointIdentifier endpointId : mHubEndpointInfos.keySet()) {
+                if (endpointId.getEndpoint() == endpointIdQuery) {
+                    searchResult.add(mHubEndpointInfos.get(endpointId));
+                }
+            }
+        }
+        return searchResult;
     }
 
     void dump(IndentingPrintWriter ipw) {
+        synchronized (mLock) {
+            dumpLocked(ipw);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void dumpLocked(IndentingPrintWriter ipw) {
         ipw.println(TAG);
 
         ipw.increaseIndent();
+        ipw.println("Hubs");
         for (HubInfo hubInfo : mHubsInfo) {
             ipw.println(hubInfo);
         }
         ipw.decreaseIndent();
+
+        ipw.println();
+
+        ipw.increaseIndent();
+        ipw.println("Endpoints");
+        for (HubEndpointInfo endpointInfo : mHubEndpointInfos.values()) {
+            ipw.println(endpointInfo);
+        }
+        ipw.decreaseIndent();
+
+        ipw.println();
     }
+
 }
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 6656a6f..9b729eb 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.chre.flags.Flags;
 import android.hardware.contexthub.HostEndpointInfo;
+import android.hardware.contexthub.HubEndpointInfo;
 import android.hardware.contexthub.MessageDeliveryStatus;
 import android.hardware.contexthub.NanSessionRequest;
 import android.hardware.contexthub.V1_0.ContextHub;
@@ -229,6 +230,15 @@
         return Collections.emptyList();
     }
 
+    /** Calls the appropriate getEndpoints function depending on the HAL version. */
+    public List<HubEndpointInfo> getEndpoints() throws RemoteException {
+        return Collections.emptyList();
+    }
+
+    /** Calls the appropriate registerEndpointCallback function depending on the HAL version. */
+    public void registerEndpointCallback(android.hardware.contexthub.IEndpointCallback cb)
+            throws RemoteException {}
+
     /**
      * @return True if this version of the Contexthub HAL supports Location setting notifications.
      */
@@ -622,6 +632,45 @@
             return retVal;
         }
 
+        @Override
+        public List<HubEndpointInfo> getEndpoints() throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return Collections.emptyList();
+            }
+
+            List<HubEndpointInfo> retVal = new ArrayList<>();
+            final List<android.hardware.contexthub.EndpointInfo> halEndpointInfos =
+                    hub.getEndpoints();
+            for (android.hardware.contexthub.EndpointInfo halEndpointInfo : halEndpointInfos) {
+                /* HAL -> API Type conversion */
+                final HubEndpointInfo endpointInfo = new HubEndpointInfo(halEndpointInfo);
+                if (DEBUG) {
+                    Log.i(TAG, "getEndpoints: endpointInfo=" + endpointInfo);
+                }
+                retVal.add(endpointInfo);
+            }
+
+            if (DEBUG) {
+                Log.i(TAG, "getEndpoints: total count=" + retVal.size());
+            }
+            return retVal;
+        }
+
+        @Override
+        public void registerEndpointCallback(android.hardware.contexthub.IEndpointCallback cb)
+                throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(TAG, "registerEndpointCallback: cb=" + cb);
+            }
+            hub.registerEndpointCallback(cb);
+        }
+
         public boolean supportsLocationSettingNotifications() {
             return true;
         }
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 436acba..c460465 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -17,7 +17,6 @@
 package com.android.server.media.projection;
 
 import static android.Manifest.permission.MANAGE_MEDIA_PROJECTION;
-import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
@@ -28,7 +27,6 @@
 import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
 import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_TASK;
 import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN;
-import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
@@ -41,10 +39,7 @@
 import android.app.ActivityOptions.LaunchCookie;
 import android.app.AppOpsManager;
 import android.app.IProcessObserver;
-import android.app.KeyguardManager;
 import android.app.compat.CompatChanges;
-import android.app.role.RoleManager;
-import android.companion.AssociationRequest;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.ComponentName;
@@ -74,7 +69,6 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.ContentRecordingSession;
@@ -85,7 +79,6 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
-import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.Watchdog;
 import com.android.server.wm.WindowManagerInternal;
@@ -140,12 +133,12 @@
     private final ActivityManagerInternal mActivityManagerInternal;
     private final PackageManager mPackageManager;
     private final WindowManagerInternal mWmInternal;
-    private final KeyguardManager mKeyguardManager;
-    private final RoleManager mRoleManager;
+
 
     private final MediaRouter mMediaRouter;
     private final MediaRouterCallback mMediaRouterCallback;
     private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+    private final MediaProjectionStopController mMediaProjectionStopController;
     private MediaRouter.RouteInfo mMediaRouteInfo;
 
     @GuardedBy("mLock")
@@ -175,72 +168,16 @@
         mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
         mMediaRouterCallback = new MediaRouterCallback();
         mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger(context);
-        mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        mKeyguardManager.addKeyguardLockedStateListener(
-                mContext.getMainExecutor(), this::onKeyguardLockedStateChanged);
-        mRoleManager = mContext.getSystemService(RoleManager.class);
+        mMediaProjectionStopController = new MediaProjectionStopController(context,
+                this::maybeStopMediaProjection);
         Watchdog.getInstance().addMonitor(this);
     }
 
-    /**
-     * In order to record the keyguard, the MediaProjection package must be either:
-     *   - a holder of RECORD_SENSITIVE_CONTENT permission, or
-     *   - be one of the bugreport allowlisted packages, or
-     *   - hold the OP_PROJECT_MEDIA AppOp.
-     */
-    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
-    private boolean canCaptureKeyguard() {
-        if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
-            return true;
-        }
+    private void maybeStopMediaProjection(int reason) {
         synchronized (mLock) {
-            if (mProjectionGrant == null || mProjectionGrant.packageName == null) {
-                return false;
-            }
-            boolean disableScreenShareProtections = Settings.Global.getInt(
-                    getContext().getContentResolver(),
-                    DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0;
-            if (disableScreenShareProtections) {
-                Slog.v(TAG,
-                        "Allowing keyguard capture as screenshare protections are disabled.");
-                return true;
-            }
-
-            if (mPackageManager.checkPermission(RECORD_SENSITIVE_CONTENT,
-                    mProjectionGrant.packageName)
-                    == PackageManager.PERMISSION_GRANTED) {
-                Slog.v(TAG,
-                        "Allowing keyguard capture for package with RECORD_SENSITIVE_CONTENT "
-                                + "permission");
-                return true;
-            }
-            if (AppOpsManager.MODE_ALLOWED == mAppOps.noteOpNoThrow(AppOpsManager.OP_PROJECT_MEDIA,
-                    mProjectionGrant.uid, mProjectionGrant.packageName, /* attributionTag= */ null,
-                    "recording lockscreen")) {
-                // Some tools use media projection by granting the OP_PROJECT_MEDIA app
-                // op via a shell command. Those tools can be granted keyguard capture
-                Slog.v(TAG,
-                        "Allowing keyguard capture for package with OP_PROJECT_MEDIA AppOp ");
-                return true;
-            }
-            if (isProjectionAppHoldingAppStreamingRoleLocked()) {
-                Slog.v(TAG,
-                        "Allowing keyguard capture for package holding app streaming role.");
-                return true;
-            }
-            return SystemConfig.getInstance().getBugreportWhitelistedPackages()
-                    .contains(mProjectionGrant.packageName);
-        }
-    }
-
-    @VisibleForTesting
-    void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
-        if (!isKeyguardLocked) return;
-        synchronized (mLock) {
-            if (mProjectionGrant != null && !canCaptureKeyguard()
-                    && mProjectionGrant.mVirtualDisplayId != INVALID_DISPLAY) {
-                Slog.d(TAG, "Content Recording: Stopped MediaProjection"
-                        + " due to keyguard lock");
+            if (!mMediaProjectionStopController.isExemptFromStopping(mProjectionGrant)) {
+                Slog.d(TAG, "Content Recording: Stopping MediaProjection due to "
+                        + MediaProjectionStopController.stopReasonToString(reason));
                 mProjectionGrant.stop();
             }
         }
@@ -310,6 +247,8 @@
                 }
             });
         }
+
+        mMediaProjectionStopController.startTrackingStopReasons(mContext);
     }
 
     @Override
@@ -736,20 +675,6 @@
         }
     }
 
-    /**
-     * Application holding the app streaming role
-     * ({@value AssociationRequest#DEVICE_PROFILE_APP_STREAMING}) are allowed to record the
-     * lockscreen.
-     *
-     * @return true if the is held by the recording application.
-     */
-    @GuardedBy("mLock")
-    private boolean isProjectionAppHoldingAppStreamingRoleLocked() {
-        return mRoleManager.getRoleHoldersAsUser(AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
-                        mContext.getUser())
-                .contains(mProjectionGrant.packageName);
-    }
-
     private void dump(final PrintWriter pw) {
         pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");
         synchronized (mLock) {
@@ -957,18 +882,19 @@
         public void requestConsentForInvalidProjection(@NonNull IMediaProjection projection) {
             requestConsentForInvalidProjection_enforcePermission();
 
-            if (android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()
-                    && mKeyguardManager.isKeyguardLocked()) {
-                Slog.v(TAG, "Reusing token: Won't request consent while the keyguard is locked");
-                return;
-            }
-
             synchronized (mLock) {
                 if (!isCurrentProjection(projection)) {
                     Slog.v(TAG, "Reusing token: Won't request consent again for a token that "
                             + "isn't current");
                     return;
                 }
+
+                if (mMediaProjectionStopController.isStartForbidden(mProjectionGrant)) {
+                    Slog.v(TAG,
+                            "Reusing token: Won't request consent while MediaProjection is "
+                                    + "restricted");
+                    return;
+                }
             }
 
             // Remove calling app identity before performing any privileged operations.
@@ -1076,7 +1002,6 @@
         }
     }
 
-    @VisibleForTesting
     final class MediaProjection extends IMediaProjection.Stub {
         // Host app has 5 minutes to begin using the token before it is invalid.
         // Some apps show a dialog for the user to interact with (selecting recording resolution)
@@ -1381,12 +1306,15 @@
         @Override
         public void notifyVirtualDisplayCreated(int displayId) {
             notifyVirtualDisplayCreated_enforcePermission();
-            if (mKeyguardManager.isKeyguardLocked() && !canCaptureKeyguard()) {
-                Slog.w(TAG, "Content Recording: Keyguard locked, aborting MediaProjection");
-                stop();
-                return;
-            }
             synchronized (mLock) {
+                if (mMediaProjectionStopController.isStartForbidden(mProjectionGrant)) {
+                    Slog.w(TAG,
+                            "Content Recording: MediaProjection start disallowed, aborting "
+                                    + "MediaProjection");
+                    stop();
+                    return;
+                }
+
                 mVirtualDisplayId = displayId;
 
                 // If prior session was does not have a valid display id, then update the display
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java
new file mode 100644
index 0000000..f5b26c4
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java
@@ -0,0 +1,222 @@
+/*
+ * 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.projection;
+
+import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT;
+import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
+
+import android.app.AppOpsManager;
+import android.app.KeyguardManager;
+import android.app.role.RoleManager;
+import android.companion.AssociationRequest;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.provider.Settings;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+import android.view.Display;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemConfig;
+
+import java.util.function.Consumer;
+
+/**
+ * Tracks events that should cause MediaProjection to stop
+ */
+public class MediaProjectionStopController {
+
+    private static final String TAG = "MediaProjectionStopController";
+    @VisibleForTesting
+    static final int STOP_REASON_KEYGUARD = 1;
+    @VisibleForTesting
+    static final int STOP_REASON_CALL_END = 2;
+
+    private final TelephonyCallback mTelephonyCallback = new ProjectionTelephonyCallback();
+    private final Consumer<Integer> mStopReasonConsumer;
+    private final KeyguardManager mKeyguardManager;
+    private final TelecomManager mTelecomManager;
+    private final TelephonyManager mTelephonyManager;
+    private final AppOpsManager mAppOpsManager;
+    private final PackageManager mPackageManager;
+    private final RoleManager mRoleManager;
+    private final ContentResolver mContentResolver;
+
+    private boolean mIsInCall;
+
+    public MediaProjectionStopController(Context context, Consumer<Integer> stopReasonConsumer) {
+        mStopReasonConsumer = stopReasonConsumer;
+        mKeyguardManager = context.getSystemService(KeyguardManager.class);
+        mTelecomManager = context.getSystemService(TelecomManager.class);
+        mTelephonyManager = context.getSystemService(TelephonyManager.class);
+        mAppOpsManager = context.getSystemService(AppOpsManager.class);
+        mPackageManager = context.getPackageManager();
+        mRoleManager = context.getSystemService(RoleManager.class);
+        mContentResolver = context.getContentResolver();
+    }
+
+    /**
+     * Start tracking stop reasons that may interrupt a MediaProjection session.
+     */
+    public void startTrackingStopReasons(Context context) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mKeyguardManager.addKeyguardLockedStateListener(context.getMainExecutor(),
+                    this::onKeyguardLockedStateChanged);
+            if (com.android.media.projection.flags.Flags.stopMediaProjectionOnCallEnd()) {
+                callStateChanged();
+                mTelephonyManager.registerTelephonyCallback(context.getMainExecutor(),
+                        mTelephonyCallback);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Checks whether the given projection grant is exempt from stopping restrictions.
+     */
+    public boolean isExemptFromStopping(
+            MediaProjectionManagerService.MediaProjection projectionGrant) {
+        return isExempt(projectionGrant, false);
+    }
+
+    /**
+     * Apps may disregard recording restrictions via MediaProjection for any stop reason if:
+     * - the "Disable Screenshare protections" developer option is enabled
+     * - the app is a holder of RECORD_SENSITIVE_CONTENT permission
+     * - the app holds the OP_PROJECT_MEDIA AppOp
+     * - the app holds the COMPANION_DEVICE_APP_STREAMING role
+     * - the app is one of the bugreport allowlisted packages
+     * - the current projection does not have an active VirtualDisplay associated with the
+     * MediaProjection session
+     */
+    private boolean isExempt(
+            MediaProjectionManagerService.MediaProjection projectionGrant, boolean forStart) {
+        if (projectionGrant == null || projectionGrant.packageName == null) {
+            return true;
+        }
+        boolean disableScreenShareProtections = Settings.Global.getInt(mContentResolver,
+                DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0;
+        if (disableScreenShareProtections) {
+            Slog.v(TAG, "Continuing MediaProjection as screenshare protections are disabled.");
+            return true;
+        }
+
+        if (mPackageManager.checkPermission(RECORD_SENSITIVE_CONTENT, projectionGrant.packageName)
+                == PackageManager.PERMISSION_GRANTED) {
+            Slog.v(TAG,
+                    "Continuing MediaProjection for package with RECORD_SENSITIVE_CONTENT "
+                            + "permission");
+            return true;
+        }
+        if (AppOpsManager.MODE_ALLOWED == mAppOpsManager.noteOpNoThrow(
+                AppOpsManager.OP_PROJECT_MEDIA, projectionGrant.uid,
+                projectionGrant.packageName, /* attributionTag= */ null, "recording lockscreen")) {
+            // Some tools use media projection by granting the OP_PROJECT_MEDIA app
+            // op via a shell command.
+            Slog.v(TAG, "Continuing MediaProjection for package with OP_PROJECT_MEDIA AppOp ");
+            return true;
+        }
+        if (mRoleManager.getRoleHoldersAsUser(AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+                projectionGrant.userHandle).contains(projectionGrant.packageName)) {
+            Slog.v(TAG, "Continuing MediaProjection for package holding app streaming role.");
+            return true;
+        }
+        if (SystemConfig.getInstance().getBugreportWhitelistedPackages().contains(
+                projectionGrant.packageName)) {
+            Slog.v(TAG, "Continuing MediaProjection for package allowlisted for bugreporting.");
+            return true;
+        }
+        if (!forStart && projectionGrant.getVirtualDisplayId() == Display.INVALID_DISPLAY) {
+            Slog.v(TAG, "Continuing MediaProjection as current projection has no VirtualDisplay.");
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * @return {@code true} if a MediaProjection session is currently in a restricted state.
+     */
+    public boolean isStartForbidden(
+            MediaProjectionManagerService.MediaProjection projectionGrant) {
+        if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
+            return false;
+        }
+
+        if (!mKeyguardManager.isKeyguardLocked()) {
+            return false;
+        }
+
+        if (isExempt(projectionGrant, true)) {
+            return false;
+        }
+        return true;
+    }
+
+    @VisibleForTesting
+    void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
+        if (!isKeyguardLocked) return;
+        if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
+            return;
+        }
+        mStopReasonConsumer.accept(STOP_REASON_KEYGUARD);
+    }
+
+    @VisibleForTesting
+    void callStateChanged() {
+        if (!com.android.media.projection.flags.Flags.stopMediaProjectionOnCallEnd()) {
+            return;
+        }
+        boolean isInCall = mTelecomManager.isInCall();
+        if (isInCall == mIsInCall) {
+            return;
+        }
+        if (mIsInCall && !isInCall) {
+            mStopReasonConsumer.accept(STOP_REASON_CALL_END);
+        }
+        mIsInCall = isInCall;
+    }
+
+    /**
+     * @return a String representation of the stop reason interrupting MediaProjection.
+     */
+    public static String stopReasonToString(int stopReason) {
+        switch (stopReason) {
+            case STOP_REASON_KEYGUARD -> {
+                return "STOP_REASON_KEYGUARD";
+            }
+            case STOP_REASON_CALL_END -> {
+                return "STOP_REASON_CALL_END";
+            }
+        }
+        return "";
+    }
+
+    private final class ProjectionTelephonyCallback extends TelephonyCallback implements
+            TelephonyCallback.CallStateListener {
+        @Override
+        public void onCallStateChanged(int state) {
+            callStateChanged();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 7ef3582..961b4b3c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3606,6 +3606,13 @@
                 case "--force-verification":
                     sessionParams.setForceVerification();
                     break;
+                case "--disable-auto-install-dependencies":
+                    if (Flags.sdkDependencyInstaller()) {
+                        sessionParams.setEnableAutoInstallDependencies(false);
+                    } else {
+                        throw new IllegalArgumentException("Unknown option " + opt);
+                    }
+                    break;
                 default:
                     throw new IllegalArgumentException("Unknown option " + opt);
             }
@@ -4894,6 +4901,10 @@
                 + "#compiler_filters");
         pw.println("          or 'skip'");
         pw.println("      --force-verification: if set, enable the verification for this install");
+        if (Flags.sdkDependencyInstaller()) {
+            pw.println("      --disable-auto-install-dependencies: if set, any missing shared");
+            pw.println("          library dependencies will not be auto-installed");
+        }
         pw.println("");
         pw.println("  install-existing [--user USER_ID|all|current]");
         pw.println("       [--instant] [--full] [--wait] [--restrict-permissions] PACKAGE");
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 4f67318..c9f66eb 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -29,6 +29,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Icon;
 import android.hardware.input.AppLaunchData;
@@ -65,6 +66,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -84,6 +86,7 @@
     private static final String ATTRIBUTE_PACKAGE = "package";
     private static final String ATTRIBUTE_CLASS = "class";
     private static final String ATTRIBUTE_SHORTCUT = "shortcut";
+    private static final String ATTRIBUTE_KEYCODE = "keycode";
     private static final String ATTRIBUTE_CATEGORY = "category";
     private static final String ATTRIBUTE_SHIFT = "shift";
     private static final String ATTRIBUTE_ROLE = "role";
@@ -167,6 +170,9 @@
                 }, UserHandle.ALL);
         mCurrentUser = currentUser;
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
+    }
+
+    void onSystemReady() {
         loadShortcuts();
     }
 
@@ -335,6 +341,7 @@
         try {
             XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
             XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
+            KeyCharacterMap virtualKcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
 
             while (true) {
                 XmlUtils.nextElement(parser);
@@ -353,15 +360,36 @@
                 String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
                 String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
                 String roleName = parser.getAttributeValue(null, ATTRIBUTE_ROLE);
+                final int keycode;
+                final int modifierState;
+                TypedArray a = mContext.getResources().obtainAttributes(parser,
+                        R.styleable.Bookmark);
+                try {
+                    keycode = a.getInt(R.styleable.Bookmark_keycode, KeyEvent.KEYCODE_UNKNOWN);
+                    modifierState = a.getInt(R.styleable.Bookmark_modifierState, 0);
+                } finally {
+                    a.recycle();
+                }
 
+                if (TextUtils.isEmpty(shortcutName) && keycode != KeyEvent.KEYCODE_UNKNOWN) {
+                    // Try to find shortcutChar using keycode
+                    shortcutName = String.valueOf(virtualKcm.getDisplayLabel(keycode)).toLowerCase(
+                            Locale.ROOT);
+                }
                 if (TextUtils.isEmpty(shortcutName)) {
                     Log.w(TAG, "Shortcut required for bookmark with category=" + categoryName
                             + " packageName=" + packageName + " className=" + className
-                            + " role=" + roleName + "shiftName=" + shiftName);
+                            + " role=" + roleName + " shiftName=" + shiftName + " keycode= "
+                            + keycode + " modifierState= " + modifierState);
                     continue;
                 }
 
-                final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true"));
+                final boolean isShiftShortcut;
+                if (!TextUtils.isEmpty(shiftName)) {
+                    isShiftShortcut = shiftName.equals("true");
+                } else {
+                    isShiftShortcut = (modifierState & KeyEvent.META_SHIFT_ON) != 0;
+                }
 
                 if (modifierShortcutManagerRefactor()) {
                     final char shortcutChar = shortcutName.charAt(0);
@@ -376,7 +404,7 @@
                         bookmark = new RoleBookmark(shortcutChar, isShiftShortcut, roleName);
                     }
                     if (bookmark != null) {
-                        Log.d(TAG, "adding shortcut " + bookmark + "shift="
+                        Log.d(TAG, "adding shortcut " + bookmark + " shift="
                                 + isShiftShortcut + " char=" + shortcutChar);
                         mBookmarks.put(new Pair<>(shortcutChar, isShiftShortcut), bookmark);
                     }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index dda5bcf..85e7cfe 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -6610,6 +6610,7 @@
         // In normal flow, systemReady is called before other system services are ready.
         // So it is better not to bind keyguard here.
         mKeyguardDelegate.onSystemReady();
+        mModifierShortcutManager.onSystemReady();
 
         mVrManagerInternal = LocalServices.getService(VrManagerInternal.class);
         if (mVrManagerInternal != null) {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 0acfe92..37883f5 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2332,6 +2332,8 @@
         Trace.traceBegin(Trace.TRACE_TAG_POWER, traceMethodName);
         try {
             // Phase 2: Handle wakefulness change and bookkeeping.
+            // Under lock, invalidate before set ensures caches won't return stale values.
+            mInjector.invalidateIsInteractiveCaches();
             mWakefulnessRaw = newWakefulness;
             mWakefulnessChanging = true;
             mDirty |= DIRTY_WAKEFULNESS;
@@ -2429,7 +2431,6 @@
     void onPowerGroupEventLocked(int event, PowerGroup powerGroup) {
         mWakefulnessChanging = true;
         mDirty |= DIRTY_WAKEFULNESS;
-        mInjector.invalidateIsInteractiveCaches();
         final int groupId = powerGroup.getGroupId();
         if (event == DisplayGroupPowerChangeListener.DISPLAY_GROUP_REMOVED) {
             mPowerGroups.delete(groupId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 54b257c..8268cae 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1022,12 +1022,12 @@
                 return;
             }
 
-            final boolean disableSecureWindows;
+            boolean disableSecureWindows;
             try {
                 disableSecureWindows = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                         Settings.Secure.DISABLE_SECURE_WINDOWS, 0) != 0;
             } catch (Settings.SettingNotFoundException e) {
-                return;
+                disableSecureWindows = false;
             }
             if (mDisableSecureWindows == disableSecureWindows) {
                 return;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 565f75b..0918965 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -888,7 +888,8 @@
 
         if (windowingMode > -1) {
             if (mService.isInLockTaskMode()
-                    && WindowConfiguration.inMultiWindowMode(windowingMode)) {
+                    && WindowConfiguration.inMultiWindowMode(windowingMode)
+                    && !container.isEmbedded()) {
                 Slog.w(TAG, "Dropping unsupported request to set multi-window windowing mode"
                         + " during locked task mode.");
                 return effects;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index b982098..76d16e1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -371,6 +371,9 @@
     }
 
     ActiveAdmin(int userId, boolean permissionBased) {
+        if (Flags.activeAdminCleanup()) {
+            throw new UnsupportedOperationException("permission based admin no longer supported");
+        }
         if (permissionBased == false) {
             throw new IllegalArgumentException("Can only pass true for permissionBased admin");
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index 395ea91..c937e10 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -21,6 +21,7 @@
 import android.annotation.UserIdInt;
 import android.app.admin.DeviceAdminInfo;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.flags.Flags;
 import android.content.ComponentName;
 import android.os.FileUtils;
 import android.os.PersistableBundle;
@@ -124,17 +125,18 @@
     final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
     final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
 
-    // Some DevicePolicyManager APIs can be called by (1) a DPC or (2) an app with permissions that
-    // isn't a DPC. For the latter, the caller won't have to provide a ComponentName and won't be
-    // mapped to an ActiveAdmin. This permission-based admin should be used to persist policies
-    // set by the permission-based caller. This admin should not be added to mAdminMap or mAdminList
-    // since a lot of methods in DPMS assume the ActiveAdmins here have a valid ComponentName.
-    // Instead, use variants of DPMS active admin getters to include the permission-based admin.
+    /**
+     * @deprecated Do not use. Policies set by permission holders must go into DevicePolicyEngine.
+     */
+    @Deprecated
     ActiveAdmin mPermissionBasedAdmin;
 
     // Create or get the permission-based admin. The permission-based admin will not have a
     // DeviceAdminInfo or ComponentName.
     ActiveAdmin createOrGetPermissionBasedAdmin(int userId) {
+        if (Flags.activeAdminCleanup()) {
+            throw new UnsupportedOperationException("permission based admin no longer supported");
+        }
         if (mPermissionBasedAdmin == null) {
             mPermissionBasedAdmin = new ActiveAdmin(userId, /* permissionBased= */ true);
         }
@@ -147,7 +149,7 @@
     // This is the list of component allowed to start lock task mode.
     List<String> mLockTaskPackages = new ArrayList<>();
 
-    /** @deprecated moved to {@link ActiveAdmin#protectedPackages}. */
+    /** @deprecated moved to DevicePolicyEngine. */
     @Deprecated
     @Nullable
     List<String> mUserControlDisabledPackages;
@@ -280,7 +282,7 @@
                 }
             }
 
-            if (policyData.mPermissionBasedAdmin != null) {
+            if (!Flags.activeAdminCleanup() && policyData.mPermissionBasedAdmin != null) {
                 out.startTag(null, "permission-based-admin");
                 policyData.mPermissionBasedAdmin.writeToXml(out);
                 out.endTag(null, "permission-based-admin");
@@ -521,7 +523,8 @@
                     } catch (RuntimeException e) {
                         Slogf.w(TAG, e, "Failed loading admin %s", name);
                     }
-                } else if ("permission-based-admin".equals(tag)) {
+                } else if (!Flags.activeAdminCleanup() && "permission-based-admin".equals(tag)) {
+
                     ActiveAdmin ap = new ActiveAdmin(policy.mUserId, /* permissionBased= */ true);
                     ap.readFromXml(parser, /* overwritePolicies= */ false);
                     policy.mPermissionBasedAdmin = ap;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 90c3dff..6292cbf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -505,6 +505,7 @@
 import com.android.internal.app.LocalePicker;
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.net.NetworkUtilsInternal;
 import com.android.internal.notification.SystemNotificationChannels;
@@ -716,24 +717,24 @@
         SECURE_SETTINGS_DEVICEOWNER_ALLOWLIST.add(Settings.Secure.LOCATION_MODE);
 
         GLOBAL_SETTINGS_ALLOWLIST = new ArraySet<>();
-        GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.ADB_ENABLED);
-        GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.ADB_WIFI_ENABLED);
-        GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.AUTO_TIME);
-        GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.AUTO_TIME_ZONE);
-        GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.DATA_ROAMING);
-        GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.USB_MASS_STORAGE_ENABLED);
-        GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.WIFI_SLEEP_POLICY);
-        GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.STAY_ON_WHILE_PLUGGED_IN);
-        GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN);
-        GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.PRIVATE_DNS_MODE);
-        GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.PRIVATE_DNS_SPECIFIER);
+        GLOBAL_SETTINGS_ALLOWLIST.add(Global.ADB_ENABLED);
+        GLOBAL_SETTINGS_ALLOWLIST.add(Global.ADB_WIFI_ENABLED);
+        GLOBAL_SETTINGS_ALLOWLIST.add(Global.AUTO_TIME);
+        GLOBAL_SETTINGS_ALLOWLIST.add(Global.AUTO_TIME_ZONE);
+        GLOBAL_SETTINGS_ALLOWLIST.add(Global.DATA_ROAMING);
+        GLOBAL_SETTINGS_ALLOWLIST.add(Global.USB_MASS_STORAGE_ENABLED);
+        GLOBAL_SETTINGS_ALLOWLIST.add(Global.WIFI_SLEEP_POLICY);
+        GLOBAL_SETTINGS_ALLOWLIST.add(Global.STAY_ON_WHILE_PLUGGED_IN);
+        GLOBAL_SETTINGS_ALLOWLIST.add(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN);
+        GLOBAL_SETTINGS_ALLOWLIST.add(Global.PRIVATE_DNS_MODE);
+        GLOBAL_SETTINGS_ALLOWLIST.add(PRIVATE_DNS_SPECIFIER);
 
         GLOBAL_SETTINGS_DEPRECATED = new ArraySet<>();
-        GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.BLUETOOTH_ON);
-        GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.DEVELOPMENT_SETTINGS_ENABLED);
-        GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.MODE_RINGER);
-        GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.NETWORK_PREFERENCE);
-        GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.WIFI_ON);
+        GLOBAL_SETTINGS_DEPRECATED.add(Global.BLUETOOTH_ON);
+        GLOBAL_SETTINGS_DEPRECATED.add(Global.DEVELOPMENT_SETTINGS_ENABLED);
+        GLOBAL_SETTINGS_DEPRECATED.add(Global.MODE_RINGER);
+        GLOBAL_SETTINGS_DEPRECATED.add(Global.NETWORK_PREFERENCE);
+        GLOBAL_SETTINGS_DEPRECATED.add(Global.WIFI_ON);
 
         SYSTEM_SETTINGS_ALLOWLIST = new ArraySet<>();
         SYSTEM_SETTINGS_ALLOWLIST.add(Settings.System.SCREEN_BRIGHTNESS);
@@ -776,7 +777,7 @@
 
     /**
      * Strings logged with {@link
-     * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB},
+     * MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB},
      * {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB},
      * {@link DevicePolicyEnums#SET_NETWORK_LOGGING_ENABLED} and
      * {@link DevicePolicyEnums#RETRIEVE_NETWORK_LOGS}.
@@ -787,11 +788,11 @@
     /**
      * For admin apps targeting R+, throw when the app sets password requirement
      * that is not taken into account at given quality. For example when quality is set
-     * to {@link android.app.admin.DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, it doesn't
+     * to {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, it doesn't
      * make sense to require certain password length. If the intent is to require a password of
      * certain length having at least NUMERIC quality, the admin should first call
-     * {@link android.app.admin.DevicePolicyManager#setPasswordQuality} and only then call
-     * {@link android.app.admin.DevicePolicyManager#setPasswordMinimumLength}.
+     * {@link DevicePolicyManager#setPasswordQuality} and only then call
+     * {@link DevicePolicyManager#setPasswordMinimumLength}.
      *
      * <p>Conversely when an admin app targeting R+ lowers password quality, those
      * requirements that stop making sense are reset to default values.
@@ -802,9 +803,9 @@
 
     /**
      * Admin apps targeting Android R+ may not use
-     * {@link android.app.admin.DevicePolicyManager#setSecureSetting} to change the deprecated
-     * {@link android.provider.Settings.Secure#LOCATION_MODE} setting. Instead they should use
-     * {@link android.app.admin.DevicePolicyManager#setLocationEnabled}.
+     * {@link DevicePolicyManager#setSecureSetting} to change the deprecated
+     * {@link Settings.Secure#LOCATION_MODE} setting. Instead they should use
+     * {@link DevicePolicyManager#setLocationEnabled}.
      */
     @ChangeId
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
@@ -850,7 +851,7 @@
     private @interface CopyAccountStatus {}
 
     /**
-     * Mapping of {@link android.app.admin.DevicePolicyManager.ApplicationExemptionConstants} to
+     * Mapping of {@link DevicePolicyManager.ApplicationExemptionConstants} to
      * corresponding app-ops.
      */
     private static final Map<Integer, String> APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS =
@@ -882,11 +883,11 @@
 
     /**
      * Admin apps targeting Android S+ may not use
-     * {@link android.app.admin.DevicePolicyManager#setPasswordQuality} to set password quality
+     * {@link DevicePolicyManager#setPasswordQuality} to set password quality
      * on the {@code DevicePolicyManager} instance obtained by calling
-     * {@link android.app.admin.DevicePolicyManager#getParentProfileInstance}.
+     * {@link DevicePolicyManager#getParentProfileInstance}.
      * Instead, they should use
-     * {@link android.app.admin.DevicePolicyManager#setRequiredPasswordComplexity} to set
+     * {@link DevicePolicyManager#setRequiredPasswordComplexity} to set
      * coarse-grained password requirements device-wide.
      */
     @ChangeId
@@ -895,7 +896,7 @@
 
     /**
      * For Admin Apps targeting U+
-     * If {@link android.security.IKeyChainService#setGrant} is called with an alias with no
+     * If {@link IKeyChainService#setGrant} is called with an alias with no
      * existing key, throw IllegalArgumentException.
      */
     @ChangeId
@@ -1477,8 +1478,8 @@
                     if (packageName == null || packageName.equals(adminPackage)) {
                         if (mIPackageManager.getPackageInfo(adminPackage, 0, userHandle) == null
                                 || mIPackageManager.getReceiverInfo(aa.info.getComponent(),
-                                PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                                MATCH_DIRECT_BOOT_AWARE
+                                        | MATCH_DIRECT_BOOT_UNAWARE,
                                 userHandle) == null) {
                             Slogf.e(LOG_TAG, String.format(
                                     "Admin package %s not found for user %d, removing active admin",
@@ -1696,7 +1697,7 @@
             return getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
         }
 
-        Context createContextAsUser(UserHandle user) throws PackageManager.NameNotFoundException {
+        Context createContextAsUser(UserHandle user) throws NameNotFoundException {
             final String packageName = mContext.getPackageName();
             return mContext.createPackageContextAsUser(packageName, 0, user);
         }
@@ -2008,25 +2009,25 @@
         }
 
         void settingsGlobalPutStringForUser(String name, String value, int userHandle) {
-            Settings.Global.putStringForUser(mContext.getContentResolver(),
+            Global.putStringForUser(mContext.getContentResolver(),
                     name, value, userHandle);
         }
 
         int settingsGlobalGetInt(String name, int def) {
-            return Settings.Global.getInt(mContext.getContentResolver(), name, def);
+            return Global.getInt(mContext.getContentResolver(), name, def);
         }
 
         @Nullable
         String settingsGlobalGetString(String name) {
-            return Settings.Global.getString(mContext.getContentResolver(), name);
+            return Global.getString(mContext.getContentResolver(), name);
         }
 
         void settingsGlobalPutInt(String name, int value) {
-            Settings.Global.putInt(mContext.getContentResolver(), name, value);
+            Global.putInt(mContext.getContentResolver(), name, value);
         }
 
         void settingsGlobalPutString(String name, String value) {
-            Settings.Global.putString(mContext.getContentResolver(), name, value);
+            Global.putString(mContext.getContentResolver(), name, value);
         }
 
         void settingsSystemPutStringForUser(String name, String value, int userId) {
@@ -3206,8 +3207,8 @@
                 return mIPackageManager.getReceiverInfo(adminName,
                         GET_META_DATA
                         | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
+                        | MATCH_DIRECT_BOOT_AWARE
+                        | MATCH_DIRECT_BOOT_UNAWARE, userHandle);
             } catch (RemoteException e) {
                 // shouldn't happen.
                 Slogf.wtf(LOG_TAG, "Error getting receiver info", e);
@@ -3218,9 +3219,9 @@
             throw new IllegalArgumentException("Unknown admin: " + adminName);
         }
 
-        if (!permission.BIND_DEVICE_ADMIN.equals(ai.permission)) {
+        if (!BIND_DEVICE_ADMIN.equals(ai.permission)) {
             final String message = "DeviceAdminReceiver " + adminName + " must be protected with "
-                    + permission.BIND_DEVICE_ADMIN;
+                    + BIND_DEVICE_ADMIN;
             Slogf.w(LOG_TAG, message);
             if (throwForMissingPermission &&
                     ai.applicationInfo.targetSdkVersion > Build.VERSION_CODES.M) {
@@ -3978,7 +3979,8 @@
             final int N = admins.size();
             for (int i = 0; i < N; i++) {
                 ActiveAdmin admin = admins.get(i);
-                if ((admin.isPermissionBased || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD))
+                if (((!Flags.activeAdminCleanup() && admin.isPermissionBased)
+                        || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD))
                         && admin.passwordExpirationTimeout > 0L
                         && now >= admin.passwordExpirationDate - EXPIRATION_GRACE_PERIOD_MS
                         && admin.passwordExpirationDate > 0L) {
@@ -4411,8 +4413,8 @@
         final ApplicationInfo ai;
         try {
             ai = mInjector.getIPackageManager().getApplicationInfo(packageName,
-                    (PackageManager.MATCH_DIRECT_BOOT_AWARE
-                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE), userHandle);
+                    (MATCH_DIRECT_BOOT_AWARE
+                            | MATCH_DIRECT_BOOT_UNAWARE), userHandle);
         } catch (RemoteException e) {
             throw new IllegalStateException(e);
         }
@@ -5575,13 +5577,25 @@
                 caller.getUserId());
         Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
 
-        ActiveAdmin activeAdmin = admin.getActiveAdmin();
+        final ActiveAdmin activeAdmin;
+        if (Flags.activeAdminCleanup()) {
+            if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
+                synchronized (getLockObject()) {
+                    activeAdmin = getActiveAdminUncheckedLocked(
+                            admin.getComponentName(), admin.getUserId());
+                }
+            } else {
+                activeAdmin = null;
+            }
+        } else {
+            activeAdmin = admin.getActiveAdmin();
+        }
 
         // We require the caller to explicitly clear any password quality requirements set
         // on the parent DPM instance, to avoid the case where password requirements are
         // specified in the form of quality on the parent but complexity on the profile
         // itself.
-        if (!calledOnParent) {
+        if (activeAdmin != null && !calledOnParent) {
             final boolean hasQualityRequirementsOnParent = activeAdmin.hasParentActiveAdmin()
                     && activeAdmin.getParentActiveAdmin().mPasswordPolicy.quality
                     != PASSWORD_QUALITY_UNSPECIFIED;
@@ -5605,20 +5619,22 @@
         }
 
         mInjector.binderWithCleanCallingIdentity(() -> {
-            // Reset the password policy.
-            if (calledOnParent) {
-                activeAdmin.getParentActiveAdmin().mPasswordPolicy = new PasswordPolicy();
-            } else {
-                activeAdmin.mPasswordPolicy = new PasswordPolicy();
+            if (activeAdmin != null) {
+                // Reset the password policy.
+                if (calledOnParent) {
+                    activeAdmin.getParentActiveAdmin().mPasswordPolicy = new PasswordPolicy();
+                } else {
+                    activeAdmin.mPasswordPolicy = new PasswordPolicy();
+                }
+                updatePasswordQualityCacheForUserGroup(caller.getUserId());
             }
+
             synchronized (getLockObject()) {
                 updatePasswordValidityCheckpointLocked(caller.getUserId(), calledOnParent);
             }
-            updatePasswordQualityCacheForUserGroup(caller.getUserId());
             saveSettingsLocked(caller.getUserId());
         });
 
-
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_PASSWORD_COMPLEXITY)
                 .setAdmin(caller.getPackageName())
@@ -5977,7 +5993,7 @@
             Preconditions.checkCallAuthorization(admin != null,
                     "Unauthorized caller cannot call resetPassword.");
             if (getTargetSdk(admin.info.getPackageName(),
-                    userHandle) <= android.os.Build.VERSION_CODES.M) {
+                    userHandle) <= Build.VERSION_CODES.M) {
                 Slogf.e(LOG_TAG, "Device admin can no longer call resetPassword()");
                 return false;
             }
@@ -6127,7 +6143,7 @@
             if (policy.mLastMaximumTimeToLock != Long.MAX_VALUE) {
                 // Make sure KEEP_SCREEN_ON is disabled, since that
                 // would allow bypassing of the maximum time to lock.
-                mInjector.settingsGlobalPutInt(Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
+                mInjector.settingsGlobalPutInt(Global.STAY_ON_WHILE_PLUGGED_IN, 0);
             }
             getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(parentId, timeMs);
         });
@@ -6299,28 +6315,33 @@
         final int callingUserId = caller.getUserId();
         ComponentName adminComponent = null;
         synchronized (getLockObject()) {
-            ActiveAdmin admin;
             // Make sure the caller has any active admin with the right policy or
             // the required permission.
             if (Flags.lockNowCoexistence()) {
-                admin = enforcePermissionsAndGetEnforcingAdmin(
+                EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
                         /* admin= */ null,
                         /* permissions= */ new String[]{MANAGE_DEVICE_POLICY_LOCK, LOCK_DEVICE},
                         /* deviceAdminPolicy= */ USES_POLICY_FORCE_LOCK,
                         caller.getPackageName(),
                         getAffectedUser(parent)
-                 ).getActiveAdmin();
+                );
+                if (Flags.activeAdminCleanup()) {
+                    adminComponent = enforcingAdmin.getComponentName();
+                } else {
+                    ActiveAdmin admin = enforcingAdmin.getActiveAdmin();
+                    adminComponent = admin == null ? null : admin.info.getComponent();
+                }
             } else {
-                admin = getActiveAdminOrCheckPermissionForCallerLocked(
+                ActiveAdmin admin = getActiveAdminOrCheckPermissionForCallerLocked(
                         null,
-                        DeviceAdminInfo.USES_POLICY_FORCE_LOCK,
+                        USES_POLICY_FORCE_LOCK,
                         parent,
                         LOCK_DEVICE);
+                adminComponent = admin == null ? null : admin.info.getComponent();
             }
             checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_LOCK_NOW);
             final long ident = mInjector.binderClearCallingIdentity();
             try {
-                adminComponent = admin == null ? null : admin.info.getComponent();
                 if (adminComponent != null) {
                     // For Profile Owners only, callers with only permission not allowed.
                     if ((flags & DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY) != 0) {
@@ -7455,7 +7476,7 @@
      * privileged APIs.
      * <p>
      * This is done by checking that the calling package is authorized to perform the app operation
-     * {@link android.app.AppOpsManager#OP_MANAGE_CREDENTIALS}.
+     * {@link AppOpsManager#OP_MANAGE_CREDENTIALS}.
      *
      * @param caller the calling identity
      * @return {@code true} if the calling process is the credential management app.
@@ -7465,7 +7486,7 @@
             AppOpsManager appOpsManager = mInjector.getAppOpsManager();
             if (appOpsManager == null) return false;
             return appOpsManager.noteOpNoThrow(AppOpsManager.OP_MANAGE_CREDENTIALS, caller.getUid(),
-                    caller.getPackageName(), null, null) == AppOpsManager.MODE_ALLOWED;
+                    caller.getPackageName(), null, null) == MODE_ALLOWED;
         });
     }
 
@@ -7776,7 +7797,7 @@
     public void wipeDataWithReason(String callerPackageName, int flags,
             @NonNull String wipeReasonForUser, boolean calledOnParentInstance,
             boolean factoryReset) {
-        if (!mHasFeature && !hasCallingOrSelfPermission(permission.MASTER_CLEAR)) {
+        if (!mHasFeature && !hasCallingOrSelfPermission(MASTER_CLEAR)) {
             return;
         }
         CallerIdentity caller = getCallerIdentity(callerPackageName);
@@ -7789,7 +7810,6 @@
                 USES_POLICY_WIPE_DATA,
                 caller.getPackageName(),
                 factoryReset ? UserHandle.USER_ALL : getAffectedUser(calledOnParentInstance));
-        ActiveAdmin admin = enforcingAdmin.getActiveAdmin();
 
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_WIPE_DATA);
 
@@ -7798,10 +7818,20 @@
                     calledByProfileOwnerOnOrgOwnedDevice, calledOnParentInstance);
         }
 
-        int userId = admin != null ? admin.getUserHandle().getIdentifier()
-                : caller.getUserId();
-        Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser, admin,
-                userId);
+        int userId;
+        ActiveAdmin admin = null;
+        if (Flags.activeAdminCleanup()) {
+            userId = enforcingAdmin.getUserId();
+            Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser,
+                    enforcingAdmin, userId);
+        } else {
+            admin = enforcingAdmin.getActiveAdmin();
+            userId = admin != null ? admin.getUserHandle().getIdentifier()
+                    : caller.getUserId();
+            Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser, admin,
+                    userId);
+        }
+
         if (calledByProfileOwnerOnOrgOwnedDevice) {
             // When wipeData is called on the parent instance, it implies wiping the entire device.
             if (calledOnParentInstance) {
@@ -7822,25 +7852,36 @@
 
         final String adminName;
         final ComponentName adminComp;
-        if (admin != null) {
-            if (admin.isPermissionBased) {
-                adminComp = null;
-                adminName = caller.getPackageName();
-                event.setAdmin(adminName);
-            } else {
-                adminComp = admin.info.getComponent();
-                adminName = adminComp.flattenToShortString();
-                event.setAdmin(adminComp);
-            }
+        if (Flags.activeAdminCleanup()) {
+            adminComp = enforcingAdmin.getComponentName();
+            adminName = adminComp != null
+                    ? adminComp.flattenToShortString()
+                    : enforcingAdmin.getPackageName();
+            event.setAdmin(enforcingAdmin.getPackageName());
+            // Not including any HSUM handling here because the "else" branch in the "flag off"
+            // case below is unreachable under normal circumstances and for permission-based
+            // callers admin won't be null.
         } else {
-            adminComp = null;
-            adminName = mInjector.getPackageManager().getPackagesForUid(caller.getUid())[0];
-            Slogf.i(LOG_TAG, "Logging wipeData() event admin as " + adminName);
-            event.setAdmin(adminName);
-            if (mInjector.userManagerIsHeadlessSystemUserMode()) {
-                // On headless system user mode, the call is meant to factory reset the whole
-                // device, otherwise the caller could simply remove the current user.
-                userId = UserHandle.USER_SYSTEM;
+            if (admin != null) {
+                if (admin.isPermissionBased) {
+                    adminComp = null;
+                    adminName = caller.getPackageName();
+                    event.setAdmin(adminName);
+                } else {
+                    adminComp = admin.info.getComponent();
+                    adminName = adminComp.flattenToShortString();
+                    event.setAdmin(adminComp);
+                }
+            } else {
+                adminComp = null;
+                adminName = mInjector.getPackageManager().getPackagesForUid(caller.getUid())[0];
+                Slogf.i(LOG_TAG, "Logging wipeData() event admin as " + adminName);
+                event.setAdmin(adminName);
+                if (mInjector.userManagerIsHeadlessSystemUserMode()) {
+                    // On headless system user mode, the call is meant to factory reset the whole
+                    // device, otherwise the caller could simply remove the current user.
+                    userId = UserHandle.USER_SYSTEM;
+                }
             }
         }
         event.write();
@@ -8154,7 +8195,7 @@
         synchronized (getLockObject()) {
             if (who == null) {
                 Preconditions.checkCallAuthorization(frpManagementAgentUid == caller.getUid()
-                                || hasCallingPermission(permission.MASTER_CLEAR)
+                                || hasCallingPermission(MASTER_CLEAR)
                                 || hasCallingPermission(MANAGE_DEVICE_POLICY_FACTORY_RESET),
                         "Must be called by the FRP management agent on device");
                 admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
@@ -8328,7 +8369,8 @@
         List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(userHandle);
         for (int i = 0; i < admins.size(); i++) {
             ActiveAdmin admin = admins.get(i);
-            if (admin.isPermissionBased || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) {
+            if ((!Flags.activeAdminCleanup() && admin.isPermissionBased)
+                    || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) {
                 affectedUserIds.add(admin.getUserHandle().getIdentifier());
                 long timeout = admin.passwordExpirationTimeout;
                 admin.passwordExpirationDate =
@@ -8422,7 +8464,7 @@
      */
     private int getUserIdToWipeForFailedPasswords(ActiveAdmin admin) {
         final int userId = admin.getUserHandle().getIdentifier();
-        if (admin.isPermissionBased) {
+        if (!Flags.activeAdminCleanup() && admin.isPermissionBased) {
             return userId;
         }
         final ComponentName component = admin.info.getComponent();
@@ -8640,9 +8682,9 @@
             Slogf.e(LOG_TAG, "Invalid proxy properties, ignoring: " + proxyProperties.toString());
             return;
         }
-        mInjector.settingsGlobalPutString(Settings.Global.GLOBAL_HTTP_PROXY_HOST, data[0]);
-        mInjector.settingsGlobalPutInt(Settings.Global.GLOBAL_HTTP_PROXY_PORT, proxyPort);
-        mInjector.settingsGlobalPutString(Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
+        mInjector.settingsGlobalPutString(Global.GLOBAL_HTTP_PROXY_HOST, data[0]);
+        mInjector.settingsGlobalPutInt(Global.GLOBAL_HTTP_PROXY_PORT, proxyPort);
+        mInjector.settingsGlobalPutString(Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
                 exclusionList);
     }
 
@@ -8763,7 +8805,7 @@
         }
 
         final int rawStatus = getEncryptionStatus();
-        if ((rawStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER) && legacyApp) {
+        if ((rawStatus == ENCRYPTION_STATUS_ACTIVE_PER_USER) && legacyApp) {
             return DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE;
         }
         return rawStatus;
@@ -8787,7 +8829,7 @@
      */
     private int getEncryptionStatus() {
         if (mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
-            return DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
+            return ENCRYPTION_STATUS_ACTIVE_PER_USER;
         } else {
             return DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED;
         }
@@ -8982,7 +9024,7 @@
         // Turn AUTO_TIME on in settings if it is required
         if (required) {
             mInjector.binderWithCleanCallingIdentity(
-                    () -> mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME,
+                    () -> mInjector.settingsGlobalPutInt(Global.AUTO_TIME,
                             1 /* AUTO_TIME on */));
         }
         DevicePolicyEventLogger
@@ -10414,7 +10456,7 @@
         policy.mDelegationMap.clear();
         policy.mStatusBarDisabled = false;
         policy.mSecondaryLockscreenEnabled = false;
-        policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
+        policy.mUserProvisioningState = STATE_USER_UNMANAGED;
         policy.mAffiliationIds.clear();
         resetAffiliationCacheLocked();
         policy.mLockTaskPackages.clear();
@@ -10449,7 +10491,7 @@
     @Override
     public int getUserProvisioningState(int userHandle) {
         if (!mHasFeature) {
-            return DevicePolicyManager.STATE_USER_UNMANAGED;
+            return STATE_USER_UNMANAGED;
         }
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(canManageUsers(caller)
@@ -10504,7 +10546,7 @@
                     // ADB shell can only move directly from un-managed to finalized as part of
                     // directly setting profile-owner or device-owner.
                     if (getUserProvisioningState(userId)
-                            != DevicePolicyManager.STATE_USER_UNMANAGED
+                            != STATE_USER_UNMANAGED
                             || newState != STATE_USER_SETUP_FINALIZED) {
                         throw new IllegalStateException("Not allowed to change provisioning state "
                                 + "unless current provisioning state is unmanaged, and new state"
@@ -10542,9 +10584,9 @@
         }
         // Valid transitions for normal use-cases.
         switch (currentState) {
-            case DevicePolicyManager.STATE_USER_UNMANAGED:
+            case STATE_USER_UNMANAGED:
                 // Can move to any state from unmanaged (except itself as an edge case)..
-                if (newState != DevicePolicyManager.STATE_USER_UNMANAGED) {
+                if (newState != STATE_USER_UNMANAGED) {
                     return;
                 }
                 break;
@@ -10568,7 +10610,7 @@
                 break;
             case DevicePolicyManager.STATE_USER_PROFILE_FINALIZED:
                 // Should only move to an unmanaged state after removing the work profile.
-                if (newState == DevicePolicyManager.STATE_USER_UNMANAGED) {
+                if (newState == STATE_USER_UNMANAGED) {
                     return;
                 }
                 break;
@@ -10940,7 +10982,7 @@
                 UserHandle userHandle = UserHandle.of(userId);
                 userContext = mContext.createPackageContextAsUser(packageName, /* flags= */ 0,
                         userHandle);
-            } catch (PackageManager.NameNotFoundException nnfe) {
+            } catch (NameNotFoundException nnfe) {
                 Slogf.w(LOG_TAG, nnfe, "%s is not installed for user %d", packageName, userId);
                 return null;
             }
@@ -11160,20 +11202,20 @@
     }
 
     private boolean canQueryAdminPolicy(CallerIdentity caller) {
-        return hasCallingOrSelfPermission(permission.QUERY_ADMIN_POLICY);
+        return hasCallingOrSelfPermission(QUERY_ADMIN_POLICY);
     }
 
     private boolean hasPermission(String permission, int pid, int uid) {
-        return mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED;
+        return mContext.checkPermission(permission, pid, uid) == PERMISSION_GRANTED;
     }
 
     private boolean hasCallingPermission(String permission) {
-        return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
+        return mContext.checkCallingPermission(permission) == PERMISSION_GRANTED;
     }
 
     private boolean hasCallingOrSelfPermission(String permission) {
         return mContext.checkCallingOrSelfPermission(permission)
-                == PackageManager.PERMISSION_GRANTED;
+                == PERMISSION_GRANTED;
     }
 
     private boolean hasPermissionForPreflight(CallerIdentity caller, String permission) {
@@ -11479,7 +11521,7 @@
 
     private String getEncryptionStatusName(int encryptionStatus) {
         switch (encryptionStatus) {
-            case DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER:
+            case ENCRYPTION_STATUS_ACTIVE_PER_USER:
                 return "per-user";
             case DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED:
                 return "unsupported";
@@ -12561,7 +12603,7 @@
 
             if ((flags & DevicePolicyManager.SKIP_SETUP_WIZARD) != 0) {
                 Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                        Settings.Secure.USER_SETUP_COMPLETE, 1, userHandle);
+                        USER_SETUP_COMPLETE, 1, userHandle);
             }
 
             sendProvisioningCompletedBroadcast(
@@ -13977,8 +14019,8 @@
             List<ResolveInfo> activitiesToEnable = mIPackageManager
                     .queryIntentActivities(intent,
                             intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                            PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                            MATCH_DIRECT_BOOT_AWARE
+                                    | MATCH_DIRECT_BOOT_UNAWARE,
                             parentUserId)
                     .getList();
 
@@ -14865,7 +14907,7 @@
         if (policy == null) {
             // We default on the power button menu, in order to be consistent with pre-P
             // behaviour.
-            return DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+            return LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
         }
         return policy.getFlags();
     }
@@ -14994,7 +15036,7 @@
                         "Permission denial: device owners cannot update %1$s", setting));
             }
 
-            if (Settings.Global.STAY_ON_WHILE_PLUGGED_IN.equals(setting)) {
+            if (Global.STAY_ON_WHILE_PLUGGED_IN.equals(setting)) {
                 // ignore if it contradicts an existing policy
                 long timeMs = getMaximumTimeToLock(
                         who, mInjector.userHandleGetCallingUserId(), /* parent */ false);
@@ -15499,7 +15541,7 @@
         final int N = users.size();
         for (int i = 0; i < N; i++) {
             int userHandle = users.get(i).id;
-            if (mInjector.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
+            if (mInjector.settingsSecureGetIntForUser(USER_SETUP_COMPLETE, 0,
                     userHandle) != 0) {
                 DevicePolicyData policy = getUserData(userHandle);
                 if (!policy.mUserSetupComplete) {
@@ -15527,7 +15569,7 @@
 
     private class SetupContentObserver extends ContentObserver {
         private final Uri mUserSetupComplete = Settings.Secure.getUriFor(
-                Settings.Secure.USER_SETUP_COMPLETE);
+                USER_SETUP_COMPLETE);
         private final Uri mPaired = Settings.Secure.getUriFor(Settings.Secure.DEVICE_PAIRED);
         private final Uri mDefaultImeChanged = Settings.Secure.getUriFor(
                 Settings.Secure.DEFAULT_INPUT_METHOD);
@@ -15575,7 +15617,7 @@
 
     private class DevicePolicyConstantsObserver extends ContentObserver {
         final Uri mConstantsUri =
-                Settings.Global.getUriFor(Settings.Global.DEVICE_POLICY_CONSTANTS);
+                Global.getUriFor(Global.DEVICE_POLICY_CONSTANTS);
 
         DevicePolicyConstantsObserver(Handler handler) {
             super(handler);
@@ -15868,9 +15910,9 @@
                 final int uid = Objects.requireNonNull(
                         mInjector.getPackageManager().getApplicationInfoAsUser(
                                 Objects.requireNonNull(packageName), /* flags= */ 0, userId)).uid;
-                return PackageManager.PERMISSION_GRANTED
+                return PERMISSION_GRANTED
                         == ActivityManager.checkComponentPermission(
-                        android.Manifest.permission.MODIFY_QUIET_MODE, uid, /* owningUid= */
+                        permission.MODIFY_QUIET_MODE, uid, /* owningUid= */
                         -1, /* exported= */ true);
             } catch (NameNotFoundException ex) {
                 Slogf.w(LOG_TAG, "Cannot find the package %s to check for permissions.",
@@ -16007,7 +16049,7 @@
 
         private @Mode int findInteractAcrossProfilesResetMode(String packageName) {
             return getDefaultCrossProfilePackages().contains(packageName)
-                    ? AppOpsManager.MODE_ALLOWED
+                    ? MODE_ALLOWED
                     : AppOpsManager.opToDefaultMode(AppOpsManager.OP_INTERACT_ACROSS_PROFILES);
         }
 
@@ -16326,7 +16368,8 @@
         if (admin.mPasswordPolicy.quality < minPasswordQuality) {
             return false;
         }
-        return admin.isPermissionBased || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+        return (!Flags.activeAdminCleanup() && admin.isPermissionBased)
+                || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
     }
 
     @Override
@@ -16732,13 +16775,13 @@
             synchronized (getLockObject()) {
                 long ident = mInjector.binderClearCallingIdentity();
                 boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
-                        >= android.os.Build.VERSION_CODES.Q;
+                        >= Build.VERSION_CODES.Q;
 
                 try {
                     if (!isPostQAdmin) {
                         // Legacy admins assume that they cannot control pre-M apps
                         if (getTargetSdk(packageName, caller.getUserId())
-                                < android.os.Build.VERSION_CODES.M) {
+                                < Build.VERSION_CODES.M) {
                             callback.sendResult(null);
                             return;
                         }
@@ -16749,7 +16792,7 @@
                     }
                     if (grantState == PERMISSION_GRANT_STATE_GRANTED
                             || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
-                            || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
+                            || grantState == PERMISSION_GRANT_STATE_DEFAULT) {
                         AdminPermissionControlParams permissionParams =
                                 new AdminPermissionControlParams(packageName, permission,
                                         grantState,
@@ -16784,26 +16827,26 @@
 
     private static final List<String> SENSOR_PERMISSIONS = new ArrayList<>();
     {
-        SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
-        SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
-        SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
-        SENSOR_PERMISSIONS.add(Manifest.permission.CAMERA);
-        SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_AUDIO);
-        SENSOR_PERMISSIONS.add(Manifest.permission.ACTIVITY_RECOGNITION);
-        SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
-        SENSOR_PERMISSIONS.add(Manifest.permission.BACKGROUND_CAMERA);
-        SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_BACKGROUND_AUDIO);
-        SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
+        SENSOR_PERMISSIONS.add(permission.ACCESS_FINE_LOCATION);
+        SENSOR_PERMISSIONS.add(permission.ACCESS_BACKGROUND_LOCATION);
+        SENSOR_PERMISSIONS.add(permission.ACCESS_COARSE_LOCATION);
+        SENSOR_PERMISSIONS.add(permission.CAMERA);
+        SENSOR_PERMISSIONS.add(permission.RECORD_AUDIO);
+        SENSOR_PERMISSIONS.add(permission.ACTIVITY_RECOGNITION);
+        SENSOR_PERMISSIONS.add(permission.BODY_SENSORS);
+        SENSOR_PERMISSIONS.add(permission.BACKGROUND_CAMERA);
+        SENSOR_PERMISSIONS.add(permission.RECORD_BACKGROUND_AUDIO);
+        SENSOR_PERMISSIONS.add(permission.BODY_SENSORS_BACKGROUND);
     }
 
     private boolean canGrantPermission(CallerIdentity caller, String permission,
             String targetPackageName) {
         boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
-                >= android.os.Build.VERSION_CODES.Q;
+                >= Build.VERSION_CODES.Q;
         if (!isPostQAdmin) {
             // Legacy admins assume that they cannot control pre-M apps
             if (getTargetSdk(targetPackageName, caller.getUserId())
-                    < android.os.Build.VERSION_CODES.M) {
+                    < Build.VERSION_CODES.M) {
                 return false;
             }
         }
@@ -16850,7 +16893,7 @@
             throws RemoteException {
         int granted;
         if (getTargetSdk(caller.getPackageName(), caller.getUserId())
-                < android.os.Build.VERSION_CODES.Q) {
+                < Build.VERSION_CODES.Q) {
             // The per-Q behavior was to not check the app-ops state.
             granted = mIPackageManager.checkPermission(permission, packageName, userId);
         } else {
@@ -16859,11 +16902,11 @@
                 if (packageState == null) {
                     Slog.w(LOG_TAG, "Can't get permission state for missing package "
                             + packageName);
-                    return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+                    return PERMISSION_GRANT_STATE_DEFAULT;
                 } else if (!packageState.getUserStateOrDefault(userId).isInstalled()) {
                     Slog.w(LOG_TAG, "Can't get permission state for uninstalled package "
                             + packageName);
-                    return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+                    return PERMISSION_GRANT_STATE_DEFAULT;
                 } else {
                     if (PermissionChecker.checkPermissionForPreflight(mContext, permission,
                             PermissionChecker.PID_UNKNOWN,
@@ -16871,7 +16914,7 @@
                             != PermissionChecker.PERMISSION_GRANTED) {
                         granted = PackageManager.PERMISSION_DENIED;
                     } else {
-                        granted = PackageManager.PERMISSION_GRANTED;
+                        granted = PERMISSION_GRANTED;
                     }
 
                 }
@@ -16882,11 +16925,11 @@
         if ((permFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED)
                 != PackageManager.FLAG_PERMISSION_POLICY_FIXED) {
             // Not controlled by policy
-            return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+            return PERMISSION_GRANT_STATE_DEFAULT;
         } else {
             // Policy controlled so return result based on permission grant state
-            return granted == PackageManager.PERMISSION_GRANTED
-                    ? DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
+            return granted == PERMISSION_GRANTED
+                    ? PERMISSION_GRANT_STATE_GRANTED
                     : DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
         }
     }
@@ -17006,9 +17049,9 @@
         }
         if (action != null) {
             switch (action) {
-                case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE:
+                case ACTION_PROVISION_MANAGED_PROFILE:
                     return checkManagedProfileProvisioningPreCondition(packageName, userId);
-                case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
+                case ACTION_PROVISION_MANAGED_DEVICE:
                 case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
                     return checkDeviceOwnerProvisioningPreCondition(componentName, userId);
             }
@@ -18355,7 +18398,7 @@
                 hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         boolean isUserCompleted = mInjector.settingsSecureGetIntForUser(
-                Settings.Secure.USER_SETUP_COMPLETE, 0, userId) != 0;
+                USER_SETUP_COMPLETE, 0, userId) != 0;
         DevicePolicyData policy = getUserData(userId);
         policy.mUserSetupComplete = isUserCompleted;
         mStateCache.setDeviceProvisioned(isUserCompleted);
@@ -19986,7 +20029,7 @@
     }
 
     private boolean isDeviceAB() {
-        return "true".equalsIgnoreCase(android.os.SystemProperties
+        return "true".equalsIgnoreCase(SystemProperties
                 .get(AB_DEVICE_KEY, ""));
     }
 
@@ -20253,7 +20296,7 @@
         return mOwners.hasDeviceOwner()
                 && mInjector.getIActivityManager().getLockTaskModeState()
                         == ActivityManager.LOCK_TASK_MODE_LOCKED
-                && !isLockTaskFeatureEnabled(DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO)
+                && !isLockTaskFeatureEnabled(LOCK_TASK_FEATURE_SYSTEM_INFO)
                 && !deviceHasKeyguard()
                 && !inEphemeralUserSession();
     }
@@ -20264,7 +20307,7 @@
         int lockTaskFeatures = policy == null
                 // We default on the power button menu, in order to be consistent with pre-P
                 // behaviour.
-                ? DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+                ? LOCK_TASK_FEATURE_GLOBAL_ACTIONS
                 : policy.getFlags();
         return (lockTaskFeatures & lockTaskFeature) == lockTaskFeature;
     }
@@ -21010,7 +21053,7 @@
 
     private boolean canHandleCheckPolicyComplianceIntent(CallerIdentity caller) {
         mInjector.binderWithCleanCallingIdentity(() -> {
-            final Intent intent = new Intent(DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE);
+            final Intent intent = new Intent(ACTION_CHECK_POLICY_COMPLIANCE);
             intent.setPackage(caller.getPackageName());
             final List<ResolveInfo> handlers =
                     mInjector.getPackageManager().queryIntentActivitiesAsUser(intent, /* flags= */
@@ -21219,6 +21262,17 @@
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
+        if (Flags.splitCreateManagedProfileEnabled()) {
+            return mInjector.binderWithCleanCallingIdentity(() -> {
+                UserHandle managedProfileUser =
+                        createManagedProfileInternal(provisioningParams, caller);
+                maybeMigrateAccount(managedProfileUser.getIdentifier(), caller.getUserId(),
+                        provisioningParams.getAccountToMigrate(),
+                        provisioningParams.isKeepingAccountOnMigration(), callerPackage);
+                finalizeCreateManagedProfileInternal(provisioningParams, managedProfileUser);
+                return managedProfileUser;
+            });
+        }
         provisioningParams.logParams(callerPackage);
 
         UserInfo userInfo = null;
@@ -21312,6 +21366,130 @@
     }
 
     @Override
+    public UserHandle createManagedProfile(
+            @NonNull ManagedProfileProvisioningParams provisioningParams,
+            @NonNull String callerPackage) {
+        Objects.requireNonNull(provisioningParams, "provisioningParams is null");
+        Objects.requireNonNull(callerPackage, "callerPackage is null");
+        Objects.requireNonNull(provisioningParams.getProfileAdminComponentName(), "admin is null");
+        Preconditions.checkCallAuthorization(
+                hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
+        CallerIdentity caller = getCallerIdentity(callerPackage);
+
+        return mInjector.binderWithCleanCallingIdentity(() ->
+                createManagedProfileInternal(provisioningParams, caller));
+    }
+
+    private UserHandle createManagedProfileInternal(
+            @NonNull ManagedProfileProvisioningParams provisioningParams,
+            @NonNull CallerIdentity caller) {
+        provisioningParams.logParams(caller.getPackageName());
+        final ComponentName admin = provisioningParams.getProfileAdminComponentName();
+        final int callingUserId = caller.getUserId();
+        UserInfo userInfo = null;
+        try {
+            final int result = checkProvisioningPreconditionSkipPermission(
+                    ACTION_PROVISION_MANAGED_PROFILE, admin, callingUserId);
+            if (result != STATUS_OK) {
+                throw new ServiceSpecificException(
+                        ERROR_PRE_CONDITION_FAILED,
+                        "Provisioning preconditions failed with result: " + result);
+            }
+
+            final long startTime = SystemClock.elapsedRealtime();
+
+            onCreateAndProvisionManagedProfileStarted(provisioningParams);
+
+            userInfo = createProfileForUser(provisioningParams, callingUserId);
+            if (userInfo == null) {
+                throw new ServiceSpecificException(
+                        ERROR_PROFILE_CREATION_FAILED,
+                        "Error creating profile, createProfileForUserEvenWhenDisallowed "
+                                + "returned null.");
+            }
+            resetInteractAcrossProfilesAppOps(caller.getUserId());
+            logEventDuration(
+                    DevicePolicyEnums.PLATFORM_PROVISIONING_CREATE_PROFILE_MS,
+                    startTime,
+                    caller.getPackageName());
+
+            maybeInstallDevicePolicyManagementRoleHolderInUser(userInfo.id);
+            installExistingAdminPackage(userInfo.id, admin.getPackageName());
+
+            if (!enableAdminAndSetProfileOwner(userInfo.id, caller.getUserId(), admin)) {
+                throw new ServiceSpecificException(
+                        ERROR_SETTING_PROFILE_OWNER_FAILED,
+                        "Error setting profile owner.");
+            }
+            setUserSetupComplete(userInfo.id);
+            startProfileForSetup(userInfo.id, caller.getPackageName());
+
+            if (provisioningParams.isOrganizationOwnedProvisioning()) {
+                synchronized (getLockObject()) {
+                    setProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(admin, userInfo.id,
+                            true);
+                }
+            }
+            return userInfo.getUserHandle();
+        } catch (Exception e) {
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_ERROR)
+                    .setStrings(caller.getPackageName())
+                    .write();
+            // In case of any errors during provisioning, remove the newly created profile.
+            if (userInfo != null) {
+                mUserManager.removeUserEvenWhenDisallowed(userInfo.id);
+            }
+            throw e;
+        }
+    }
+
+    private UserInfo createProfileForUser(ManagedProfileProvisioningParams params, int userId) {
+        final Set<String> nonRequiredApps = params.isLeaveAllSystemAppsEnabled()
+                ? Collections.emptySet()
+                : mOverlayPackagesProvider.getNonRequiredApps(params.getProfileAdminComponentName(),
+                        userId, ACTION_PROVISION_MANAGED_PROFILE);
+        if (nonRequiredApps.isEmpty()) {
+            Slogf.i(LOG_TAG, "No disallowed packages for the managed profile.");
+        } else {
+            for (String packageName : nonRequiredApps) {
+                Slogf.i(LOG_TAG, "Disallowed package [" + packageName + "]");
+            }
+        }
+        return mUserManager.createProfileForUserEvenWhenDisallowed(
+                params.getProfileName(),
+                UserManager.USER_TYPE_PROFILE_MANAGED,
+                UserInfo.FLAG_DISABLED,
+                userId,
+                nonRequiredApps.toArray(new String[nonRequiredApps.size()]));
+    }
+
+    @Override
+    public void finalizeCreateManagedProfile(
+            @NonNull ManagedProfileProvisioningParams provisioningParams,
+            @NonNull UserHandle managedProfileUser) {
+        Objects.requireNonNull(provisioningParams, "provisioningParams is null");
+        Objects.requireNonNull(managedProfileUser, "managedProfileUser is null");
+        Preconditions.checkCallAuthorization(
+                hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
+        mInjector.binderWithCleanCallingIdentity(() -> {
+            finalizeCreateManagedProfileInternal(provisioningParams, managedProfileUser);
+        });
+    }
+
+    private void finalizeCreateManagedProfileInternal(
+            @NonNull ManagedProfileProvisioningParams provisioningParams,
+            @NonNull UserHandle managedProfileUser
+    ) {
+        onCreateAndProvisionManagedProfileCompleted(provisioningParams);
+        sendProvisioningCompletedBroadcast(
+                managedProfileUser.getIdentifier(),
+                ACTION_PROVISION_MANAGED_PROFILE,
+                provisioningParams.isLeaveAllSystemAppsEnabled());
+    }
+
+    @Override
     public void finalizeWorkProfileProvisioning(UserHandle managedProfileUser,
             Account migratedAccount) {
         Preconditions.checkCallAuthorization(
@@ -21481,7 +21659,7 @@
 
     private void pregrantDefaultInteractAcrossProfilesAppOps(@UserIdInt int userId) {
         final String op =
-                AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES);
+                AppOpsManager.permissionToOp(permission.INTERACT_ACROSS_PROFILES);
         for (String packageName : getConfigurableDefaultCrossProfilePackages(userId)) {
             if (!appOpIsDefaultOrAllowed(userId, op, packageName)) {
                 continue;
@@ -21684,7 +21862,8 @@
                 Slogf.i(LOG_TAG, "Account removed from the primary user.");
             } else {
                 // TODO(174768447): Revisit start activity logic.
-                final Intent removeIntent = result.getParcelable(AccountManager.KEY_INTENT, android.content.Intent.class);
+                final Intent removeIntent =
+                        result.getParcelable(AccountManager.KEY_INTENT, Intent.class);
                 removeIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
                 if (removeIntent != null) {
                     Slogf.i(LOG_TAG, "Starting activity to remove account");
@@ -21980,7 +22159,7 @@
         }
         synchronized (getLockObject()) {
             mInjector.settingsGlobalPutStringForUser(
-                    Settings.Global.DEVICE_DEMO_MODE, Integer.toString(/* value= */ 1), userId);
+                    Global.DEVICE_DEMO_MODE, Integer.toString(/* value= */ 1), userId);
         }
 
         setUserProvisioningState(STATE_USER_SETUP_FINALIZED, userId);
@@ -22243,7 +22422,7 @@
     @Override
     public boolean isDevicePotentiallyStolen(String callerPackageName) {
         final CallerIdentity caller = getCallerIdentity(callerPackageName);
-        if (!android.app.admin.flags.Flags.deviceTheftImplEnabled()) {
+        if (!Flags.deviceTheftImplEnabled()) {
             return false;
         }
         enforcePermission(QUERY_DEVICE_STOLEN_STATE, caller.getPackageName(),
@@ -22279,7 +22458,7 @@
     @Override
     public void setDrawables(@NonNull List<DevicePolicyDrawableResource> drawables) {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
-                android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+                permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
 
         Objects.requireNonNull(drawables, "drawables must be provided.");
 
@@ -22295,7 +22474,7 @@
     @Override
     public void resetDrawables(@NonNull List<String> drawableIds) {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
-                android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+                permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
 
         Objects.requireNonNull(drawableIds, "drawableIds must be provided.");
 
@@ -22321,7 +22500,7 @@
     @Override
     public void setStrings(@NonNull List<DevicePolicyStringResource> strings) {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
-                android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+                permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
 
         Objects.requireNonNull(strings, "strings must be provided.");
 
@@ -22336,7 +22515,7 @@
     @Override
     public void resetStrings(@NonNull List<String> stringIds) {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
-                android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+                permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
 
         mInjector.binderWithCleanCallingIdentity(() -> {
             if (mDeviceManagementResourcesProvider.removeStrings(stringIds)) {
@@ -22406,7 +22585,7 @@
     @Override
     public void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState() {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
-                android.Manifest.permission.MANAGE_ROLE_HOLDERS));
+                permission.MANAGE_ROLE_HOLDERS));
         setBypassDevicePolicyManagementRoleQualificationStateInternal(
                 /* currentRoleHolder= */ null, /* allowBypass= */ false);
     }
@@ -22414,7 +22593,7 @@
     @Override
     public boolean shouldAllowBypassingDevicePolicyManagementRoleQualification() {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
-                android.Manifest.permission.MANAGE_ROLE_HOLDERS));
+                permission.MANAGE_ROLE_HOLDERS));
         return mInjector.binderWithCleanCallingIdentity(() -> {
             if (getUserData(
                     UserHandle.USER_SYSTEM).mBypassDevicePolicyManagementRoleQualifications) {
@@ -23410,7 +23589,8 @@
             return EnforcingAdmin.createDeviceAdminEnforcingAdmin(admin.info.getComponent(), userId,
                     admin);
         }
-        admin = getUserData(userId).createOrGetPermissionBasedAdmin(userId);
+        admin = Flags.activeAdminCleanup()
+                ? null : getUserData(userId).createOrGetPermissionBasedAdmin(userId);
         return  EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId, admin);
     }
 
@@ -23433,8 +23613,8 @@
                 }
             }
         }
-
-        admin = getUserData(userId).createOrGetPermissionBasedAdmin(userId);
+        admin = Flags.activeAdminCleanup()
+                ? null : getUserData(userId).createOrGetPermissionBasedAdmin(userId);
         return  EnforcingAdmin.createEnforcingAdmin(packageName, userId, admin);
     }
 
@@ -24006,7 +24186,7 @@
                     if (!isRuntimePermission(permission)) {
                         continue;
                     }
-                    int grantState = DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+                    int grantState = PERMISSION_GRANT_STATE_DEFAULT;
                     try {
                         grantState = getPermissionGrantStateForUser(
                                 packageInfo.packageName, permission,
@@ -24019,7 +24199,7 @@
                         Slogf.e(LOG_TAG, e, "Error retrieving permission grant state for %s "
                                         + "and %s", packageInfo.packageName, permission);
                     }
-                    if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
+                    if (grantState == PERMISSION_GRANT_STATE_DEFAULT) {
                         // Not Controlled by a policy
                         continue;
                     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 58e3a7d..1fd628a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -23,6 +23,7 @@
 import android.app.admin.DpcAuthority;
 import android.app.admin.RoleAuthority;
 import android.app.admin.UnknownAuthority;
+import android.app.admin.flags.Flags;
 import android.content.ComponentName;
 import android.os.UserHandle;
 
@@ -295,9 +296,17 @@
 
     @Nullable
     public ActiveAdmin getActiveAdmin() {
+        if (Flags.activeAdminCleanup()) {
+            throw new UnsupportedOperationException("getActiveAdmin() no longer supported");
+        }
         return mActiveAdmin;
     }
 
+    @Nullable
+    ComponentName getComponentName() {
+        return mComponentName;
+    }
+
     @NonNull
     android.app.admin.EnforcingAdmin getParcelableAdmin() {
         Authority authority;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a01ea64..3e7c4ef 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2769,8 +2769,9 @@
             mSystemServiceManager.startService(WEAR_MODE_SERVICE_CLASS);
             t.traceEnd();
 
-            boolean enableWristOrientationService = SystemProperties.getBoolean(
-                    "config.enable_wristorientation", false);
+            boolean enableWristOrientationService =
+                    !android.server.Flags.migrateWristOrientation()
+                    && SystemProperties.getBoolean("config.enable_wristorientation", false);
             if (enableWristOrientationService) {
                 t.traceBegin("StartWristOrientationService");
                 mSystemServiceManager.startService(WRIST_ORIENTATION_SERVICE_CLASS);
diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig
index e2ac22d..4412968 100644
--- a/services/java/com/android/server/flags.aconfig
+++ b/services/java/com/android/server/flags.aconfig
@@ -39,6 +39,14 @@
 }
 
 flag {
+     name: "migrate_wrist_orientation"
+     namespace: "wear_frameworks"
+     description: "Migrate wrist orientation service functionality to wear settings service"
+     bug: "352725980"
+     is_fixed_read_only: true
+}
+
+flag {
     name: "allow_network_time_update_service"
     namespace: "wear_systems"
     description: "Allow NetworkTimeUpdateService on Wear"
diff --git a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
index 08155c7..9772ef9 100644
--- a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
+++ b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
@@ -2380,10 +2380,14 @@
     @Test
     public void doTestMigrateIkeSession_Vcn() throws Exception {
         final int expectedKeepalive = 2097; // Any unlikely number will do
-        final NetworkCapabilities vcnNc = new NetworkCapabilities.Builder()
-                .addTransportType(TRANSPORT_CELLULAR)
-                .setTransportInfo(new VcnTransportInfo(TEST_SUB_ID, expectedKeepalive))
-                .build();
+        final NetworkCapabilities vcnNc =
+                new NetworkCapabilities.Builder()
+                        .addTransportType(TRANSPORT_CELLULAR)
+                        .setTransportInfo(
+                                new VcnTransportInfo.Builder()
+                                        .setMinUdpPort4500NatTimeoutSeconds(expectedKeepalive)
+                                        .build())
+                        .build();
         final Ikev2VpnProfile ikev2VpnProfile = makeIkeV2VpnProfile(
                 true /* isAutomaticIpVersionSelectionEnabled */,
                 true /* isAutomaticNattKeepaliveTimerEnabled */,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index dcbc234..5a872ea 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -47,10 +47,8 @@
 import static com.android.server.am.ProcessList.NETWORK_STATE_BLOCK;
 import static com.android.server.am.ProcessList.NETWORK_STATE_NO_CHANGE;
 import static com.android.server.am.ProcessList.NETWORK_STATE_UNBLOCK;
-
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -80,6 +78,7 @@
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.ApplicationThreadConstants;
 import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
 import android.app.ForegroundServiceDelegationOptions;
@@ -87,6 +86,7 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.SyncNotedAppOp;
+import android.app.backup.BackupAnnotations;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -111,6 +111,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.permission.IPermissionManager;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -133,6 +134,7 @@
 import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
 import com.android.server.am.UidObserverController.ChangeRecord;
 import com.android.server.appop.AppOpsService;
+import com.android.server.job.JobSchedulerInternal;
 import com.android.server.notification.NotificationManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.ActivityTaskManagerService;
@@ -228,6 +230,7 @@
     @Mock private PackageManagerInternal mPackageManagerInternal;
     @Mock private ActivityTaskManagerInternal mActivityTaskManagerInternal;
     @Mock private NotificationManagerInternal mNotificationManagerInternal;
+    @Mock private JobSchedulerInternal mJobSchedulerInternal;
     @Mock private ContentResolver mContentResolver;
 
     private TestInjector mInjector;
@@ -249,6 +252,7 @@
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
         LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInternal);
         LocalServices.addService(NotificationManagerInternal.class, mNotificationManagerInternal);
+        LocalServices.addService(JobSchedulerInternal.class, mJobSchedulerInternal);
 
         doReturn(new ComponentName("", "")).when(mPackageManagerInternal)
                 .getSystemUiServiceComponent();
@@ -308,6 +312,7 @@
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
         LocalServices.removeServiceForTest(NotificationManagerInternal.class);
+        LocalServices.removeServiceForTest(JobSchedulerInternal.class);
 
         if (mMockingSession != null) {
             mMockingSession.finishMocking();
@@ -1548,6 +1553,50 @@
                         eq(notificationId), anyInt());
     }
 
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void bindBackupAgent_fullBackup_shouldUseRestrictedMode_setsInFullBackup()
+            throws Exception {
+        ActivityManagerService spyAms = spy(mAms);
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.packageName = TEST_PACKAGE;
+        applicationInfo.processName = TEST_PACKAGE;
+        applicationInfo.uid = TEST_UID;
+        doReturn(applicationInfo).when(mPackageManager).getApplicationInfo(eq(TEST_PACKAGE),
+                anyLong(), anyInt());
+        ProcessRecord appRec = new ProcessRecord(mAms, applicationInfo, TAG, TEST_UID);
+        doReturn(appRec).when(spyAms).getProcessRecordLocked(eq(TEST_PACKAGE), eq(TEST_UID));
+
+        spyAms.bindBackupAgent(TEST_PACKAGE, ApplicationThreadConstants.BACKUP_MODE_FULL,
+                UserHandle.USER_SYSTEM,
+                BackupAnnotations.BackupDestination.CLOUD, /* shouldUseRestrictedMode= */
+                true);
+
+        assertThat(appRec.isInFullBackup()).isTrue();
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void bindBackupAgent_fullBackup_shouldNotUseRestrictedMode_doesNotSetInFullBackup()
+            throws Exception {
+        ActivityManagerService spyAms = spy(mAms);
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.packageName = TEST_PACKAGE;
+        applicationInfo.processName = TEST_PACKAGE;
+        applicationInfo.uid = TEST_UID;
+        doReturn(applicationInfo).when(mPackageManager).getApplicationInfo(eq(TEST_PACKAGE),
+                anyLong(), anyInt());
+        ProcessRecord appRec = new ProcessRecord(mAms, applicationInfo, TAG, TEST_UID);
+        doReturn(appRec).when(spyAms).getProcessRecordLocked(eq(TEST_PACKAGE), eq(TEST_UID));
+
+        spyAms.bindBackupAgent(TEST_PACKAGE, ApplicationThreadConstants.BACKUP_MODE_FULL,
+                UserHandle.USER_SYSTEM,
+                BackupAnnotations.BackupDestination.CLOUD, /* shouldUseRestrictedMode= */
+                false);
+
+        assertThat(appRec.isInFullBackup()).isFalse();
+    }
+
     private static class TestHandler extends Handler {
         private static final long WAIT_FOR_MSG_TIMEOUT_MS = 4000; // 4 sec
         private static final long WAIT_FOR_MSG_INTERVAL_MS = 400; // 0.4 sec
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index 1caa02a..5eb23a2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -58,6 +58,7 @@
 import com.android.server.wm.ActivityTaskManagerService;
 
 import org.junit.Rule;
+import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -187,8 +188,8 @@
 
         doReturn(true).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
                 eq(BroadcastFilter.RESTRICT_PRIORITY_VALUES), any(ApplicationInfo.class));
-        doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), anyInt());
+        doReturn(true).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), any(ApplicationInfo.class));
     }
 
     public void tearDown() throws Exception {
@@ -308,4 +309,8 @@
         app.mOptRecord.setPendingFreeze(pendingFreeze);
         app.mOptRecord.setFrozen(frozen);
     }
+
+    ArgumentMatcher<ApplicationInfo> appInfoEquals(int uid) {
+        return test -> (test.uid == uid);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 88caaa6..82237bc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -47,6 +47,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doAnswer;
@@ -184,7 +185,10 @@
     }
 
     private static BroadcastFilter makeMockRegisteredReceiver() {
-        return mock(BroadcastFilter.class);
+        final BroadcastFilter filter = mock(BroadcastFilter.class);
+        final ApplicationInfo info = makeApplicationInfo(PACKAGE_ORANGE);
+        doReturn(info).when(filter).getApplicationInfo();
+        return filter;
     }
 
     private BroadcastRecord makeBroadcastRecord(Intent intent) {
@@ -716,9 +720,9 @@
     @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
     @Test
     public void testRunnableAt_Cached_Prioritized_NonDeferrable_changeIdDisabled() {
-        doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+        doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
                 eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
-                eq(getUidForPackage(PACKAGE_GREEN)));
+                argThat(appInfoEquals(getUidForPackage(PACKAGE_GREEN))));
         final List receivers = List.of(
                 withPriority(makeManifestReceiver(PACKAGE_RED, PACKAGE_RED), 10),
                 withPriority(makeManifestReceiver(PACKAGE_GREEN, PACKAGE_GREEN), -10));
@@ -1288,9 +1292,9 @@
     @SuppressWarnings("GuardedBy")
     @Test
     public void testDeliveryGroupPolicy_prioritized_diffReceivers_changeIdDisabled() {
-        doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+        doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
                 eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
-                eq(getUidForPackage(PACKAGE_GREEN)));
+                argThat(appInfoEquals(getUidForPackage(PACKAGE_GREEN))));
 
         final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
         final Intent screenOff = new Intent(Intent.ACTION_SCREEN_OFF);
@@ -1823,9 +1827,9 @@
     @SuppressWarnings("GuardedBy")
     @Test
     public void testDeliveryDeferredForCached_changeIdDisabled() throws Exception {
-        doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+        doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
                 eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
-                eq(getUidForPackage(PACKAGE_GREEN)));
+                argThat(appInfoEquals(getUidForPackage(PACKAGE_GREEN))));
 
         final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
         final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
@@ -2027,9 +2031,9 @@
     @Test
     public void testDeliveryDeferredForCached_withInfiniteDeferred_changeIdDisabled()
             throws Exception {
-        doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+        doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
                 eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
-                eq(getUidForPackage(PACKAGE_GREEN)));
+                argThat(appInfoEquals(getUidForPackage(PACKAGE_GREEN))));
         final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
         final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index a38ef78..ea80f28 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1659,8 +1659,9 @@
         final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
         final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
 
-        doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(receiverBlueApp.uid));
+        doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
+                argThat(appInfoEquals(receiverBlueApp.uid)));
 
         // Enqueue a normal broadcast that will go to several processes, and
         // then enqueue a foreground broadcast that risks reordering
@@ -2471,8 +2472,9 @@
         final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
         final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
 
-        doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(receiverBlueApp.uid));
+        doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
+                argThat(appInfoEquals(receiverBlueApp.uid)));
 
         mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
                 ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index f9f3790..8482fd6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -19,12 +19,12 @@
 import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.server.am.BroadcastRecord.LIMIT_PRIORITY_SCOPE;
 import static com.android.server.am.BroadcastRecord.DELIVERY_DEFERRED;
 import static com.android.server.am.BroadcastRecord.DELIVERY_DELIVERED;
 import static com.android.server.am.BroadcastRecord.DELIVERY_PENDING;
 import static com.android.server.am.BroadcastRecord.DELIVERY_SKIPPED;
 import static com.android.server.am.BroadcastRecord.DELIVERY_TIMEOUT;
+import static com.android.server.am.BroadcastRecord.LIMIT_PRIORITY_SCOPE;
 import static com.android.server.am.BroadcastRecord.calculateBlockedUntilBeyondCount;
 import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive;
 import static com.android.server.am.BroadcastRecord.calculateUrgent;
@@ -35,7 +35,8 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 
 import android.app.BackgroundStartPrivileges;
@@ -63,6 +64,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.junit.MockitoJUnitRunner;
@@ -108,8 +110,8 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), anyInt());
+        doReturn(true).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), any(ApplicationInfo.class));
     }
 
     @Test
@@ -222,8 +224,8 @@
     @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
     @Test
     public void testIsPrioritized_withDifferentPriorities_withFirstUidChangeIdDisabled() {
-        doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
+        doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(1))));
 
         assertTrue(isPrioritized(List.of(
                 createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -256,8 +258,8 @@
     @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
     @Test
     public void testIsPrioritized_withDifferentPriorities_withLastUidChangeIdDisabled() {
-        doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(3)));
+        doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(3))));
 
         assertTrue(isPrioritized(List.of(
                 createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -294,8 +296,8 @@
     @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
     @Test
     public void testIsPrioritized_withDifferentPriorities_withUidChangeIdDisabled() {
-        doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
+        doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(2))));
 
         assertTrue(isPrioritized(List.of(
                 createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -328,10 +330,10 @@
     @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
     @Test
     public void testIsPrioritized_withDifferentPriorities_withMultipleUidChangeIdDisabled() {
-        doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
-        doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
+        doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(1))));
+        doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(2))));
 
         assertTrue(isPrioritized(List.of(
                 createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -362,10 +364,10 @@
         assertArrayEquals(new int[] {0, 0, 1, 1, 3},
                 calculateBlockedUntilBeyondCount(List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), 20),
-                        createResolveInfo(PACKAGE2, getAppId(3), 20),
+                        createResolveInfo(PACKAGE3, getAppId(3), 20),
                         createResolveInfo(PACKAGE3, getAppId(3), 10),
                         createResolveInfo(PACKAGE3, getAppId(3), 0),
-                        createResolveInfo(PACKAGE3, getAppId(2), 0)), false, mPlatformCompat));
+                        createResolveInfo(PACKAGE2, getAppId(2), 0)), false, mPlatformCompat));
     }
 
     @Test
@@ -592,8 +594,8 @@
     @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
     @Test
     public void testSetDeliveryState_DeferUntilActive_changeIdDisabled() {
-        doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
+        doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(1))));
         final BroadcastRecord r = createBroadcastRecord(
                 new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -960,8 +962,8 @@
                         createResolveInfo(PACKAGE2, getAppId(2)),
                         createResolveInfo(PACKAGE3, getAppId(3)))));
 
-        doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
+        doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(1))));
         assertArrayEquals(new boolean[] {false, true, true}, calculateChangeState(
                 List.of(createResolveInfo(PACKAGE1, getAppId(1)),
                         createResolveInfo(PACKAGE2, getAppId(2)),
@@ -969,11 +971,11 @@
         assertArrayEquals(new boolean[] {false, true, false, true}, calculateChangeState(
                 List.of(createResolveInfo(PACKAGE1, getAppId(1)),
                         createResolveInfo(PACKAGE2, getAppId(2)),
-                        createResolveInfo(PACKAGE2, getAppId(1)),
+                        createResolveInfo(PACKAGE1, getAppId(1)),
                         createResolveInfo(PACKAGE3, getAppId(3)))));
 
-        doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
+        doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(2))));
         assertArrayEquals(new boolean[] {false, false, true}, calculateChangeState(
                 List.of(createResolveInfo(PACKAGE1, getAppId(1)),
                         createResolveInfo(PACKAGE2, getAppId(2)),
@@ -987,8 +989,8 @@
                                 createResolveInfo(PACKAGE2, getAppId(2)),
                                 createResolveInfo(PACKAGE3, getAppId(3)))));
 
-        doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(3)));
+        doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(3))));
         assertArrayEquals(new boolean[] {false, false, false}, calculateChangeState(
                 List.of(createResolveInfo(PACKAGE1, getAppId(1)),
                         createResolveInfo(PACKAGE2, getAppId(2)),
@@ -998,7 +1000,7 @@
                         List.of(createResolveInfo(PACKAGE1, getAppId(1)),
                                 createResolveInfo(PACKAGE3, getAppId(3)),
                                 createResolveInfo(PACKAGE2, getAppId(2)),
-                                createResolveInfo(PACKAGE2, getAppId(1)),
+                                createResolveInfo(PACKAGE1, getAppId(1)),
                                 createResolveInfo(PACKAGE2, getAppId(2)),
                                 createResolveInfo(PACKAGE3, getAppId(3)))));
     }
@@ -1185,4 +1187,8 @@
         assertEquals("deferred", expectedDeferredCount, r.deferredCount);
         assertEquals("beyond", expectedBeyondCount, r.beyondCount);
     }
+
+    private ArgumentMatcher<ApplicationInfo> appInfoEquals(int uid) {
+        return test -> (test.uid == uid);
+    }
 }
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 f82a860..94cf4cb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -3306,7 +3306,7 @@
         if (Flags.pushGlobalStateToOomadjuster()) {
             mProcessStateController.setBackupTarget(app, app.userId);
         } else {
-            BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0);
+            BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0, true);
             backupTarget.app = app;
             doReturn(backupTarget).when(mService.mBackupTargets).get(anyInt());
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 65286d9..07f2188 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -18,9 +18,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -32,20 +30,27 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.ApplicationThreadConstants;
+import android.app.IActivityManager;
 import android.app.backup.BackupAgent;
-import android.app.backup.BackupAnnotations;
 import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
 import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IBackupObserver;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
+import android.compat.testing.PlatformCompatChangeRule;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.os.Handler;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.testing.TestableContext;
 import android.util.FeatureFlagUtils;
@@ -68,7 +73,9 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -77,8 +84,12 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
 import java.util.function.IntConsumer;
 
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class UserBackupManagerServiceTest {
@@ -88,6 +99,11 @@
     private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 100;
     @UserIdInt private static final int USER_ID = 0;
 
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock IBackupManagerMonitor mBackupManagerMonitor;
     @Mock IBackupObserver mBackupObserver;
     @Mock PackageManager mPackageManager;
@@ -99,10 +115,14 @@
     @Mock JobScheduler mJobScheduler;
     @Mock BackupHandler mBackupHandler;
     @Mock BackupManagerMonitorEventSender mBackupManagerMonitorEventSender;
+    @Mock IActivityManager mActivityManager;
+    @Mock
+    ActivityManagerInternal mActivityManagerInternal;
 
     private TestableContext mContext;
     private MockitoSession mSession;
     private TestBackupService mService;
+    private ApplicationInfo mTestPackageApplicationInfo;
 
     @Before
     public void setUp() throws Exception {
@@ -120,12 +140,14 @@
         mContext.getTestablePermissions().setPermission(android.Manifest.permission.BACKUP,
                 PackageManager.PERMISSION_GRANTED);
 
-        mService = new TestBackupService(mContext, mPackageManager, mOperationStorage,
-                mTransportManager, mBackupHandler);
+        mService = new TestBackupService();
         mService.setEnabled(true);
         mService.setSetupComplete(true);
         mService.enqueueFullBackup("com.test.backup.app", /* lastBackedUp= */ 0);
-        }
+
+        mTestPackageApplicationInfo = new ApplicationInfo();
+        mTestPackageApplicationInfo.packageName = TEST_PACKAGE;
+    }
 
     @After
     public void tearDown() {
@@ -298,9 +320,160 @@
                 new DataTypeResult(/* dataType */ "type_2"));
         mService.reportDelayedRestoreResult(TEST_PACKAGE, results);
 
-
         verify(mBackupManagerMonitorEventSender).sendAgentLoggingResults(
-                eq(packageInfo), eq(results), eq(BackupAnnotations.OperationType.RESTORE));
+                eq(packageInfo), eq(results), eq(OperationType.RESTORE));
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+    public void bindToAgentSynchronous_restrictedModeChangesFlagOff_shouldUseRestrictedMode()
+            throws Exception {
+        mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+                ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+        verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+                /* useRestrictedMode= */ eq(true));
+        // Make sure we never hit the code that checks the property.
+        verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+    public void bindToAgentSynchronous_keyValueBackup_shouldNotUseRestrictedMode()
+            throws Exception {
+        mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+                ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL, BackupDestination.CLOUD);
+
+        verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+                /* useRestrictedMode= */ eq(false));
+        // Make sure we never hit the code that checks the property.
+        verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+    public void bindToAgentSynchronous_keyValueRestore_shouldNotUseRestrictedMode()
+            throws Exception {
+        mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+                ApplicationThreadConstants.BACKUP_MODE_RESTORE, BackupDestination.CLOUD);
+
+        verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+                /* useRestrictedMode= */ eq(false));
+        // Make sure we never hit the code that checks the property.
+        verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+    public void bindToAgentSynchronous_packageOptedIn_shouldUseRestrictedMode()
+            throws Exception {
+        when(mPackageManager.getPropertyAsUser(
+                eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+                eq(TEST_PACKAGE), any(), anyInt())).thenReturn(new PackageManager.Property(
+                PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ true,
+                TEST_PACKAGE, /* className= */ null));
+
+        mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+                ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+        verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+                /* useRestrictedMode= */ eq(true));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+    public void bindToAgentSynchronous_packageOptedOut_shouldNotUseRestrictedMode()
+            throws Exception {
+        when(mPackageManager.getPropertyAsUser(
+                eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+                eq(TEST_PACKAGE), any(), anyInt())).thenReturn(new PackageManager.Property(
+                PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ false,
+                TEST_PACKAGE, /* className= */ null));
+
+        mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+                ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+        verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+                /* useRestrictedMode= */ eq(false));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+    @DisableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+    public void bindToAgentSynchronous_targetSdkBelowB_shouldUseRestrictedMode()
+            throws Exception {
+        // Mock that the app has not explicitly set the property.
+        when(mPackageManager.getPropertyAsUser(
+                eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+                eq(TEST_PACKAGE), any(), anyInt())).thenThrow(
+                    new PackageManager.NameNotFoundException()
+        );
+
+        mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+                ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+        verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+                /* useRestrictedMode= */ eq(true));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+    @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+    public void bindToAgentSynchronous_targetSdkB_notInList_shouldUseRestrictedMode()
+            throws Exception {
+        // Mock that the app has not explicitly set the property.
+        when(mPackageManager.getPropertyAsUser(
+                eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+                eq(TEST_PACKAGE), any(), anyInt())).thenThrow(
+                    new PackageManager.NameNotFoundException()
+        );
+        mService.clearNoRestrictedModePackages();
+
+        mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+                ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+        verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+                /* useRestrictedMode= */ eq(true));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+    @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+    public void bindToAgentSynchronous_forRestore_targetSdkB_inList_shouldNotUseRestrictedMode()
+            throws Exception {
+        // Mock that the app has not explicitly set the property.
+        when(mPackageManager.getPropertyAsUser(
+                eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+                eq(TEST_PACKAGE), any(), anyInt())).thenThrow(
+                    new PackageManager.NameNotFoundException()
+        );
+        mService.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.RESTORE);
+
+        mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+                ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL, BackupDestination.CLOUD);
+
+        verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+                /* useRestrictedMode= */ eq(false));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+    @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+    public void bindToAgentSynchronous_forBackup_targetSdkB_inList_shouldNotUseRestrictedMode()
+            throws Exception {
+        // Mock that the app has not explicitly set the property.
+        when(mPackageManager.getPropertyAsUser(
+                eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+                eq(TEST_PACKAGE), any(), anyInt())).thenThrow(
+                    new PackageManager.NameNotFoundException()
+        );
+        mService.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.BACKUP);
+
+        mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+                ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+        verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+                /* useRestrictedMode= */ eq(false));
     }
 
     private static PackageInfo getPackageInfo(String packageName) {
@@ -316,11 +489,9 @@
 
         private volatile Thread mWorkerThread = null;
 
-        TestBackupService(Context context, PackageManager packageManager,
-                LifecycleOperationStorage operationStorage, TransportManager transportManager,
-                BackupHandler backupHandler) {
-            super(context, packageManager, operationStorage, transportManager, backupHandler,
-                    createConstants(context));
+        TestBackupService() {
+            super(mContext, mPackageManager, mOperationStorage, mTransportManager, mBackupHandler,
+                    createConstants(mContext), mActivityManager, mActivityManagerInternal);
         }
 
         private static BackupManagerConstants createConstants(Context context) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
index 9474253..3310573 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
@@ -18,34 +18,95 @@
 
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.when;
 
+import android.app.backup.BackupAnnotations;
+import android.app.backup.BackupTransport;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.UserBackupManagerService;
+import com.android.server.backup.transport.BackupTransportClient;
+import com.android.server.backup.transport.TransportConnection;
+import com.android.server.backup.utils.BackupEligibilityRules;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class PerformFullTransportBackupTaskTest {
+    private static final String TEST_PACKAGE_1 = "package1";
+    private static final String TEST_PACKAGE_2 = "package2";
+
+    @Mock
+    BackupAgentTimeoutParameters mBackupAgentTimeoutParameters;
+    @Mock
+    BackupEligibilityRules mBackupEligibilityRules;
     @Mock
     UserBackupManagerService mBackupManagerService;
     @Mock
+    BackupTransportClient mBackupTransportClient;
+    @Mock
+    CountDownLatch mLatch;
+    @Mock
+    OperationStorage mOperationStorage;
+    @Mock
+    PackageManager mPackageManager;
+    @Mock
+    TransportConnection mTransportConnection;
+    @Mock
     TransportManager mTransportManager;
+    @Mock
+    UserBackupManagerService.BackupWakeLock mWakeLock;
+
+    private final List<String> mEligiblePackages = new ArrayList<>();
+
+    private PerformFullTransportBackupTask mTask;
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        when(mBackupManagerService.getPackageManager()).thenReturn(mPackageManager);
+        when(mBackupManagerService.getQueueLock()).thenReturn("something!");
+        when(mBackupManagerService.isEnabled()).thenReturn(true);
+        when(mBackupManagerService.getWakelock()).thenReturn(mWakeLock);
+        when(mBackupManagerService.isSetupComplete()).thenReturn(true);
+        when(mBackupManagerService.getAgentTimeoutParameters()).thenReturn(
+                mBackupAgentTimeoutParameters);
         when(mBackupManagerService.getTransportManager()).thenReturn(mTransportManager);
+        when(mTransportManager.getCurrentTransportClient(any())).thenReturn(mTransportConnection);
+        when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransportClient);
+        when(mTransportConnection.connect(any())).thenReturn(mBackupTransportClient);
+        when(mBackupTransportClient.performFullBackup(any(), any(), anyInt())).thenReturn(
+                BackupTransport.TRANSPORT_ERROR);
+        when(mBackupEligibilityRules.appIsEligibleForBackup(
+                argThat(app -> mEligiblePackages.contains(app.packageName)))).thenReturn(
+                true);
+        when(mBackupEligibilityRules.appGetsFullBackup(
+                argThat(app -> mEligiblePackages.contains(app.packageName)))).thenReturn(
+                true);
     }
 
     @Test
@@ -70,4 +131,49 @@
                                     /* backupEligibilityRules */  null);
                 });
     }
+
+    @Test
+    public void run_setsAndClearsNoRestrictedModePackages() throws Exception {
+        mockPackageEligibleForFullBackup(TEST_PACKAGE_1);
+        mockPackageEligibleForFullBackup(TEST_PACKAGE_2);
+        createTask(new String[] {TEST_PACKAGE_1, TEST_PACKAGE_2});
+        when(mBackupTransportClient.getPackagesThatShouldNotUseRestrictedMode(any(),
+                anyInt())).thenReturn(Set.of("package1"));
+
+        mTask.run();
+
+        InOrder inOrder = inOrder(mBackupManagerService);
+        inOrder.verify(mBackupManagerService).setNoRestrictedModePackages(
+                eq(Set.of("package1")),
+                eq(BackupAnnotations.OperationType.BACKUP));
+        inOrder.verify(mBackupManagerService).clearNoRestrictedModePackages();
+    }
+
+    private void createTask(String[] packageNames) {
+        mTask = PerformFullTransportBackupTask
+                .newWithCurrentTransport(
+                        mBackupManagerService,
+                        mOperationStorage,
+                        /* observer */  null,
+                        /* whichPackages */  packageNames,
+                        /* updateSchedule */  false,
+                        /* runningJob */  null,
+                        mLatch,
+                        /* backupObserver */  null,
+                        /* monitor */  null,
+                        /* userInitiated */  false,
+                        /* caller */  null,
+                        mBackupEligibilityRules);
+    }
+
+    private void mockPackageEligibleForFullBackup(String packageName) throws Exception {
+        mEligiblePackages.add(packageName);
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.packageName = packageName;
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = packageName;
+        packageInfo.applicationInfo = appInfo;
+        when(mPackageManager.getPackageInfoAsUser(eq(packageName), anyInt(), anyInt())).thenReturn(
+                packageInfo);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
index 414532b..055adf6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
@@ -23,8 +23,10 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.backup.BackupAnnotations;
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
 import android.app.backup.BackupTransport;
@@ -91,6 +93,8 @@
     private UserBackupManagerService mBackupManagerService;
     @Mock
     private TransportConnection mTransportConnection;
+    @Mock
+    private BackupTransportClient mBackupTransportClient;
 
     private Set<String> mExcludedkeys = new HashSet<>();
     private Map<String, String> mBackupData = new HashMap<>();
@@ -151,6 +155,23 @@
     }
 
     @Test
+    public void setNoRestrictedModePackages_callsTransportAndSetsValue() throws Exception {
+        PackageInfo packageInfo1 = new PackageInfo();
+        packageInfo1.packageName = "package1";
+        PackageInfo packageInfo2 = new PackageInfo();
+        packageInfo2.packageName = "package2";
+        when(mBackupTransportClient.getPackagesThatShouldNotUseRestrictedMode(any(),
+                anyInt())).thenReturn(Set.of("package1"));
+
+        mRestoreTask.setNoRestrictedModePackages(mBackupTransportClient,
+                new PackageInfo[]{packageInfo1, packageInfo2});
+
+        verify(mBackupManagerService).setNoRestrictedModePackages(
+                eq(Set.of("package1")),
+                eq(BackupAnnotations.OperationType.RESTORE));
+    }
+
+    @Test
     public void testFilterExcludedKeys() throws Exception {
         when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME)))
                 .thenReturn(mExcludedkeys);
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
index 2d7d46f..13e3207 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
@@ -19,7 +19,14 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
 
+import android.app.backup.BackupAnnotations.OperationType;
 import android.app.backup.BackupTransport;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.RestoreDescription;
@@ -38,15 +45,31 @@
 import com.android.internal.backup.ITransportStatusCallback;
 import com.android.internal.infra.AndroidFuture;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.List;
+import java.util.Set;
 
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class BackupTransportClientTest {
 
+    @Mock
+    IBackupTransport mMockBackupTransport;
+
+    private BackupTransportClient mMockingTransportClient;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mMockingTransportClient = new BackupTransportClient(
+                mMockBackupTransport);
+    }
+
     private static class TestFuturesFakeTransportBinder extends FakeTransportBinderBase {
         public final Object mLock = new Object();
 
@@ -128,6 +151,70 @@
         thread.join();
     }
 
+    @Test
+    public void getPackagesThatShouldNotUseRestrictedMode_passesSetAsListToBinder()
+            throws Exception {
+        mockGetPackagesThatShouldNotUseRestrictedModeReturn(List.of("package1", "package2"));
+
+        mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+                Set.of("package1", "package2"),
+                OperationType.BACKUP);
+
+        verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(
+                argThat(list -> Set.copyOf(list).equals(Set.of("package1", "package2"))),
+                eq(OperationType.BACKUP), any());
+    }
+
+    @Test
+    public void getPackagesThatShouldNotUseRestrictedMode_forRestore_callsBinderForRestore()
+            throws Exception {
+        mockGetPackagesThatShouldNotUseRestrictedModeReturn(null);
+
+        mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+                Set.of(),
+                OperationType.RESTORE);
+
+        verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(),
+                eq(OperationType.RESTORE), any());
+    }
+
+    @Test
+    public void getPackagesThatShouldNotUseRestrictedMode_forBackup_callsBinderForBackup()
+            throws Exception {
+        mockGetPackagesThatShouldNotUseRestrictedModeReturn(null);
+
+        mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+                Set.of(),
+                OperationType.BACKUP);
+
+        verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(),
+                eq(OperationType.BACKUP), any());
+    }
+
+    @Test
+    public void getPackagesThatShouldNotUseRestrictedMode_nullResult_returnsEmptySet()
+            throws Exception {
+        mockGetPackagesThatShouldNotUseRestrictedModeReturn(null);
+
+        Set<String> result = mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+                Set.of(),
+                OperationType.BACKUP);
+
+        assertThat(result).isEqualTo(Set.of());
+    }
+
+    @Test
+    public void getPackagesThatShouldNotUseRestrictedMode_returnsResultAsSet()
+            throws Exception {
+        mockGetPackagesThatShouldNotUseRestrictedModeReturn(List.of("package1", "package2"));
+
+        Set<String> result = mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+                Set.of("package1", "package2"),
+                OperationType.BACKUP);
+
+        assertThat(result).isEqualTo(Set.of("package1", "package2"));
+    }
+
     private static class TestCallbacksFakeTransportBinder extends FakeTransportBinderBase {
         public final Object mLock = new Object();
 
@@ -158,7 +245,6 @@
         assertThat(status).isEqualTo(123);
     }
 
-
     @Test
     public void testFinishBackup_completesLater_returnsStatus() throws Exception {
         TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder();
@@ -211,6 +297,14 @@
         thread.join();
     }
 
+    private void mockGetPackagesThatShouldNotUseRestrictedModeReturn(List<String> returnList)
+            throws Exception {
+        doAnswer(
+                i -> ((AndroidFuture<List<String>>) i.getArguments()[2]).complete(returnList)).when(
+                mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(), anyInt(),
+                any());
+    }
+
     // Convenience layer so we only need to fake specific methods useful for each test case.
     private static class FakeTransportBinderBase implements IBackupTransport {
         @Override public void name(AndroidFuture<String> f) throws RemoteException {}
@@ -258,6 +352,10 @@
         @Override
         public void getBackupManagerMonitor(AndroidFuture<IBackupManagerMonitor> resultFuture)
                 throws RemoteException {}
+        @Override
+        public void getPackagesThatShouldNotUseRestrictedMode(List<String> packageNames,
+                int operationType, AndroidFuture<List<String>> resultFuture)
+                throws RemoteException {}
         @Override public IBinder asBinder() {
             return null;
         }
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index 359cf63..b48c2d7 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -2705,12 +2705,11 @@
         verify(mInvalidateInteractiveCachesMock).call();
 
         listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-        verify(mInvalidateInteractiveCachesMock, times(2)).call();
 
         mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP,
                 mClock.now(), 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
 
-        verify(mInvalidateInteractiveCachesMock, times(3)).call();
+        verify(mInvalidateInteractiveCachesMock, times(2)).call();
     }
 
     @Test
@@ -2732,12 +2731,11 @@
         verify(mInvalidateInteractiveCachesMock).call();
 
         listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-        verify(mInvalidateInteractiveCachesMock, times(2)).call();
 
         mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, mClock.now(),
                 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
 
-        verify(mInvalidateInteractiveCachesMock, times(3)).call();
+        verify(mInvalidateInteractiveCachesMock, times(2)).call();
     }
 
     @Test
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 b81bf3c..f6f831f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -21,6 +21,7 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS;
+import static android.hardware.biometrics.BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE;
 
 import static com.android.server.biometrics.sensors.LockoutTracker.LOCKOUT_NONE;
 
@@ -354,6 +355,21 @@
         assertThat(preAuthInfo.getIsMandatoryBiometricsAuthentication()).isTrue();
     }
 
+    @Test
+    public void prioritizeStrengthErrorBeforeCameraUnavailableError() throws Exception {
+        final BiometricSensor sensor = getFaceSensorWithStrength(
+                BiometricManager.Authenticators.BIOMETRIC_WEAK);
+        final PromptInfo promptInfo = new PromptInfo();
+        promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+        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.getCanAuthenticateResult()).isEqualTo(BIOMETRIC_ERROR_NO_HARDWARE);
+    }
+
     private BiometricSensor getFingerprintSensor() {
         BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FINGERPRINT,
                 TYPE_FINGERPRINT, BiometricManager.Authenticators.BIOMETRIC_STRONG,
@@ -372,9 +388,10 @@
         return sensor;
     }
 
-    private BiometricSensor getFaceSensor() {
+    private BiometricSensor getFaceSensorWithStrength(
+            @BiometricManager.Authenticators.Types int sensorStrength) {
         BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
-                BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+                sensorStrength, mFaceAuthenticator) {
             @Override
             boolean confirmationAlwaysRequired(int userId) {
                 return false;
@@ -388,4 +405,8 @@
 
         return sensor;
     }
+
+    private BiometricSensor getFaceSensor() {
+        return getFaceSensorWithStrength(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index 73aec63..510c2bc 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -532,6 +532,8 @@
         MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
         projection.start(mIMediaProjectionCallback);
 
+        doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+                RECORD_SENSITIVE_CONTENT, projection.packageName);
         doReturn(true).when(mKeyguardManager).isKeyguardLocked();
         MediaProjectionManagerService.BinderService mediaProjectionBinderService =
                 mService.new BinderService(mContext);
@@ -540,50 +542,6 @@
         verify(mContext, never()).startActivityAsUser(any(), any());
     }
 
-    @EnableFlags(android.companion.virtualdevice.flags
-            .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
-    @Test
-    public void testKeyguardLocked_stopsActiveProjection() throws Exception {
-        MediaProjectionManagerService service =
-                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
-        MediaProjectionManagerService.MediaProjection projection =
-                startProjectionPreconditions(service);
-        projection.start(mIMediaProjectionCallback);
-        projection.notifyVirtualDisplayCreated(10);
-
-        assertThat(service.getActiveProjectionInfo()).isNotNull();
-
-        doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager)
-                .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName);
-        service.onKeyguardLockedStateChanged(true);
-
-        verify(mMediaProjectionMetricsLogger).logStopped(UID, TARGET_UID_UNKNOWN);
-        assertThat(service.getActiveProjectionInfo()).isNull();
-        assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue();
-    }
-
-    @EnableFlags(android.companion.virtualdevice.flags
-            .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
-    @Test
-    public void testKeyguardLocked_packageAllowlisted_doesNotStopActiveProjection()
-            throws NameNotFoundException {
-        MediaProjectionManagerService service =
-                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
-        MediaProjectionManagerService.MediaProjection projection =
-                startProjectionPreconditions(service);
-        projection.start(mIMediaProjectionCallback);
-        projection.notifyVirtualDisplayCreated(10);
-
-        assertThat(service.getActiveProjectionInfo()).isNotNull();
-
-        doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
-                RECORD_SENSITIVE_CONTENT, projection.packageName);
-        service.onKeyguardLockedStateChanged(true);
-
-        verifyZeroInteractions(mMediaProjectionMetricsLogger);
-        assertThat(service.getActiveProjectionInfo()).isNotNull();
-    }
-
     @Test
     public void stop_noActiveProjections_doesNotLog() throws Exception {
         MediaProjectionManagerService service =
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java
new file mode 100644
index 0000000..89d2d28
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java
@@ -0,0 +1,429 @@
+/*
+ * 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.projection;
+
+
+import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT;
+import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.app.Instrumentation;
+import android.app.KeyguardManager;
+import android.app.role.RoleManager;
+import android.companion.AssociationRequest;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.projection.MediaProjectionManager;
+import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+import android.telecom.TelecomManager;
+import android.testing.TestableContext;
+import android.util.ArraySet;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Tests for the {@link MediaProjectionStopController} class.
+ * <p>
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:MediaProjectionStopControllerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SuppressLint({"UseCheckPermission", "VisibleForTests", "MissingPermission"})
+public class MediaProjectionStopControllerTest {
+    private static final int UID = 10;
+    private static final String PACKAGE_NAME = "test.package";
+    private final ApplicationInfo mAppInfo = new ApplicationInfo();
+    @Rule
+    public final TestableContext mContext = spy(
+            new TestableContext(InstrumentationRegistry.getInstrumentation().getContext()));
+
+    private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector =
+            new MediaProjectionManagerService.Injector() {
+                @Override
+                MediaProjectionMetricsLogger mediaProjectionMetricsLogger(Context context) {
+                    return mMediaProjectionMetricsLogger;
+                }
+            };
+
+    private MediaProjectionManagerService mService;
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Mock
+    private ActivityManagerInternal mAmInternal;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private KeyguardManager mKeyguardManager;
+    @Mock
+    private TelecomManager mTelecomManager;
+
+    private AppOpsManager mAppOpsManager;
+    @Mock
+    private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+    @Mock
+    private Consumer<Integer> mStopConsumer;
+
+    private MediaProjectionStopController mStopController;
+
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.addService(ActivityManagerInternal.class, mAmInternal);
+
+        mAppOpsManager = mockAppOpsManager();
+        mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager);
+        mContext.addMockSystemService(KeyguardManager.class, mKeyguardManager);
+        mContext.addMockSystemService(TelecomManager.class, mTelecomManager);
+        mContext.setMockPackageManager(mPackageManager);
+
+        mStopController = new MediaProjectionStopController(mContext, mStopConsumer);
+        mService = new MediaProjectionManagerService(mContext,
+                mMediaProjectionMetricsLoggerInjector);
+
+        mAppInfo.targetSdkVersion = 35;
+    }
+
+    private static AppOpsManager mockAppOpsManager() {
+        return mock(AppOpsManager.class, invocationOnMock -> {
+            if (invocationOnMock.getMethod().getName().startsWith("noteOp")) {
+                // Mockito will return 0 for non-stubbed method which corresponds to MODE_ALLOWED
+                // and is not what we want.
+                return AppOpsManager.MODE_IGNORED;
+            }
+            return Answers.RETURNS_DEFAULTS.answer(invocationOnMock);
+        });
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.removeServiceForTest(WindowManagerInternal.class);
+    }
+
+    @Test
+    @EnableFlags(
+            android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+    public void testMediaProjectionNotRestricted() throws Exception {
+        when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
+
+        assertThat(mStopController.isStartForbidden(
+                createMediaProjection(PACKAGE_NAME))).isFalse();
+    }
+
+    @Test
+    @EnableFlags(
+            android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+    public void testMediaProjectionRestricted() throws Exception {
+        MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+        mediaProjection.notifyVirtualDisplayCreated(1);
+        doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+                RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+        when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+
+        assertThat(mStopController.isStartForbidden(mediaProjection)).isTrue();
+    }
+
+    @Test
+    public void testExemptFromStoppingNullProjection() throws Exception {
+        assertThat(mStopController.isExemptFromStopping(null)).isTrue();
+    }
+
+    @Test
+    public void testExemptFromStoppingInvalidProjection() throws Exception {
+        assertThat(mStopController.isExemptFromStopping(createMediaProjection(null))).isTrue();
+    }
+
+    @Test
+    public void testExemptFromStoppingDisableScreenshareProtections() throws Exception {
+        MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+        doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+                RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+        int value = Settings.Global.getInt(mContext.getContentResolver(),
+                DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0);
+        try {
+            Settings.Global.putInt(mContext.getContentResolver(),
+                    DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 1);
+
+            assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+        } finally {
+            Settings.Global.putInt(mContext.getContentResolver(),
+                    DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, value);
+        }
+    }
+
+    @Test
+    public void testExemptFromStoppingHasOpProjectMedia() throws Exception {
+        MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+        doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+                RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
+                .noteOpNoThrow(eq(AppOpsManager.OP_PROJECT_MEDIA),
+                        eq(mediaProjection.uid), eq(mediaProjection.packageName),
+                        nullable(String.class),
+                        nullable(String.class));
+        assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+    }
+
+    @Test
+    public void testExemptFromStoppingHasAppStreamingRole() throws Exception {
+        runWithRole(
+                AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+                () -> {
+                    try {
+                        MediaProjectionManagerService.MediaProjection mediaProjection =
+                                createMediaProjection();
+                        doReturn(PackageManager.PERMISSION_DENIED).when(
+                                mPackageManager).checkPermission(
+                                RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+                        assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                });
+    }
+
+    @Test
+    public void testExemptFromStoppingIsBugreportAllowlisted() throws Exception {
+        ArraySet<String> packages = SystemConfig.getInstance().getBugreportWhitelistedPackages();
+        if (packages.isEmpty()) {
+            return;
+        }
+        MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection(
+                packages.valueAt(0));
+        doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+                RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+        assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+    }
+
+    @Test
+    public void testExemptFromStoppingHasNoDisplay() throws Exception {
+        MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection(
+                PACKAGE_NAME);
+        doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+                RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+        assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+    }
+
+    @Test
+    public void testExemptFromStoppingHasRecordSensitiveContentPermission() throws Exception {
+        MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
+                RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+        assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+    }
+
+    @Test
+    public void testExemptFromStoppingIsFalse() throws Exception {
+        MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+        mediaProjection.notifyVirtualDisplayCreated(1);
+        doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+                RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+        assertThat(mStopController.isExemptFromStopping(mediaProjection)).isFalse();
+    }
+
+    @Test
+    @EnableFlags(
+            android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+    public void testKeyguardLockedStateChanged_unlocked() {
+        mStopController.onKeyguardLockedStateChanged(false);
+
+        verify(mStopConsumer, never()).accept(anyInt());
+    }
+
+    @Test
+    @EnableFlags(
+            android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+    public void testKeyguardLockedStateChanged_locked() {
+        mStopController.onKeyguardLockedStateChanged(true);
+
+        verify(mStopConsumer).accept(MediaProjectionStopController.STOP_REASON_KEYGUARD);
+    }
+
+    @Test
+    @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+    public void testCallStateChanged_callStarts() {
+        // Setup call state to false
+        when(mTelecomManager.isInCall()).thenReturn(false);
+        mStopController.callStateChanged();
+
+        clearInvocations(mStopConsumer);
+
+        when(mTelecomManager.isInCall()).thenReturn(true);
+        mStopController.callStateChanged();
+
+        verify(mStopConsumer, never()).accept(anyInt());
+    }
+
+    @Test
+    @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+    public void testCallStateChanged_remainsInCall() {
+        // Setup call state to false
+        when(mTelecomManager.isInCall()).thenReturn(true);
+        mStopController.callStateChanged();
+
+        clearInvocations(mStopConsumer);
+
+        when(mTelecomManager.isInCall()).thenReturn(true);
+        mStopController.callStateChanged();
+
+        verify(mStopConsumer, never()).accept(anyInt());
+    }
+
+    @Test
+    @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+    public void testCallStateChanged_remainsNoCall() {
+        // Setup call state to false
+        when(mTelecomManager.isInCall()).thenReturn(false);
+        mStopController.callStateChanged();
+
+        clearInvocations(mStopConsumer);
+
+        when(mTelecomManager.isInCall()).thenReturn(false);
+        mStopController.callStateChanged();
+
+        verify(mStopConsumer, never()).accept(anyInt());
+    }
+
+    @Test
+    @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+    public void testCallStateChanged_callEnds() {
+        // Setup call state to false
+        when(mTelecomManager.isInCall()).thenReturn(true);
+        mStopController.callStateChanged();
+
+        clearInvocations(mStopConsumer);
+
+        when(mTelecomManager.isInCall()).thenReturn(false);
+        mStopController.callStateChanged();
+
+        verify(mStopConsumer).accept(MediaProjectionStopController.STOP_REASON_CALL_END);
+    }
+
+    private MediaProjectionManagerService.MediaProjection createMediaProjection()
+            throws NameNotFoundException {
+        return createMediaProjection(PACKAGE_NAME);
+    }
+
+    private MediaProjectionManagerService.MediaProjection createMediaProjection(String packageName)
+            throws NameNotFoundException {
+        doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(),
+                any(ApplicationInfoFlags.class), any(UserHandle.class));
+        doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(Mockito.isNull(),
+                any(ApplicationInfoFlags.class), any(UserHandle.class));
+        return mService.createProjectionInternal(UID, packageName,
+                MediaProjectionManager.TYPE_SCREEN_CAPTURE, false, mContext.getUser(),
+                INVALID_DISPLAY);
+    }
+
+    /**
+     * Run the provided block giving the current context's package the provided role.
+     */
+    @SuppressWarnings("SameParameterValue")
+    private void runWithRole(String role, Runnable block) {
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        String packageName = mContext.getPackageName();
+        UserHandle user = instrumentation.getTargetContext().getUser();
+        RoleManager roleManager = Objects.requireNonNull(
+                mContext.getSystemService(RoleManager.class));
+        try {
+            CountDownLatch latch = new CountDownLatch(1);
+            instrumentation.getUiAutomation().adoptShellPermissionIdentity(
+                    Manifest.permission.MANAGE_ROLE_HOLDERS,
+                    Manifest.permission.BYPASS_ROLE_QUALIFICATION);
+
+            roleManager.setBypassingRoleQualification(true);
+            roleManager.addRoleHolderAsUser(role, packageName,
+                    /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
+                    mContext.getMainExecutor(), success -> {
+                        if (success) {
+                            latch.countDown();
+                        } else {
+                            Assert.fail("Couldn't set role for test (failure) " + role);
+                        }
+                    });
+            assertWithMessage("Couldn't set role for test (timeout) : " + role)
+                    .that(latch.await(1, TimeUnit.SECONDS)).isTrue();
+            block.run();
+
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        } finally {
+            roleManager.removeRoleHolderAsUser(role, packageName,
+                    /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
+                    mContext.getMainExecutor(), (aBool) -> {
+                    });
+            roleManager.setBypassingRoleQualification(false);
+            instrumentation.getUiAutomation()
+                    .dropShellPermissionIdentity();
+        }
+    }
+}
diff --git a/services/tests/wmtests/res/xml/bookmarks.xml b/services/tests/wmtests/res/xml/bookmarks.xml
index 197b366..787f4e8 100644
--- a/services/tests/wmtests/res/xml/bookmarks.xml
+++ b/services/tests/wmtests/res/xml/bookmarks.xml
@@ -13,60 +13,70 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<bookmarks>
+<bookmarks xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
     <!-- the key combinations for the following shortcuts must be in sync
          with the key combinations sent by the test in ModifierShortcutTests.java -->
     <bookmark
         role="android.app.role.BROWSER"
-        shortcut="b" />
+        androidprv:keycode="KEYCODE_B"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_CONTACTS"
-        shortcut="c" />
+        androidprv:keycode="KEYCODE_C"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_EMAIL"
-        shortcut="e" />
+        androidprv:keycode="KEYCODE_E"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_CALENDAR"
-        shortcut="k" />
+        androidprv:keycode="KEYCODE_K"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_MAPS"
-        shortcut="m" />
+        androidprv:keycode="KEYCODE_M"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_MUSIC"
-        shortcut="p" />
+        androidprv:keycode="KEYCODE_P"
+        androidprv:modifierState="META" />
     <bookmark
         role="android.app.role.SMS"
-        shortcut="s" />
+        androidprv:keycode="KEYCODE_S"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_CALCULATOR"
-        shortcut="u" />
+        androidprv:keycode="KEYCODE_U"
+        androidprv:modifierState="META" />
 
     <bookmark
         role="android.app.role.BROWSER"
-        shortcut="b"
-        shift="true" />
+        androidprv:keycode="KEYCODE_B"
+        androidprv:modifierState="META|SHIFT" />
 
     <bookmark
         category="android.intent.category.APP_CONTACTS"
-        shortcut="c"
-        shift="true" />
+        androidprv:keycode="KEYCODE_C"
+        androidprv:modifierState="META|SHIFT" />
 
     <bookmark
         package="com.test"
         class="com.test.BookmarkTest"
-        shortcut="j"
-        shift="true" />
+        androidprv:keycode="KEYCODE_J"
+        androidprv:modifierState="META|SHIFT" />
 
     <!-- The following shortcuts will not be invoked by tests but are here to
          provide test coverage of parsing the different types of shortcut. -->
     <bookmark
         package="com.test"
         class="com.test.BookmarkTest"
-        shortcut="j" />
+        androidprv:keycode="KEYCODE_J"
+        androidprv:modifierState="META" />
     <bookmark
         package="com.test2"
         class="com.test.BookmarkTest"
-        shortcut="d" />
+        androidprv:keycode="KEYCODE_D"
+        androidprv:modifierState="META" />
 
 
     <!-- It's intended that this package/class will NOT resolve so we test the resolution
@@ -74,6 +84,7 @@
     <bookmark
         package="com.test3"
         class="com.test.BookmarkTest"
-        shortcut="f" />
+        androidprv:keycode="KEYCODE_F"
+        androidprv:modifierState="META" />
 
 </bookmarks>
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
index 0575d98..82a5add 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
@@ -116,6 +116,7 @@
 
         mModifierShortcutManager = new ModifierShortcutManager(
                 mContext, mHandler, UserHandle.SYSTEM);
+        mModifierShortcutManager.onSystemReady();
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index a51ce995..bc03c23 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -88,6 +88,7 @@
 import android.telecom.TelecomManager;
 import android.view.Display;
 import android.view.InputEvent;
+import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.autofill.AutofillManagerInternal;
@@ -270,11 +271,15 @@
         // Return mocked services: LocalServices.getService
         mMockitoSession = mockitoSession()
                 .mockStatic(LocalServices.class, spyStubOnly)
+                .mockStatic(KeyCharacterMap.class)
                 .strictness(Strictness.LENIENT)
                 .startMocking();
 
         mPhoneWindowManager = spy(new PhoneWindowManager());
 
+        KeyCharacterMap virtualKcm = mContext.getSystemService(InputManager.class)
+                .getInputDevice(KeyCharacterMap.VIRTUAL_KEYBOARD).getKeyCharacterMap();
+        doReturn(virtualKcm).when(() -> KeyCharacterMap.load(anyInt()));
         doReturn(mWindowManagerInternal).when(
                 () -> LocalServices.getService(eq(WindowManagerInternal.class)));
         doReturn(mActivityManagerInternal).when(
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 b737d35..50e0e18 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -71,6 +71,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -84,12 +85,14 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.when;
 
+import android.content.ContentResolver;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.InputConfig;
 import android.os.RemoteException;
@@ -97,6 +100,7 @@
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.provider.Settings;
 import android.util.ArraySet;
 import android.util.MergedConfiguration;
 import android.view.Gravity;
@@ -1557,6 +1561,57 @@
     }
 
     @Test
+    public void testIsSecureLocked_flagSecureSet() {
+        WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window",
+                1 /* ownerId */);
+        window.mAttrs.flags |= WindowManager.LayoutParams.FLAG_SECURE;
+
+        assertTrue(window.isSecureLocked());
+    }
+
+    @Test
+    public void testIsSecureLocked_flagSecureNotSet() {
+        WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window",
+                1 /* ownerId */);
+
+        assertFalse(window.isSecureLocked());
+    }
+
+    @Test
+    public void testIsSecureLocked_disableSecureWindows() {
+        assumeTrue(Build.IS_DEBUGGABLE);
+
+        WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window",
+                1 /* ownerId */);
+        window.mAttrs.flags |= WindowManager.LayoutParams.FLAG_SECURE;
+        ContentResolver cr = useFakeSettingsProvider();
+
+        // isSecureLocked should return false when DISABLE_SECURE_WINDOWS is set to 1
+        Settings.Secure.putString(cr, Settings.Secure.DISABLE_SECURE_WINDOWS, "1");
+        mWm.mSettingsObserver.onChange(false /* selfChange */,
+                Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS));
+        assertFalse(window.isSecureLocked());
+
+        // isSecureLocked should return true if DISABLE_SECURE_WINDOWS is set to 0.
+        Settings.Secure.putString(cr, Settings.Secure.DISABLE_SECURE_WINDOWS, "0");
+        mWm.mSettingsObserver.onChange(false /* selfChange */,
+                Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS));
+        assertTrue(window.isSecureLocked());
+
+        // Disable secure windows again.
+        Settings.Secure.putString(cr, Settings.Secure.DISABLE_SECURE_WINDOWS, "1");
+        mWm.mSettingsObserver.onChange(false /* selfChange */,
+                Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS));
+        assertFalse(window.isSecureLocked());
+
+        // isSecureLocked should return true if DISABLE_SECURE_WINDOWS is deleted.
+        Settings.Secure.putString(cr, Settings.Secure.DISABLE_SECURE_WINDOWS, null);
+        mWm.mSettingsObserver.onChange(false /* selfChange */,
+                Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS));
+        assertTrue(window.isSecureLocked());
+    }
+
+    @Test
     @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
     public void testIsSecureLocked_sensitiveContentProtectionManagerEnabled() {
         String testPackage = "test";
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 9f5e6d1..2c998c4 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
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.helpers
 
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.content.Context
 import android.graphics.Insets
 import android.graphics.Rect
@@ -74,13 +75,28 @@
             .waitForAndVerify()
     }
 
+    /** Launch an app and ensure it's moved to Desktop if it has not. */
+    fun enterDesktopMode(
+        wmHelper: WindowManagerStateHelper,
+        device: UiDevice,
+        motionEventHelper: MotionEventHelper = MotionEventHelper(getInstrumentation(), TOUCH),
+    ) {
+        innerHelper.launchViaIntent(wmHelper)
+        if (!isInDesktopWindowingMode(wmHelper)) {
+            enterDesktopModeWithDrag(
+                wmHelper = wmHelper,
+                device = device,
+                motionEventHelper = motionEventHelper
+            )
+        }
+    }
+
     /** Move an app to Desktop by dragging the app handle at the top. */
-    fun enterDesktopWithDrag(
+    fun enterDesktopModeWithDrag(
         wmHelper: WindowManagerStateHelper,
         device: UiDevice,
         motionEventHelper: MotionEventHelper = MotionEventHelper(getInstrumentation(), TOUCH)
     ) {
-        innerHelper.launchViaIntent(wmHelper)
         dragToDesktop(
             wmHelper = wmHelper,
             device = device,
@@ -150,6 +166,25 @@
             .waitForAndVerify()
     }
 
+    private fun getHeaderEmptyView(caption: UiObject2?): UiObject2 {
+        return caption
+            ?.children
+            ?.find { it.resourceName.endsWith(HEADER_EMPTY_VIEW) }
+            ?: error("Unable to find resource $HEADER_EMPTY_VIEW\n")
+    }
+
+    /** Click on an existing window's header to bring it to the front. */
+    fun bringToFront(wmHelper: WindowManagerStateHelper, device: UiDevice) {
+        val caption = getCaptionForTheApp(wmHelper, device)
+        val openHeaderView = getHeaderEmptyView(caption)
+        openHeaderView.click()
+        wmHelper
+            .StateSyncBuilder()
+            .withAppTransitionIdle()
+            .withTopVisibleApp(innerHelper)
+            .waitForAndVerify()
+    }
+
     /** Open maximize menu and click snap resize button on the app header for the given app. */
     fun snapResizeDesktopApp(
         wmHelper: WindowManagerStateHelper,
@@ -415,6 +450,10 @@
         return metricInsets.getInsetsIgnoringVisibility(typeMask)
     }
 
+    // Requirement of DesktopWindowingMode is having a minimum of 1 app in WINDOWING_MODE_FREEFORM.
+    private fun isInDesktopWindowingMode(wmHelper: WindowManagerStateHelper) =
+        wmHelper.getWindow(innerHelper)?.windowingMode == WINDOWING_MODE_FREEFORM
+
     private companion object {
         val TIMEOUT: Duration = Duration.ofSeconds(3)
         const val SNAP_RESIZE_DRAG_INSET: Int = 5 // inset to avoid dragging to display edge
@@ -427,6 +466,7 @@
         const val SNAP_LEFT_BUTTON: String = "maximize_menu_snap_left_button"
         const val SNAP_RIGHT_BUTTON: String = "maximize_menu_snap_right_button"
         const val MINIMIZE_BUTTON_VIEW: String = "minimize_window"
+        const val HEADER_EMPTY_VIEW: String = "caption_handle"
         val caption: BySelector
             get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
     }
diff --git a/tests/Input/res/xml/bookmarks.xml b/tests/Input/res/xml/bookmarks.xml
index ba3f187..a4c898d 100644
--- a/tests/Input/res/xml/bookmarks.xml
+++ b/tests/Input/res/xml/bookmarks.xml
@@ -14,47 +14,55 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<bookmarks>
+<bookmarks xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
     <!-- the key combinations for the following shortcuts must be in sync
          with the key combinations sent by the test in KeyGestureControllerTests.java -->
     <bookmark
         role="android.app.role.BROWSER"
-        shortcut="b" />
+        androidprv:keycode="KEYCODE_B"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_CONTACTS"
-        shortcut="c" />
+        androidprv:keycode="KEYCODE_C"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_EMAIL"
-        shortcut="e" />
+        androidprv:keycode="KEYCODE_E"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_CALENDAR"
-        shortcut="k" />
+        androidprv:keycode="KEYCODE_K"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_MAPS"
-        shortcut="m" />
+        androidprv:keycode="KEYCODE_M"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_MUSIC"
-        shortcut="p" />
+        androidprv:keycode="KEYCODE_P"
+        androidprv:modifierState="META" />
     <bookmark
         role="android.app.role.SMS"
-        shortcut="s" />
+        androidprv:keycode="KEYCODE_S"
+        androidprv:modifierState="META" />
     <bookmark
         category="android.intent.category.APP_CALCULATOR"
-        shortcut="u" />
+        androidprv:keycode="KEYCODE_U"
+        androidprv:modifierState="META" />
 
     <bookmark
         role="android.app.role.BROWSER"
-        shortcut="b"
-        shift="true" />
+        androidprv:keycode="KEYCODE_B"
+        androidprv:modifierState="META|SHIFT" />
 
     <bookmark
         category="android.intent.category.APP_CONTACTS"
-        shortcut="c"
+        androidprv:keycode="KEYCODE_C"
         shift="true" />
 
     <bookmark
         package="com.test"
         class="com.test.BookmarkTest"
-        shortcut="j"
+        androidprv:keycode="KEYCODE_J"
         shift="true" />
 </bookmarks>
\ No newline at end of file
diff --git a/tests/Input/res/xml/bookmarks_legacy.xml b/tests/Input/res/xml/bookmarks_legacy.xml
new file mode 100644
index 0000000..8bacf49
--- /dev/null
+++ b/tests/Input/res/xml/bookmarks_legacy.xml
@@ -0,0 +1,60 @@
+<?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.
+  -->
+<bookmarks>
+    <!-- The key combinations for the following shortcuts are legacy way defining bookmarks and we
+         should prefer new way of defining bookmarks as shown in {@link bookmarks.xml} -->
+    <bookmark
+        role="android.app.role.BROWSER"
+        shortcut="b" />
+    <bookmark
+        category="android.intent.category.APP_CONTACTS"
+        shortcut="c" />
+    <bookmark
+        category="android.intent.category.APP_EMAIL"
+        shortcut="e" />
+    <bookmark
+        category="android.intent.category.APP_CALENDAR"
+        shortcut="k" />
+    <bookmark
+        category="android.intent.category.APP_MAPS"
+        shortcut="m" />
+    <bookmark
+        category="android.intent.category.APP_MUSIC"
+        shortcut="p" />
+    <bookmark
+        role="android.app.role.SMS"
+        shortcut="s" />
+    <bookmark
+        category="android.intent.category.APP_CALCULATOR"
+        shortcut="u" />
+
+    <bookmark
+        role="android.app.role.BROWSER"
+        shortcut="b"
+        shift="true" />
+
+    <bookmark
+        category="android.intent.category.APP_CONTACTS"
+        shortcut="c"
+        shift="true" />
+
+    <bookmark
+        package="com.test"
+        class="com.test.BookmarkTest"
+        shortcut="j"
+        shift="true" />
+</bookmarks>
\ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 09a686c..d1f8668 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -115,13 +115,11 @@
     private lateinit var iInputManager: IInputManager
 
     @Mock
-    private lateinit var resources: Resources
-
-    @Mock
     private lateinit var packageManager: PackageManager
 
     private var currentPid = 0
     private lateinit var context: Context
+    private lateinit var resources: Resources
     private lateinit var keyGestureController: KeyGestureController
     private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
     private lateinit var testLooper: TestLooper
@@ -130,6 +128,7 @@
     @Before
     fun setup() {
         context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        resources = Mockito.spy(context.resources)
         setupInputDevices()
         setupBehaviors()
         testLooper = TestLooper()
@@ -146,10 +145,6 @@
     private fun setupBehaviors() {
         Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1")
         Mockito.`when`(resources.getBoolean(R.bool.config_enableScreenshotChord)).thenReturn(true)
-        val testBookmarks: XmlResourceParser = context.resources.getXml(
-            com.android.test.input.R.xml.bookmarks
-        )
-        Mockito.`when`(resources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks)
         Mockito.`when`(context.resources).thenReturn(resources)
         Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
             .thenReturn(true)
@@ -158,6 +153,11 @@
         Mockito.`when`(context.packageManager).thenReturn(packageManager)
     }
 
+    private fun setupBookmarks(bookmarkRes: Int) {
+        val testBookmarks: XmlResourceParser = context.resources.getXml(bookmarkRes)
+        Mockito.`when`(resources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks)
+    }
+
     private fun setupInputDevices() {
         val correctIm = context.getSystemService(InputManager::class.java)!!
         val virtualDevice = correctIm.getInputDevice(KeyCharacterMap.VIRTUAL_KEYBOARD)!!
@@ -604,45 +604,6 @@
                 AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR)
             ),
             TestData(
-                "META + SHIFT + B -> Launch Default Browser",
-                intArrayOf(
-                    KeyEvent.KEYCODE_META_LEFT,
-                    KeyEvent.KEYCODE_SHIFT_LEFT,
-                    KeyEvent.KEYCODE_B
-                ),
-                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
-                intArrayOf(KeyEvent.KEYCODE_B),
-                KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
-                AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
-            ),
-            TestData(
-                "META + SHIFT + C -> Launch Default Contacts",
-                intArrayOf(
-                    KeyEvent.KEYCODE_META_LEFT,
-                    KeyEvent.KEYCODE_SHIFT_LEFT,
-                    KeyEvent.KEYCODE_C
-                ),
-                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
-                intArrayOf(KeyEvent.KEYCODE_C),
-                KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
-                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
-            ),
-            TestData(
-                "META + SHIFT + J -> Launch Target Activity",
-                intArrayOf(
-                    KeyEvent.KEYCODE_META_LEFT,
-                    KeyEvent.KEYCODE_SHIFT_LEFT,
-                    KeyEvent.KEYCODE_J
-                ),
-                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
-                intArrayOf(KeyEvent.KEYCODE_J),
-                KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
-                AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest")
-            ),
-            TestData(
                 "META + CTRL + DEL -> Trigger Bug Report",
                 intArrayOf(
                     KeyEvent.KEYCODE_META_LEFT,
@@ -866,6 +827,139 @@
     }
 
     @Keep
+    private fun bookmarkArguments(): Array<TestData> {
+        return arrayOf(
+            TestData(
+                "META + B -> Launch Default Browser",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_B),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_B),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+            ),
+            TestData(
+                "META + C -> Launch Default Contacts",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_C),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_C),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+            ),
+            TestData(
+                "META + E -> Launch Default Email",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_E),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_E),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL)
+            ),
+            TestData(
+                "META + K -> Launch Default Calendar",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_K),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_K),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR)
+            ),
+            TestData(
+                "META + M -> Launch Default Maps",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_M),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_M),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MAPS)
+            ),
+            TestData(
+                "META + P -> Launch Default Music",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_P),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_P),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MUSIC)
+            ),
+            TestData(
+                "META + S -> Launch Default SMS",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_S),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_S),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_SMS)
+            ),
+            TestData(
+                "META + U -> Launch Default Calculator",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_U),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_U),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR)
+            ),
+            TestData(
+                "META + SHIFT + B -> Launch Default Browser",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_SHIFT_LEFT,
+                    KeyEvent.KEYCODE_B
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_B),
+                KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+            ),
+            TestData(
+                "META + SHIFT + C -> Launch Default Contacts",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_SHIFT_LEFT,
+                    KeyEvent.KEYCODE_C
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_C),
+                KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+            ),
+            TestData(
+                "META + SHIFT + J -> Launch Target Activity",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_SHIFT_LEFT,
+                    KeyEvent.KEYCODE_J
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_J),
+                KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest")
+            )
+        )
+    }
+
+    @Test
+    @Parameters(method = "bookmarkArguments")
+    fun testBookmarks(test: TestData) {
+        setupBookmarks(com.android.test.input.R.xml.bookmarks)
+        setupKeyGestureController()
+        testKeyGestureInternal(test)
+    }
+
+    @Test
+    @Parameters(method = "bookmarkArguments")
+    fun testBookmarksLegacy(test: TestData) {
+        setupBookmarks(com.android.test.input.R.xml.bookmarks_legacy)
+        setupKeyGestureController()
+        testKeyGestureInternal(test)
+    }
+
+    @Keep
     private fun systemKeysTestArguments(): Array<TestData> {
         return arrayOf(
             TestData(
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
index 2692e12..44641f7 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -57,6 +58,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
 
 import perfetto.protos.Protolog;
 import perfetto.protos.ProtologCommon;
@@ -858,6 +860,39 @@
                 .isEqualTo("This message should also be logged 567");
     }
 
+    @Test
+    public void enablesLogGroupAfterLoadingConfig() {
+        sProtoLog.stopLoggingToLogcat(
+                new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {});
+        Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse();
+
+        doAnswer((Answer<Void>) invocation -> {
+            // logToLogcat is still false before we laod the viewer config
+            Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse();
+            return null;
+        }).when(sReader).unloadViewerConfig(any(), any());
+
+        sProtoLog.startLoggingToLogcat(
+                new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {});
+        Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isTrue();
+    }
+
+    @Test
+    public void disablesLogGroupBeforeUnloadingConfig() {
+        sProtoLog.startLoggingToLogcat(
+                new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {});
+        Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isTrue();
+
+        doAnswer((Answer<Void>) invocation -> {
+            // Already set logToLogcat to false by the time we unload the config
+            Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse();
+            return null;
+        }).when(sReader).unloadViewerConfig(any(), any());
+        sProtoLog.stopLoggingToLogcat(
+                new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {});
+        Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse();
+    }
+
     private enum TestProtoLogGroup implements IProtoLogGroup {
         TEST_GROUP(true, true, false, "TEST_TAG");