Merge "[Forensic] Refactor ForensicEvent" into main
diff --git a/Android.bp b/Android.bp
index 48f0928..54cb268 100644
--- a/Android.bp
+++ b/Android.bp
@@ -107,7 +107,6 @@
         ":android.hardware.radio.data-V3-java-source",
         ":android.hardware.radio.network-V3-java-source",
         ":android.hardware.radio.voice-V3-java-source",
-        ":android.hardware.security.keymint-V3-java-source",
         ":android.hardware.security.secureclock-V1-java-source",
         ":android.hardware.thermal-V3-java-source",
         ":android.hardware.tv.tuner-V3-java-source",
@@ -116,7 +115,6 @@
         ":android.security.legacykeystore-java-source",
         ":android.security.maintenance-java-source",
         ":android.security.metrics-java-source",
-        ":android.system.keystore2-V4-java-source",
         ":android.hardware.cas-V1-java-source",
         ":credstore_aidl",
         ":dumpstate_aidl",
@@ -149,7 +147,16 @@
         ":statslog-framework-java-gen", // FrameworkStatsLog.java
         ":statslog-hwui-java-gen", // HwuiStatsLog.java
         ":audio_policy_configuration_V7_0",
-    ],
+    ] + select(release_flag("RELEASE_ATTEST_MODULES"), {
+        true: [
+            ":android.hardware.security.keymint-V4-java-source",
+            ":android.system.keystore2-V5-java-source",
+        ],
+        default: [
+            ":android.hardware.security.keymint-V3-java-source",
+            ":android.system.keystore2-V4-java-source",
+        ],
+    }),
 }
 
 java_library {
@@ -398,6 +405,7 @@
         "bouncycastle-repackaged-unbundled",
         "com.android.sysprop.foldlockbehavior",
         "com.android.sysprop.view",
+        "configinfra_framework_flags_java_lib",
         "framework-internal-utils",
         "dynamic_instrumentation_manager_aidl-java",
         // If MimeMap ever becomes its own APEX, then this dependency would need to be removed
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 5b9f1ef..19a32c1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13569,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
@@ -13805,6 +13806,7 @@
 
   public final class SharedLibraryInfo implements android.os.Parcelable {
     method public int describeContents();
+    method @FlaggedApi("android.content.pm.sdk_dependency_installer") @NonNull public java.util.List<java.lang.String> getCertDigests();
     method @NonNull public android.content.pm.VersionedPackage getDeclaringPackage();
     method @NonNull public java.util.List<android.content.pm.VersionedPackage> getDependentPackages();
     method @IntRange(from=0xffffffff) public long getLongVersion();
@@ -17472,6 +17474,7 @@
     method public void setFloatUniform(@NonNull String, @NonNull float[]);
     method public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter);
     method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader);
+    method public void setInputXfermode(@NonNull String, @NonNull android.graphics.RuntimeXfermode);
     method public void setIntUniform(@NonNull String, int);
     method public void setIntUniform(@NonNull String, int, int);
     method public void setIntUniform(@NonNull String, int, int, int);
@@ -17490,7 +17493,9 @@
     method public void setFloatUniform(@NonNull String, float, float, float, float);
     method public void setFloatUniform(@NonNull String, @NonNull float[]);
     method public void setInputBuffer(@NonNull String, @NonNull android.graphics.BitmapShader);
+    method @FlaggedApi("com.android.graphics.hwui.flags.runtime_color_filters_blenders") public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter);
     method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader);
+    method @FlaggedApi("com.android.graphics.hwui.flags.runtime_color_filters_blenders") public void setInputXfermode(@NonNull String, @NonNull android.graphics.RuntimeXfermode);
     method public void setIntUniform(@NonNull String, int);
     method public void setIntUniform(@NonNull String, int, int);
     method public void setIntUniform(@NonNull String, int, int, int);
@@ -17510,6 +17515,7 @@
     method public void setFloatUniform(@NonNull String, @NonNull float[]);
     method public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter);
     method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader);
+    method public void setInputXfermode(@NonNull String, @NonNull android.graphics.RuntimeXfermode);
     method public void setIntUniform(@NonNull String, int);
     method public void setIntUniform(@NonNull String, int, int);
     method public void setIntUniform(@NonNull String, int, int, int);
@@ -34731,8 +34737,6 @@
     method public static android.os.VibrationEffect createWaveform(long[], int[], int);
     method public int describeContents();
     method @NonNull public static android.os.VibrationEffect.Composition startComposition();
-    method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public static android.os.VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope();
-    method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public static android.os.VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope(@FloatRange(from=0) float);
     field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
     field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
     field public static final int EFFECT_CLICK = 0; // 0x0
@@ -34760,8 +34764,10 @@
   }
 
   @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public static final class VibrationEffect.WaveformEnvelopeBuilder {
+    ctor public VibrationEffect.WaveformEnvelopeBuilder();
     method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public android.os.VibrationEffect.WaveformEnvelopeBuilder addControlPoint(@FloatRange(from=0, to=1) float, @FloatRange(from=0) float, int);
     method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public android.os.VibrationEffect build();
+    method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public android.os.VibrationEffect.WaveformEnvelopeBuilder setInitialFrequencyHz(@FloatRange(from=0) float);
   }
 
   public abstract class Vibrator {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0ea00f5..9197a01 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
@@ -1937,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();
@@ -4590,6 +4594,24 @@
 
 }
 
+package android.content.pm.dependencyinstaller {
+
+  @FlaggedApi("android.content.pm.sdk_dependency_installer") public final class DependencyInstallerCallback implements android.os.Parcelable {
+    method public int describeContents();
+    method public void onAllDependenciesResolved(@NonNull int[]);
+    method public void onFailureToResolveAllDependencies();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.dependencyinstaller.DependencyInstallerCallback> CREATOR;
+  }
+
+  @FlaggedApi("android.content.pm.sdk_dependency_installer") public abstract class DependencyInstallerService extends android.app.Service {
+    ctor public DependencyInstallerService();
+    method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
+    method public abstract void onDependenciesRequired(@NonNull java.util.List<android.content.pm.SharedLibraryInfo>, @NonNull android.content.pm.dependencyinstaller.DependencyInstallerCallback);
+  }
+
+}
+
 package android.content.pm.dex {
 
   public class ArtManager {
@@ -5401,6 +5423,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getDefaultBrightnessConfiguration();
     method public android.util.Pair<float[],float[]> getMinimumBrightnessCurve();
     method public android.graphics.Point getStableDisplaySize();
+    method @FlaggedApi("com.android.server.display.feature.flags.is_always_on_available_api") public boolean isAlwaysOnDisplayCurrentlyAvailable();
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String);
     method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 136c636..8fd2cd5 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2318,7 +2318,7 @@
 
   public class SharedConnectivityManager {
     method @Nullable public static android.net.wifi.sharedconnectivity.app.SharedConnectivityManager create(@NonNull android.content.Context, @NonNull String, @NonNull String);
-    method @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api") @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
+    method @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
     method @Nullable public android.content.ServiceConnection getServiceConnection();
     method public void setService(@Nullable android.os.IInterface);
   }
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/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index a063917..53a7dad 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import static android.app.appfunctions.flags.Flags.enableAppFunctionManager;
+import static android.provider.flags.Flags.stageFlagsForBuild;
 import static android.server.Flags.removeGameManagerServiceFromWear;
 
 import android.accounts.AccountManager;
@@ -216,6 +217,7 @@
 import android.os.UserManager;
 import android.os.Vibrator;
 import android.os.VibratorManager;
+import android.os.flagging.ConfigInfrastructureFrameworkInitializer;
 import android.os.health.SystemHealthManager;
 import android.os.image.DynamicSystemManager;
 import android.os.image.IDynamicSystemService;
@@ -1837,6 +1839,10 @@
             VirtualizationFrameworkInitializer.registerServiceWrappers();
             ConnectivityFrameworkInitializerBaklava.registerServiceWrappers();
 
+            if (stageFlagsForBuild()) {
+                ConfigInfrastructureFrameworkInitializer.registerServiceWrappers();
+            }
+
             if (com.android.server.telecom.flags.Flags.telecomMainlineBlockedNumbersManager()) {
                 ProviderFrameworkInitializer.registerServiceWrappers();
             }
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 13b13b9..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.
      *
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index f7191e6..5dfec98 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -105,6 +105,8 @@
     private final List<VersionedPackage> mOptionalDependentPackages;
     private List<SharedLibraryInfo> mDependencies;
 
+    private final List<String> mCertDigests;
+
     /**
      * Creates a new instance.
      *
@@ -134,6 +136,7 @@
         mDependencies = dependencies;
         mIsNative = isNative;
         mOptionalDependentPackages = null;
+        mCertDigests = null;
     }
 
     /**
@@ -165,6 +168,7 @@
         mDeclaringPackage = declaringPackage;
         mDependencies = dependencies;
         mIsNative = isNative;
+        mCertDigests = null;
 
         var allDependents = allDependentPackages.first;
         var usesLibOptional = allDependentPackages.second;
@@ -206,6 +210,7 @@
         mIsNative = parcel.readBoolean();
         mOptionalDependentPackages = parcel.readParcelableList(new ArrayList<>(),
                 VersionedPackage.class.getClassLoader(), VersionedPackage.class);
+        mCertDigests = parcel.createStringArrayList();
     }
 
     /**
@@ -214,6 +219,7 @@
      * @param versionMajor
      */
     public SharedLibraryInfo(String name, long versionMajor, int type) {
+        //TODO: change to this(name, versionMajor, type, /* certDigest= */null); after flag removal
         mPath = null;
         mPackageName = null;
         mName = name;
@@ -224,6 +230,29 @@
         mDependencies = null;
         mIsNative = false;
         mOptionalDependentPackages = null;
+        mCertDigests = null;
+    }
+
+    /**
+     * @hide
+     * @param name The lib name.
+     * @param versionMajor The lib major version.
+     * @param type The type of shared library.
+     * @param certDigests The list of certificate digests for this shared library.
+     */
+    @FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+    public SharedLibraryInfo(String name, long versionMajor, int type, List<String> certDigests) {
+        mPath = null;
+        mPackageName = null;
+        mName = name;
+        mVersion = versionMajor;
+        mType = type;
+        mDeclaringPackage = null;
+        mDependentPackages = null;
+        mDependencies = null;
+        mIsNative = false;
+        mOptionalDependentPackages = null;
+        mCertDigests = certDigests;
     }
 
     /**
@@ -433,6 +462,19 @@
         return mOptionalDependentPackages;
     }
 
+    /**
+     * Gets the list of certificate digests for the shared library.
+     *
+     * @return The list of certificate digests
+     */
+    @FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+    public @NonNull List<String> getCertDigests() {
+        if (mCertDigests == null) {
+            return Collections.emptyList();
+        }
+        return mCertDigests;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -463,6 +505,7 @@
         parcel.writeTypedList(mDependencies);
         parcel.writeBoolean(mIsNative);
         parcel.writeParcelableList(mOptionalDependentPackages, flags);
+        parcel.writeStringList(mCertDigests);
     }
 
     private static String typeToString(int type) {
diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl
new file mode 100644
index 0000000..06fcabc
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.dependencyinstaller;
+
+parcelable DependencyInstallerCallback;
\ No newline at end of file
diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java
new file mode 100644
index 0000000..ba089f7
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java
@@ -0,0 +1,100 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.dependencyinstaller;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.pm.Flags;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+/**
+ * Callbacks for {@link DependencyInstallerService}. The implementation of
+ * DependencyInstallerService uses this interface to indicate completion of the session creation
+ * request given by the system server.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+public final class DependencyInstallerCallback implements Parcelable {
+    private final IBinder mBinder;
+    private final IDependencyInstallerCallback mCallback;
+
+    /** @hide */
+    public DependencyInstallerCallback(IBinder binder) {
+        mBinder = binder;
+        mCallback = IDependencyInstallerCallback.Stub.asInterface(binder);
+    }
+
+    private DependencyInstallerCallback(Parcel in) {
+        mBinder = in.readStrongBinder();
+        mCallback = IDependencyInstallerCallback.Stub.asInterface(mBinder);
+    }
+
+    /**
+     * Callback to indicate that all the requested dependencies have been resolved and their
+     * sessions created. See {@link  DependencyInstallerService#onDependenciesRequired}.
+     *
+     * @param sessionIds the install session IDs for all requested dependencies
+     */
+    public void onAllDependenciesResolved(@NonNull int[] sessionIds) {
+        try {
+            mCallback.onAllDependenciesResolved(sessionIds);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Callback to indicate that at least one of the required dependencies could not be resolved
+     * and any associated sessions have been abandoned.
+     */
+    public void onFailureToResolveAllDependencies() {
+        try {
+            mCallback.onFailureToResolveAllDependencies();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeStrongBinder(mBinder);
+    }
+
+    public static final @NonNull Creator<DependencyInstallerCallback> CREATOR =
+            new Creator<>() {
+                @Override
+                public DependencyInstallerCallback createFromParcel(Parcel in) {
+                    return new DependencyInstallerCallback(in);
+                }
+
+                @Override
+                public DependencyInstallerCallback[] newArray(int size) {
+                    return new DependencyInstallerCallback[size];
+                }
+            };
+}
diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java
new file mode 100644
index 0000000..1186415
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.dependencyinstaller;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.Flags;
+import android.content.pm.SharedLibraryInfo;
+import android.os.IBinder;
+
+import java.util.List;
+
+/**
+ * Service that needs to be implemented by the holder of the DependencyInstaller role. This service
+ * will be invoked by the system during application installations if it depends on
+ * {@link android.content.pm.SharedLibraryInfo#TYPE_STATIC} or
+ * {@link android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE} and those dependencies aren't
+ * already installed.
+ * <p>
+ * Below is an example manifest registration for a {@code DependencyInstallerService}.
+ * <pre>
+ * {@code
+ * <service android:name=".ExampleDependencyInstallerService"
+ *     android:permission="android.permission.BIND_DEPENDENCY_INSTALLER" >
+ *     ...
+ *     <intent-filter>
+ *         <action android:name="android.content.pm.action.INSTALL_DEPENDENCY" />
+ *     </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+public abstract class DependencyInstallerService extends Service {
+
+    private IDependencyInstallerService mBinder;
+
+    @Override
+    public final @NonNull IBinder onBind(@Nullable Intent intent) {
+        if (mBinder == null) {
+            mBinder = new IDependencyInstallerService.Stub() {
+                @Override
+                public void onDependenciesRequired(List<SharedLibraryInfo> neededLibraries,
+                        DependencyInstallerCallback callback) {
+                    DependencyInstallerService.this.onDependenciesRequired(neededLibraries,
+                            callback);
+                }
+            };
+        }
+        return mBinder.asBinder();
+    }
+
+    /**
+     * Notify the holder of the DependencyInstaller role of the missing dependencies required for
+     * the completion of an active install session.
+     *
+     * @param neededLibraries the list of shared library dependencies needed to be obtained and
+     *                        installed.
+     */
+    public abstract void onDependenciesRequired(@NonNull List<SharedLibraryInfo> neededLibraries,
+            @NonNull DependencyInstallerCallback callback);
+}
diff --git a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl
new file mode 100644
index 0000000..92d1d9e
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.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.content.pm.dependencyinstaller;
+
+import java.util.List;
+
+/**
+* Callbacks for Dependency Installer. The app side invokes on this interface to indicate
+* completion of the async dependency install request given by the system server.
+*
+* {@hide}
+*/
+oneway interface IDependencyInstallerCallback {
+    /**
+     * Callback to indicate that all the requested dependencies have been resolved and have been
+     * committed for installation. See {@link  DependencyInstallerService#onDependenciesRequired}.
+     *
+     * @param sessionIds the install session IDs for all requested dependencies
+     */
+    void onAllDependenciesResolved(in int[] sessionIds);
+
+    /**
+     * Callback to indicate that at least one of the required dependencies could not be resolved
+     * and any associated sessions have been abandoned.
+     */
+    void onFailureToResolveAllDependencies();
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl
new file mode 100644
index 0000000..94f5bf4
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl
@@ -0,0 +1,37 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.dependencyinstaller;
+
+import android.content.pm.dependencyinstaller.DependencyInstallerCallback;
+import android.content.pm.SharedLibraryInfo;
+import java.util.List;
+
+/**
+* Interface used to communicate with the application code that holds the Dependency Installer role.
+* {@hide}
+*/
+oneway interface IDependencyInstallerService {
+    /**
+     * Notify dependency installer of the required dependencies to complete the current install
+     * session.
+     *
+     * @param neededLibraries the list of shared library dependencies needed to be obtained and
+     *                        installed.
+     */
+    void onDependenciesRequired(in List<SharedLibraryInfo> neededLibraries,
+                in DependencyInstallerCallback callback);
+ }
\ No newline at end of file
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index e6a1640..25327a9 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -61,6 +61,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.display.feature.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -102,6 +103,7 @@
     private final WeakDisplayCache mDisplayCache = new WeakDisplayCache();
 
     private int mDisplayIdToMirror = INVALID_DISPLAY;
+    private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
 
     /**
      * Broadcast receiver that indicates when the Wifi display status changes.
@@ -1613,6 +1615,17 @@
     }
 
     /**
+     * Returns whether this device supports Always On Display.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_IS_ALWAYS_ON_AVAILABLE_API)
+    public boolean isAlwaysOnDisplayCurrentlyAvailable() {
+        return getAmbientDisplayConfiguration().alwaysOnAvailableForUser(mContext.getUserId());
+    }
+
+    /**
      * Returns whether device supports seamless refresh rate switching.
      *
      * Match content frame rate setting has three options: seamless, non-seamless and never.
@@ -1674,6 +1687,15 @@
         }
     }
 
+    private AmbientDisplayConfiguration getAmbientDisplayConfiguration() {
+        synchronized (this) {
+            if (mAmbientDisplayConfiguration == null) {
+                mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
+            }
+        }
+        return mAmbientDisplayConfiguration;
+    }
+
     /**
      * Creates a VirtualDisplay that will mirror the content of displayIdToMirror
      * @param name The name for the virtual display
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 9ad2e7f..036ccd8 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -118,18 +118,33 @@
         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();
-        }
+        final Instrumentation instrumentation = 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.
 
+        // Holdback study.
+        if (mUseConcurrent && Flags.messageQueueForceLegacy()) {
+            mUseConcurrent = false;
+        }
+
         mQuitAllowed = quitAllowed;
         mPtr = nativeInit();
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace(blockedBy = ActivityThread.class)
+    private static Instrumentation getInstrumentation() {
+        final ActivityThread activityThread = ActivityThread.currentActivityThread();
+        if (activityThread != null) {
+            return activityThread.getInstrumentation();
+        }
+        return null;
+    }
+
+    private static Instrumentation getInstrumentation$ravenwood() {
+        return null; // Instrumentation not supported on Ravenwood yet.
+    }
+
     @Override
     protected void finalize() throws Throwable {
         try {
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/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 70cbc73..f6bc389 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -1826,52 +1826,6 @@
     }
 
     /**
-     * Start building a waveform vibration.
-     *
-     * <p>The waveform envelope builder offers more flexibility for creating waveform effects,
-     * allowing control over vibration amplitude and frequency via smooth transitions between
-     * values. The waveform will start the first transition from the vibrator off state, using
-     * the same frequency of the first control point. To provide a different initial vibration
-     * frequency, use {@link #startWaveformEnvelope(float)}.
-     *
-     * <p>Note: To check whether waveform envelope effects are supported, use
-     * {@link Vibrator#areEnvelopeEffectsSupported()}.
-     *
-     * @see VibrationEffect.WaveformEnvelopeBuilder
-     */
-    @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
-    @NonNull
-    public static VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope() {
-        return new WaveformEnvelopeBuilder();
-    }
-
-    /**
-     * Start building a waveform vibration with an initial frequency.
-     *
-     * <p>The waveform envelope builder offers more flexibility for creating waveform effects,
-     * allowing control over vibration amplitude and frequency via smooth transitions between
-     * values.
-     *
-     * <p>This is the same as {@link #startWaveformEnvelope()}, but the waveform will start
-     * vibrating at given frequency, in hertz, while it transitions to the new amplitude and
-     * frequency of the first control point.
-     *
-     * <p>Note: To check whether waveform envelope effects are supported, use
-     * {@link Vibrator#areEnvelopeEffectsSupported()}.
-     *
-     * @param initialFrequencyHz The starting frequency of the vibration, in hertz. Must be greater
-     *                           than zero.
-     *
-     * @see VibrationEffect.WaveformEnvelopeBuilder
-     */
-    @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
-    @NonNull
-    public static VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope(
-            @FloatRange(from = 0) float initialFrequencyHz) {
-        return new WaveformEnvelopeBuilder(initialFrequencyHz);
-    }
-
-    /**
      * A builder for waveform effects described by its envelope.
      *
      * <p>Waveform effect envelopes are defined by one or more control points describing a target
@@ -1882,7 +1836,7 @@
      * 100ms, holds that state for 200ms, and then ramps back down over 100ms:
      *
      * <pre>{@code
-     * VibrationEffect effect = VibrationEffect.startWaveformEnvelope()
+     * VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
      *     .addControlPoint(1.0f, 120f, 100)
      *     .addControlPoint(1.0f, 120f, 200)
      *     .addControlPoint(0.0f, 120f, 100)
@@ -1916,20 +1870,48 @@
      * {@link VibratorEnvelopeEffectInfo#getMaxControlPointDurationMillis()}
      * <li>Maximum total effect duration: {@link VibratorEnvelopeEffectInfo#getMaxDurationMillis()}
      * </ul>
-     *
-     * @see VibrationEffect#startWaveformEnvelope()
      */
     @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
     public static final class WaveformEnvelopeBuilder {
 
         private ArrayList<PwleSegment> mSegments = new ArrayList<>();
         private float mLastAmplitude = 0f;
-        private float mLastFrequencyHz = 0f;
+        private float mLastFrequencyHz = Float.NaN;
 
-        private WaveformEnvelopeBuilder() {}
+        public WaveformEnvelopeBuilder() {}
 
-        private WaveformEnvelopeBuilder(float initialFrequency) {
-            mLastFrequencyHz = initialFrequency;
+        /**
+         * Sets the initial frequency for the waveform in Hertz.
+         *
+         * <p>The effect will start vibrating at this frequency when it transitions to the
+         * amplitude and frequency defined by the first control point.
+         *
+         * <p>The frequency must be greater than zero and within the supported range. To determine
+         * the supported range, use {@link Vibrator#getFrequencyProfile()}. Creating
+         * effects using frequencies outside this range will result in the vibration not playing.
+         *
+         * @param initialFrequencyHz The starting frequency of the vibration, in Hz. Must be
+         *                           greater than zero.
+         */
+        @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+        @SuppressWarnings("MissingGetterMatchingBuilder")// No getter to initial frequency once set.
+        @NonNull
+        public WaveformEnvelopeBuilder setInitialFrequencyHz(
+                @FloatRange(from = 0) float initialFrequencyHz) {
+
+            if (mSegments.isEmpty()) {
+                mLastFrequencyHz = initialFrequencyHz;
+            } else {
+                PwleSegment firstSegment = mSegments.getFirst();
+                mSegments.set(0, new PwleSegment(
+                        firstSegment.getStartAmplitude(),
+                        firstSegment.getEndAmplitude(),
+                        initialFrequencyHz, // Update start frequency
+                        firstSegment.getEndFrequencyHz(),
+                        (int) firstSegment.getDuration()));
+            }
+
+            return this;
         }
 
         /**
@@ -1940,15 +1922,13 @@
          * perceived intensity. It's determined by the actuator response curve.
          *
          * <p>Frequency must be greater than zero and within the supported range. To determine
-         * the supported range, use {@link Vibrator#getFrequencyProfile()}. This method returns a
-         * {@link android.os.vibrator.VibratorFrequencyProfile} object, which contains the
-         * minimum and maximum frequencies, among other frequency-related information. Creating
+         * the supported range, use {@link Vibrator#getFrequencyProfile()}. Creating
          * effects using frequencies outside this range will result in the vibration not playing.
          *
          * <p>Time specifies the duration (in milliseconds) for the vibrator to smoothly transition
          * from the previous control point to this new one. It must be greater than zero. To
          * transition as quickly as possible, use
-         * {@link Vibrator#getMinEnvelopeEffectControlPointDurationMillis()}.
+         * {@link VibratorEnvelopeEffectInfo#getMinControlPointDurationMillis()}.
          *
          * @param amplitude   The amplitude value between 0 and 1, inclusive. 0 represents the
          *                    vibrator being off, and 1 represents the maximum achievable amplitude
@@ -1963,7 +1943,7 @@
                 @FloatRange(from = 0, to = 1) float amplitude,
                 @FloatRange(from = 0) float frequencyHz, int timeMillis) {
 
-            if (mLastFrequencyHz == 0) {
+            if (Float.isNaN(mLastFrequencyHz)) {
                 mLastFrequencyHz = frequencyHz;
             }
 
@@ -1984,6 +1964,7 @@
          * calling this method again.
          *
          * @return The {@link VibrationEffect} resulting from the list of control points.
+         * @throws IllegalStateException if no control points were added to the builder.
          */
         @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
         @NonNull
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 118167d..0144506 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -4,6 +4,15 @@
 
 # keep-sorted start block=yes newline_separated=yes
 flag {
+     # Holdback study for concurrent MessageQueue.
+     # Do not promote beyond trunkfood.
+     namespace: "system_performance"
+     name: "message_queue_force_legacy"
+     description: "Whether to holdback concurrent MessageQueue (force legacy)."
+     bug: "336880969"
+}
+
+flag {
     name: "adpf_gpu_report_actual_work_duration"
     is_exported: true
     namespace: "game"
diff --git a/core/java/android/os/vibrator/VibratorEnvelopeEffectInfo.java b/core/java/android/os/vibrator/VibratorEnvelopeEffectInfo.java
index f2ad7a4..afaab55 100644
--- a/core/java/android/os/vibrator/VibratorEnvelopeEffectInfo.java
+++ b/core/java/android/os/vibrator/VibratorEnvelopeEffectInfo.java
@@ -34,11 +34,11 @@
  * </ul>
  *
  * <p>This information can be used to help construct waveform envelope effects with
- * {@link VibrationEffect#startWaveformEnvelope()}. When designing these effects, it is also
+ * {@link VibrationEffect.WaveformEnvelopeBuilder}. When designing these effects, it is also
  * recommended to check the {@link VibratorFrequencyProfile} for information about the supported
  * frequency range and the vibrator's output response.
  *
- * @see VibrationEffect#startWaveformEnvelope()
+ * @see VibrationEffect.WaveformEnvelopeBuilder
  * @see VibratorFrequencyProfile
  */
 @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 5a71282..8cb96ae 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -930,8 +930,8 @@
             // of the next vsync event.
             int totalFrameDelays = mBufferStuffingData.numberFrameDelays
                     + mBufferStuffingData.numberWaitsForNextVsync + 1;
-            long vsyncsSinceLastCallback =
-                    (frameTimeNanos - mLastNoOffsetFrameTimeNanos) / mLastFrameIntervalNanos;
+            long vsyncsSinceLastCallback = mLastFrameIntervalNanos > 0
+                    ? (frameTimeNanos - mLastNoOffsetFrameTimeNanos) / mLastFrameIntervalNanos : 0;
 
             // Detected idle state due to a longer inactive period since the last vsync callback
             // than the total expected number of vsync frame delays. End buffer stuffing recovery.
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/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index f474b34..eebdeadc 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -402,4 +402,11 @@
     namespace: "lse_desktop_experience"
     description: "Enables HSUM on desktop mode."
     bug: "366397912"
+}
+
+flag {
+    name: "enable_multiple_desktops"
+    namespace: "lse_desktop_experience"
+    description: "Enable multiple desktop sessions for desktop windowing."
+    bug: "379158791"
 }
\ 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/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 8a6e6be..6db3c4d 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -2377,6 +2377,7 @@
      * Flags are separated by type and by default value. They are sorted alphabetically within each
      * section.
      */
+    @SuppressWarnings("AndroidFrameworkCompatChange")
     private void parseBaseAppBasicFlags(ParsingPackage pkg, TypedArray sa) {
         int targetSdk = pkg.getTargetSdkVersion();
         //@formatter:off
@@ -2414,12 +2415,19 @@
                 .setResetEnabledSettingsOnAppDataCleared(bool(false,
                         R.styleable.AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared,
                         sa))
-                .setOnBackInvokedCallbackEnabled(bool(false, R.styleable.AndroidManifestApplication_enableOnBackInvokedCallback, sa))
                 // targetSdkVersion gated
                 .setAllowAudioPlaybackCapture(bool(targetSdk >= Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture, sa))
                 .setHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa))
                 .setRequestLegacyExternalStorage(bool(targetSdk < Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_requestLegacyExternalStorage, sa))
                 .setCleartextTrafficAllowed(bool(targetSdk < Build.VERSION_CODES.P, R.styleable.AndroidManifestApplication_usesCleartextTraffic, sa))
+                // CompatChange.isChangeEnabled() can't be used here because this is called during
+                // PackageManagerService initialization. PlatformCompat can't be used because this
+                // code is not guaranteed to be called from the system_server process. Therefore
+                // accessing Build.VERSION_CODES directly and suppressing
+                // AndroidFrameworkCompatChange warning
+                .setOnBackInvokedCallbackEnabled(bool(
+                        targetSdk > Build.VERSION_CODES.VANILLA_ICE_CREAM,
+                        R.styleable.AndroidManifestApplication_enableOnBackInvokedCallback, sa))
                 // Ints Default 0
                 .setUiOptions(anInt(R.styleable.AndroidManifestApplication_uiOptions, sa))
                 // Ints
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 1925b3a..593b982 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -2201,29 +2201,9 @@
             return false;
         }
 
-        // Compute the count of data items we'll actually forward to Java.
-        size_t count = 0;
-        if (mRemovedVsyncId <= 0) {
-            count = jankData.size();
-        } else {
-            for (const gui::JankData& frame : jankData) {
-                if (frame.frameVsyncId <= mRemovedVsyncId) {
-                    count++;
-                }
-            }
-        }
-
-        if (count == 0) {
-            return false;
-        }
-
-        jobjectArray jJankDataArray = env->NewObjectArray(count, gJankDataClassInfo.clazz, nullptr);
-        for (size_t i = 0, j = 0; i < jankData.size() && j < count; i++) {
-            // Filter any data for frames past our removal vsync.
-            if (mRemovedVsyncId > 0 && jankData[i].frameVsyncId > mRemovedVsyncId) {
-                continue;
-            }
-
+        jobjectArray jJankDataArray =
+                env->NewObjectArray(jankData.size(), gJankDataClassInfo.clazz, nullptr);
+        for (size_t i = 0; i < jankData.size(); i++) {
             // The exposed constants in SurfaceControl are simplified, so we need to translate the
             // jank type we get from SF to what is exposed in Java.
             int sfJankType = jankData[i].jankType;
@@ -2250,7 +2230,7 @@
                                    jankData[i].frameVsyncId, javaJankType,
                                    jankData[i].frameIntervalNs, jankData[i].scheduledAppFrameTimeNs,
                                    jankData[i].actualAppFrameTimeNs);
-            env->SetObjectArrayElement(jJankDataArray, j++, jJankData);
+            env->SetObjectArrayElement(jJankDataArray, i, jJankData);
             env->DeleteLocalRef(jJankData);
         }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 95d07df..0042459 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7921,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> -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 089b4b3..5088b5a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7225,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/tests/coretests/src/android/content/pm/SharedLibraryInfoTest.java b/core/tests/coretests/src/android/content/pm/SharedLibraryInfoTest.java
new file mode 100644
index 0000000..df5cd4e
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/SharedLibraryInfoTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+@Presubmit
+@RunWith(JUnit4.class)
+public final class SharedLibraryInfoTest {
+
+    @Rule
+    public final CheckFlagsRule checkFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    private static final String LIBRARY_NAME = "name";
+    private static final long VERSION_MAJOR = 1L;
+    private static final List<String> CERT_DIGESTS = ImmutableList.of("digest1", "digest2");
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+    public void sharedLibraryInfo_serializedAndDeserialized_retainsCertDigestInfo() {
+        SharedLibraryInfo toParcel = new SharedLibraryInfo(LIBRARY_NAME, VERSION_MAJOR,
+                SharedLibraryInfo.TYPE_SDK_PACKAGE, CERT_DIGESTS);
+
+        SharedLibraryInfo fromParcel = parcelAndUnparcel(toParcel);
+
+        assertThat(fromParcel.getCertDigests().size()).isEqualTo(toParcel.getCertDigests().size());
+        assertThat(fromParcel.getCertDigests().get(0)).isEqualTo(toParcel.getCertDigests().get(0));
+        assertThat(fromParcel.getCertDigests().get(1)).isEqualTo(toParcel.getCertDigests().get(1));
+    }
+
+    private SharedLibraryInfo parcelAndUnparcel(SharedLibraryInfo sharedLibraryInfo) {
+        Parcel parcel = Parcel.obtain();
+        parcel.setDataPosition(0);
+        sharedLibraryInfo.writeToParcel(parcel, /* flags= */0);
+
+        parcel.setDataPosition(0);
+        return SharedLibraryInfo.CREATOR.createFromParcel(parcel);
+    }
+}
diff --git a/core/tests/vibrator/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
index 1cd1190..ccc5108 100644
--- a/core/tests/vibrator/src/android/os/VibrationEffectTest.java
+++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
@@ -414,10 +414,10 @@
 
     @Test
     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
-    public void computeLegacyPattern_effectsViaStartWaveformEnvelope() {
-        // Effects created via startWaveformEnvelope are not expected to be converted to long[]
+    public void computeLegacyPattern_effectsViaWaveformEnvelopeBuilder() {
+        // Effects created via waveformEnvelopeBuilder are not expected to be converted to long[]
         // patterns, as they are not configured to always play with the default amplitude.
-        VibrationEffect effect = VibrationEffect.startWaveformEnvelope()
+        VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
                 .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 50)
                 .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 80)
@@ -426,7 +426,8 @@
 
         assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
 
-        effect = VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 60)
+        effect = new VibrationEffect.WaveformEnvelopeBuilder()
+                .setInitialFrequencyHz(/*initialFrequencyHz=*/ 60)
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
                 .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 50)
                 .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 80)
@@ -644,7 +645,7 @@
     @Test
     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
     public void testValidateWaveformEnvelopeBuilder() {
-        VibrationEffect.startWaveformEnvelope()
+        new VibrationEffect.WaveformEnvelopeBuilder()
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
                 .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 50)
                 .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 80)
@@ -652,7 +653,8 @@
                 .build()
                 .validate();
 
-        VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 30)
+        new VibrationEffect.WaveformEnvelopeBuilder()
+                .setInitialFrequencyHz(/*initialFrequencyHz=*/ 30)
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
                 .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 50)
                 .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 80)
@@ -661,13 +663,13 @@
                 .validate();
 
         VibrationEffect.createRepeatingEffect(
-                /*preamble=*/ VibrationEffect.startWaveformEnvelope()
+                /*preamble=*/ new VibrationEffect.WaveformEnvelopeBuilder()
                         .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f,
                                 /*timeMillis=*/ 20)
                         .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f,
                                 /*timeMillis=*/ 50)
                         .build(),
-                /*repeatingEffect=*/ VibrationEffect.startWaveformEnvelope()
+                /*repeatingEffect=*/ new VibrationEffect.WaveformEnvelopeBuilder()
                         .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f,
                                 /*timeMillis=*/ 20)
                         .addControlPoint(/*amplitude=*/ 0.5f, /*frequencyHz=*/ 150f,
@@ -676,7 +678,7 @@
         ).validate();
 
         VibrationEffect.createRepeatingEffect(
-                /*effect=*/ VibrationEffect.startWaveformEnvelope()
+                /*effect=*/ new VibrationEffect.WaveformEnvelopeBuilder()
                         .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f,
                                 /*timeMillis=*/ 20)
                         .addControlPoint(/*amplitude=*/ 0.5f, /*frequencyHz=*/ 150f,
@@ -685,59 +687,70 @@
         ).validate();
 
         assertThrows(IllegalStateException.class,
-                () -> VibrationEffect.startWaveformEnvelope().build().validate());
+                () -> new VibrationEffect.WaveformEnvelopeBuilder().build().validate());
         assertThrows(IllegalArgumentException.class,
-                () -> VibrationEffect.startWaveformEnvelope()
+                () -> new VibrationEffect.WaveformEnvelopeBuilder()
                         .addControlPoint(/*amplitude=*/ -1.0f, /*frequencyHz=*/ 60f,
                                 /*timeMillis=*/ 20)
                         .build()
                         .validate());
         assertThrows(IllegalArgumentException.class,
-                () -> VibrationEffect.startWaveformEnvelope()
+                () -> new VibrationEffect.WaveformEnvelopeBuilder()
                         .addControlPoint(/*amplitude=*/ 1.1f, /*frequencyHz=*/ 60f,
                                 /*timeMillis=*/ 20)
                         .build()
                         .validate());
         assertThrows(IllegalArgumentException.class,
-                () -> VibrationEffect.startWaveformEnvelope()
+                () -> new VibrationEffect.WaveformEnvelopeBuilder()
                         .addControlPoint(/*amplitude=*/ 0.8f, /*frequencyHz=*/ 0f,
                                 /*timeMillis=*/ 20)
                         .build()
                         .validate());
         assertThrows(IllegalArgumentException.class,
-                () -> VibrationEffect.startWaveformEnvelope()
+                () -> new VibrationEffect.WaveformEnvelopeBuilder()
                         .addControlPoint(/*amplitude=*/ 0.8f, /*frequencyHz=*/ 100f,
                                 /*timeMillis=*/ 0)
                         .build()
                         .validate());
 
         assertThrows(IllegalStateException.class,
-                () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+                () -> new VibrationEffect.WaveformEnvelopeBuilder()
+                        .setInitialFrequencyHz(/*initialFrequencyHz=*/ 30)
                         .build().validate());
         assertThrows(IllegalArgumentException.class,
-                () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+                () -> new VibrationEffect.WaveformEnvelopeBuilder()
+                        .setInitialFrequencyHz(/*initialFrequencyHz=*/ 30)
                         .addControlPoint(/*amplitude=*/ -1.0f, /*frequencyHz=*/ 60f,
                                 /*timeMillis=*/ 20)
                         .build()
                         .validate());
         assertThrows(IllegalArgumentException.class,
-                () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+                () -> new VibrationEffect.WaveformEnvelopeBuilder()
+                        .setInitialFrequencyHz(/*initialFrequencyHz=*/ 30)
                         .addControlPoint(/*amplitude=*/ 1.1f, /*frequencyHz=*/ 60f,
                                 /*timeMillis=*/ 20)
                         .build()
                         .validate());
         assertThrows(IllegalArgumentException.class,
-                () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+                () -> new VibrationEffect.WaveformEnvelopeBuilder()
+                        .setInitialFrequencyHz(/*initialFrequencyHz=*/ 30)
                         .addControlPoint(/*amplitude=*/ 0.8f, /*frequencyHz=*/ 0f,
                                 /*timeMillis=*/ 20)
                         .build()
                         .validate());
         assertThrows(IllegalArgumentException.class,
-                () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+                () -> new VibrationEffect.WaveformEnvelopeBuilder()
+                        .setInitialFrequencyHz(/*initialFrequencyHz=*/ 30)
                         .addControlPoint(/*amplitude=*/ 0.8f, /*frequencyHz=*/ 100f,
                                 /*timeMillis=*/ 0)
                         .build()
                         .validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> new VibrationEffect.WaveformEnvelopeBuilder()
+                        .setInitialFrequencyHz(/*initialFrequencyHz=*/ 0)
+                        .addControlPoint(/*amplitude=*/ 0.8f, /*frequencyHz=*/ 100f,
+                                /*timeMillis=*/ 20)
+                        .build().validate());
     }
 
     @Test
@@ -1381,14 +1394,15 @@
     @Test
     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
     public void testIsHapticFeedbackCandidate_longEnvelopeEffects_notCandidates() {
-        assertFalse(VibrationEffect.startWaveformEnvelope()
+        assertFalse(new VibrationEffect.WaveformEnvelopeBuilder()
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 200)
                 .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500)
                 .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 800)
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400)
                 .build()
                 .isHapticFeedbackCandidate());
-        assertFalse(VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 40)
+        assertFalse(new VibrationEffect.WaveformEnvelopeBuilder()
+                .setInitialFrequencyHz(/*initialFrequencyHz=*/ 40)
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 200)
                 .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500)
                 .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 800)
@@ -1413,13 +1427,14 @@
     @Test
     @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
     public void testIsHapticFeedbackCandidate_shortEnvelopeEffects_areCandidates() {
-        assertTrue(VibrationEffect.startWaveformEnvelope()
+        assertTrue(new VibrationEffect.WaveformEnvelopeBuilder()
                 .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500)
                 .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400)
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 100)
                 .build()
                 .isHapticFeedbackCandidate());
-        assertTrue(VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 30)
+        assertTrue(new VibrationEffect.WaveformEnvelopeBuilder()
+                .setInitialFrequencyHz(/*initialFrequencyHz=*/ 30)
                 .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500)
                 .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400)
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 100)
diff --git a/graphics/java/android/graphics/RuntimeColorFilter.java b/graphics/java/android/graphics/RuntimeColorFilter.java
index 52724ce..d112f71 100644
--- a/graphics/java/android/graphics/RuntimeColorFilter.java
+++ b/graphics/java/android/graphics/RuntimeColorFilter.java
@@ -283,6 +283,23 @@
         nativeUpdateChild(getNativeInstance(), filterName, colorFilter.getNativeInstance());
     }
 
+    /**
+     * Assigns the uniform xfermode to the provided xfermode parameter.  If the shader program does
+     * not have a uniform xfermode with that name then an IllegalArgumentException is thrown.
+     *
+     * @param xfermodeName name matching the uniform declared in the AGSL program
+     * @param xfermode filter passed into the AGSL program for sampling
+     */
+    public void setInputXfermode(@NonNull String xfermodeName, @NonNull RuntimeXfermode xfermode) {
+        if (xfermodeName == null) {
+            throw new NullPointerException("The xfermodeName parameter must not be null");
+        }
+        if (xfermode == null) {
+            throw new NullPointerException("The xfermode parameter must not be null");
+        }
+        nativeUpdateChild(getNativeInstance(), xfermodeName, xfermode.createNativeInstance());
+    }
+
     /** @hide */
     @Override
     protected long createNativeInstance() {
diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java
index 78d257f..6316c1f 100644
--- a/graphics/java/android/graphics/RuntimeShader.java
+++ b/graphics/java/android/graphics/RuntimeShader.java
@@ -18,10 +18,13 @@
 
 import android.annotation.ColorInt;
 import android.annotation.ColorLong;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.util.ArrayMap;
 import android.view.Window;
 
+import com.android.graphics.hwui.flags.Flags;
+
 import libcore.util.NativeAllocationRegistry;
 
 /**
@@ -525,6 +528,45 @@
         discardNativeInstance();
     }
 
+    /**
+     * Assigns the uniform color filter to the provided color filter parameter.  If the shader
+     * program does not have a uniform color filter with that name then an IllegalArgumentException
+     * is thrown.
+     *
+     * @param filterName name matching the uniform declared in the AGSL program
+     * @param colorFilter filter passed into the AGSL program for sampling
+     */
+    @FlaggedApi(Flags.FLAG_RUNTIME_COLOR_FILTERS_BLENDERS)
+    public void setInputColorFilter(@NonNull String filterName, @NonNull ColorFilter colorFilter) {
+        if (filterName == null) {
+            throw new NullPointerException("The filterName parameter must not be null");
+        }
+        if (colorFilter == null) {
+            throw new NullPointerException("The colorFilter parameter must not be null");
+        }
+        nativeUpdateChild(mNativeInstanceRuntimeShaderBuilder, filterName,
+                colorFilter.getNativeInstance());
+    }
+
+    /**
+     * Assigns the uniform xfermode to the provided xfermode parameter.  If the shader program does
+     * not have a uniform xfermode with that name then an IllegalArgumentException is thrown.
+     *
+     * @param xfermodeName name matching the uniform declared in the AGSL program
+     * @param xfermode filter passed into the AGSL program for sampling
+     */
+    @FlaggedApi(Flags.FLAG_RUNTIME_COLOR_FILTERS_BLENDERS)
+    public void setInputXfermode(@NonNull String xfermodeName, @NonNull RuntimeXfermode xfermode) {
+        if (xfermodeName == null) {
+            throw new NullPointerException("The xfermodeName parameter must not be null");
+        }
+        if (xfermode == null) {
+            throw new NullPointerException("The xfermode parameter must not be null");
+        }
+        nativeUpdateChild(mNativeInstanceRuntimeShaderBuilder, xfermodeName,
+                xfermode.createNativeInstance());
+    }
+
 
     /** @hide */
     @Override
@@ -552,5 +594,7 @@
             int value4, int count);
     private static native void nativeUpdateShader(
             long shaderBuilder, String shaderName, long shader);
+    private static native void nativeUpdateChild(
+            long shaderBuilder, String childName, long child);
 }
 
diff --git a/graphics/java/android/graphics/RuntimeXfermode.java b/graphics/java/android/graphics/RuntimeXfermode.java
index f5a6568..51d97a4 100644
--- a/graphics/java/android/graphics/RuntimeXfermode.java
+++ b/graphics/java/android/graphics/RuntimeXfermode.java
@@ -288,6 +288,23 @@
         nativeUpdateChild(mBuilderNativeInstance, filterName, colorFilter.getNativeInstance());
     }
 
+    /**
+     * Assigns the uniform xfermode to the provided xfermode parameter.  If the shader program does
+     * not have a uniform xfermode with that name then an IllegalArgumentException is thrown.
+     *
+     * @param xfermodeName name matching the uniform declared in the AGSL program
+     * @param xfermode xfermode function passed into the AGSL program for sampling
+     */
+    public void setInputXfermode(@NonNull String xfermodeName, @NonNull RuntimeXfermode xfermode) {
+        if (xfermodeName == null) {
+            throw new NullPointerException("The xfermodeName parameter must not be null");
+        }
+        if (xfermode == null) {
+            throw new NullPointerException("The xfermode parameter must not be null");
+        }
+        nativeUpdateChild(mBuilderNativeInstance, xfermodeName, xfermode.createNativeInstance());
+    }
+
     /** @hide */
     public long createNativeInstance() {
         return nativeCreateNativeInstance(mBuilderNativeInstance);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
index 220fc6f..819cf34 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
@@ -94,7 +94,6 @@
      */
     void scheduleBackup() {
         if (!mSaveEmbeddingState) {
-            // TODO(b/289875940): enabled internally for broader testing.
             return;
         }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java
index cb280c5..0f1246c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java
@@ -44,7 +44,6 @@
     @NonNull
     private final IBinder mSecondaryContainerToken;
 
-    // TODO(b/289875940): making this as non-null once the tag can be auto-generated from the rule.
     @Nullable
     final String mSplitRuleTag;
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java
index a79a89a..bf342d7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java
@@ -25,6 +25,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * This class holds the Parcelable data of a {@link TaskFragmentContainer}.
  */
@@ -61,6 +64,12 @@
     @NonNull
     final Rect mLastRequestedBounds;
 
+    /**
+     * Individual associated activity tokens in different containers that should be finished on
+     * exit.
+     */
+    final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
+
     ParcelableTaskFragmentContainerData(@NonNull IBinder token, @Nullable String overlayTag,
             @Nullable IBinder associatedActivityToken) {
         mToken = token;
@@ -74,6 +83,7 @@
         mOverlayTag = in.readString();
         mAssociatedActivityToken = in.readStrongBinder();
         mLastRequestedBounds = in.readTypedObject(Rect.CREATOR);
+        in.readBinderList(mActivitiesToFinishOnExit);
     }
 
     public static final Creator<ParcelableTaskFragmentContainerData> CREATOR = new Creator<>() {
@@ -99,7 +109,7 @@
         dest.writeString(mOverlayTag);
         dest.writeStrongBinder(mAssociatedActivityToken);
         dest.writeTypedObject(mLastRequestedBounds, flags);
+        dest.writeBinderList(mActivitiesToFinishOnExit);
     }
-
 }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index faf73c2..5ba30dd 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -98,10 +98,20 @@
         mCurrentSplitAttributes = mDefaultSplitAttributes;
 
         if (shouldFinishPrimaryWithSecondary(splitRule)) {
-            mSecondaryContainer.addContainerToFinishOnExit(mPrimaryContainer);
+            addContainerToFinishOnExitWhenRestore(mSecondaryContainer, mPrimaryContainer);
         }
         if (shouldFinishSecondaryWithPrimary(splitRule)) {
-            mPrimaryContainer.addContainerToFinishOnExit(mSecondaryContainer);
+            addContainerToFinishOnExitWhenRestore(mPrimaryContainer, mSecondaryContainer);
+        }
+    }
+
+    private void addContainerToFinishOnExitWhenRestore(
+            @NonNull TaskFragmentContainer containerToAdd,
+            @NonNull TaskFragmentContainer containerToFinish) {
+        // If an activity was already added to be finished after the restoration, then that's it.
+        // Otherwise, add the container to finish on exit.
+        if (!containerToAdd.hasActivityToFinishOnExit(containerToFinish)) {
+            containerToAdd.addContainerToFinishOnExit(containerToFinish);
         }
     }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index dc1d983..b3e003e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -96,12 +96,6 @@
             new ArrayList<>();
 
     /**
-     * Individual associated activity tokens in different containers that should be finished on
-     * exit.
-     */
-    private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
-
-    /**
      * The launch options that was used to create this container. Must not {@link Bundle#isEmpty()}
      * for {@link #isOverlay()} container.
      */
@@ -114,7 +108,6 @@
     /**
      * Windowing mode that was requested last via {@link android.window.WindowContainerTransaction}.
      */
-    // TODO(b/289875940): review this and other field that might need to be moved in the base class.
     @WindowingMode
     private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED;
 
@@ -443,7 +436,7 @@
             // Remove the activity now because there can be a delay before the server callback.
             mInfo.getActivities().remove(activityToken);
         }
-        mActivitiesToFinishOnExit.remove(activityToken);
+        mParcelableData.mActivitiesToFinishOnExit.remove(activityToken);
         finishSelfWithActivityIfNeeded(wct, activityToken);
     }
 
@@ -624,7 +617,20 @@
         if (mIsFinished) {
             return;
         }
-        mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken());
+        mParcelableData.mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken());
+    }
+
+    /**
+     * Returns {@code true} if an Activity from the given {@code container} was added to be
+     * finished on exit. Otherwise, return {@code false}.
+     */
+    boolean hasActivityToFinishOnExit(@NonNull TaskFragmentContainer container) {
+        for (IBinder activity : mParcelableData.mActivitiesToFinishOnExit) {
+            if (container.hasActivity(activity)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
@@ -634,7 +640,7 @@
         if (mIsFinished) {
             return;
         }
-        mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken());
+        mParcelableData.mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken());
     }
 
     /** Removes all dependencies that should be finished when this container is finished. */
@@ -643,7 +649,7 @@
             return;
         }
         mContainersToFinishOnExit.clear();
-        mActivitiesToFinishOnExit.clear();
+        mParcelableData.mActivitiesToFinishOnExit.clear();
     }
 
     /**
@@ -721,7 +727,7 @@
         mContainersToFinishOnExit.clear();
 
         // Finish associated activities
-        for (IBinder activityToken : mActivitiesToFinishOnExit) {
+        for (IBinder activityToken : mParcelableData.mActivitiesToFinishOnExit) {
             final Activity activity = mController.getActivity(activityToken);
             if (activity == null || activity.isFinishing()
                     || controller.shouldRetainAssociatedActivity(this, activity)) {
@@ -729,7 +735,7 @@
             }
             wct.finishActivity(activity.getActivityToken());
         }
-        mActivitiesToFinishOnExit.clear();
+        mParcelableData.mActivitiesToFinishOnExit.clear();
     }
 
     @GuardedBy("mController.mLock")
@@ -1082,7 +1088,7 @@
                 + " pendingAppearedActivities=" + mPendingAppearedActivities
                 + (includeContainersToFinishOnExit ? " containersToFinishOnExit="
                 + containersToFinishOnExitToString() : "")
-                + " activitiesToFinishOnExit=" + mActivitiesToFinishOnExit
+                + " activitiesToFinishOnExit=" + mParcelableData.mActivitiesToFinishOnExit
                 + " info=" + mInfo
                 + "}";
     }
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
index 23e7441..f14dfdb 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
@@ -50,6 +50,22 @@
     fun createMenu(snapshotList: List<Pair<Int, TaskSnapshot>>,
              onIconClickListener: ((Int) -> Unit),
              onOutsideClickListener: (() -> Unit)): ManageWindowsView {
+        val bitmapList = snapshotList.map { (index, snapshot) ->
+            index to Bitmap.wrapHardwareBuffer(snapshot.hardwareBuffer, snapshot.colorSpace)
+        }
+        return createAndShowMenuView(
+            bitmapList,
+            onIconClickListener,
+            onOutsideClickListener
+        )
+    }
+
+    /** Creates the menu view with the given bitmaps, and displays it. */
+    fun createAndShowMenuView(
+        snapshotList: List<Pair<Int, Bitmap?>>,
+        onIconClickListener: ((Int) -> Unit),
+        onOutsideClickListener: (() -> Unit)
+    ): ManageWindowsView {
         menuView = ManageWindowsView(context, menuBackgroundColor).apply {
             this.onOutsideClickListener = onOutsideClickListener
             this.onIconClickListener = onIconClickListener
@@ -120,7 +136,7 @@
         }
 
         fun generateIconViews(
-            snapshotList: List<Pair<Int, TaskSnapshot>>
+            snapshotList: List<Pair<Int, Bitmap?>>
         ) {
             menuWidth = 0
             menuHeight = 0
@@ -133,7 +149,7 @@
             // Add each icon to the menu, adding a new row when needed.
             for ((iconCount, taskInfoSnapshotPair) in snapshotList.withIndex()) {
                 val taskId = taskInfoSnapshotPair.first
-                val snapshot = taskInfoSnapshotPair.second
+                val snapshotBitmap = taskInfoSnapshotPair.second
                 // Once a row is filled, make a new row and increase the menu height.
                 if (iconCount % MENU_MAX_ICONS_PER_ROW == 0) {
                     rowLayout = LinearLayout(context)
@@ -141,10 +157,7 @@
                     rootView.addView(rowLayout)
                     menuHeight += (instanceIconHeight + iconMargin).toInt()
                 }
-                val snapshotBitmap = Bitmap.wrapHardwareBuffer(
-                    snapshot.hardwareBuffer,
-                    snapshot.colorSpace
-                )
+
                 val croppedBitmap = snapshotBitmap?.let { cropBitmap(it) }
                 val scaledSnapshotBitmap = croppedBitmap?.let {
                     Bitmap.createScaledBitmap(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt
new file mode 100644
index 0000000..3bcbf13
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.annotation.ColorRes
+import android.content.Context
+import android.graphics.Color
+import com.android.internal.R
+import com.android.wm.shell.dagger.WMSingleton
+import javax.inject.Inject
+
+/**
+ * Contains configuration properties for the letterbox implementation in Shell.
+ */
+@WMSingleton
+class LetterboxConfiguration @Inject constructor(
+    private val context: Context
+) {
+
+    // TODO(b/370940685): Integrate CommandHandler in LetterboxConfiguration.
+    private var letterboxBackgroundColorOverride: Color? = Color.valueOf(Color.YELLOW)
+
+    // Color resource id for the solid color letterbox background type.
+    private var letterboxBackgroundColorResourceIdOverride: Int? = null
+
+    /**
+     * Sets color of letterbox background which is used when using the solid background mode.
+     */
+    fun setLetterboxBackgroundColor(color: Color) {
+        letterboxBackgroundColorOverride = color
+    }
+
+    /**
+     * Sets color ID of letterbox background which is used when using the solid background mode.
+     */
+    fun setLetterboxBackgroundColorResourceId(@ColorRes colorId: Int) {
+        letterboxBackgroundColorResourceIdOverride = colorId
+    }
+
+    /**
+     * Gets color of letterbox background which is used when the solid color mode is active.
+     */
+    fun getLetterboxBackgroundColor(): Color {
+        if (letterboxBackgroundColorOverride != null) {
+            return letterboxBackgroundColorOverride!!
+        }
+        val colorId = if (letterboxBackgroundColorResourceIdOverride != null) {
+            letterboxBackgroundColorResourceIdOverride
+        } else {
+            R.color.config_letterboxBackgroundColor
+        }
+        // Query color dynamically because material colors extracted from wallpaper are updated
+        // when wallpaper is changed.
+        return Color.valueOf(context.getResources().getColor(colorId!!, /* theme */null))
+    }
+
+    /**
+     * Resets color of letterbox background to the default.
+     */
+    fun resetLetterboxBackgroundColor() {
+        letterboxBackgroundColorOverride = null
+        letterboxBackgroundColorResourceIdOverride = null
+    }
+
+    /**
+     * The background color for the Letterbox.
+     */
+    fun getBackgroundColorRgbArray(): FloatArray = getLetterboxBackgroundColor().components
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt
new file mode 100644
index 0000000..0ac7aff
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.graphics.Rect
+import android.view.SurfaceControl
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.dagger.WMSingleton
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT
+import javax.inject.Inject
+
+/**
+ * Component responsible for handling the lifecycle of the letterbox surfaces.
+ */
+@WMSingleton
+class LetterboxController @Inject constructor(
+    private val letterboxConfiguration: LetterboxConfiguration
+) {
+
+    companion object {
+        /*
+         * Letterbox surfaces need to stay below the activity layer which is 0.
+         */
+        // TODO(b/378673153): Consider adding this to [TaskConstants].
+        @JvmStatic
+        private val TASK_CHILD_LAYER_LETTERBOX_BACKGROUND = -1000
+        @JvmStatic
+        private val TAG = "LetterboxController"
+    }
+
+    private val letterboxMap = mutableMapOf<LetterboxKey, LetterboxItem>()
+
+    /**
+     * Creates a Letterbox Surface for a given displayId/taskId if it doesn't exist.
+     */
+    fun createLetterboxSurface(
+        key: LetterboxKey,
+        startTransaction: SurfaceControl.Transaction,
+        parentLeash: SurfaceControl
+    ) {
+        letterboxMap.runOnItem(key, onMissed = { k, m ->
+            m[k] = LetterboxItem(
+                SurfaceControl.Builder()
+                    .setName("ShellLetterboxSurface-$key")
+                    .setHidden(true)
+                    .setColorLayer()
+                    .setParent(parentLeash)
+                    .setCallsite("LetterboxController-createLetterboxSurface")
+                    .build().apply {
+                        startTransaction.setLayer(
+                            this,
+                            TASK_CHILD_LAYER_LETTERBOX_BACKGROUND
+                        ).setColorSpaceAgnostic(this, true)
+                            .setColor(this, letterboxConfiguration.getBackgroundColorRgbArray())
+                    }
+            )
+        })
+    }
+
+    /**
+     * Invoked to destroy the surfaces for a letterbox session for given displayId/taskId.
+     */
+    fun destroyLetterboxSurface(
+        key: LetterboxKey,
+        startTransaction: SurfaceControl.Transaction
+    ) {
+        letterboxMap.runOnItem(key, onFound = { item ->
+            item.fullWindowSurface?.run {
+                startTransaction.remove(this)
+            }
+        })
+        letterboxMap.remove(key)
+    }
+
+    /**
+     * Invoked to show/hide the letterbox surfaces for given displayId/taskId.
+     */
+    fun updateLetterboxSurfaceVisibility(
+        key: LetterboxKey,
+        startTransaction: SurfaceControl.Transaction,
+        visible: Boolean = true
+    ) {
+        letterboxMap.runOnItem(key, onFound = { item ->
+            item.fullWindowSurface?.run {
+                startTransaction.setVisibility(this, visible)
+            }
+        })
+    }
+
+    /**
+     * Updates the bounds for the letterbox surfaces for given displayId/taskId.
+     */
+    fun updateLetterboxSurfaceBounds(
+        key: LetterboxKey,
+        startTransaction: SurfaceControl.Transaction,
+        bounds: Rect
+    ) {
+        letterboxMap.runOnItem(key, onFound = { item ->
+            item.fullWindowSurface?.run {
+                startTransaction.moveAndCrop(this, bounds)
+            }
+        })
+    }
+
+    /*
+     * Executes [onFound] on the [LetterboxItem] if present or [onMissed] if not present.
+     */
+    private fun MutableMap<LetterboxKey, LetterboxItem>.runOnItem(
+        key: LetterboxKey,
+        onFound: (LetterboxItem) -> Unit = { _ -> },
+        onMissed: (
+            LetterboxKey,
+            MutableMap<LetterboxKey, LetterboxItem>
+        ) -> Unit = { _, _ -> }
+    ) {
+        this[key]?.let {
+            return onFound(it)
+        }
+        return onMissed(key, this)
+    }
+
+    fun dump() {
+        ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, "${letterboxMap.keys}")
+    }
+
+    private fun SurfaceControl.Transaction.moveAndCrop(
+        surface: SurfaceControl,
+        rect: Rect
+    ): SurfaceControl.Transaction =
+        setPosition(surface, rect.left.toFloat(), rect.top.toFloat())
+            .setWindowCrop(
+                surface,
+                rect.width(),
+                rect.height()
+            )
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt
new file mode 100644
index 0000000..98fd247
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.view.SurfaceControl
+
+// The key to use for identify the letterbox sessions.
+data class LetterboxKey(val displayId: Int, val taskId: Int)
+
+// Encapsulate the objects for the specific letterbox session.
+data class LetterboxItem(val fullWindowSurface: SurfaceControl?)
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
new file mode 100644
index 0000000..67429bd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.graphics.Rect
+import android.os.IBinder
+import android.view.SurfaceControl
+import android.window.TransitionInfo
+import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags.appCompatRefactoring
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT
+import com.android.wm.shell.shared.TransitionUtil.isClosingType
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+
+/**
+ * The [TransitionObserver] to handle Letterboxing events in Shell.
+ */
+class LetterboxTransitionObserver(
+    shellInit: ShellInit,
+    private val transitions: Transitions,
+    private val letterboxController: LetterboxController
+) : Transitions.TransitionObserver {
+
+    companion object {
+        @JvmStatic
+        private val TAG = "LetterboxTransitionObserver"
+    }
+
+    init {
+        if (appCompatRefactoring()) {
+            ProtoLog.v(
+                WM_SHELL_APP_COMPAT,
+                "%s: %s",
+                TAG,
+                "Initializing LetterboxTransitionObserver"
+            )
+            shellInit.addInitCallback({
+                transitions.registerObserver(this)
+            }, this)
+        }
+    }
+
+    override fun onTransitionReady(
+        transition: IBinder,
+        info: TransitionInfo,
+        startTransaction: SurfaceControl.Transaction,
+        finishTransaction: SurfaceControl.Transaction
+    ) {
+        // We recognise the operation to execute and delegate to the LetterboxController
+        // the related operation.
+        // TODO(b/377875151): Identify Desktop Windowing Transactions.
+        // TODO(b/377857898): Handling multiple surfaces
+        // TODO(b/371500295): Handle input events detection.
+        for (change in info.changes) {
+            change.taskInfo?.let { ti ->
+                val key = LetterboxKey(ti.displayId, ti.taskId)
+                if (isClosingType(change.mode)) {
+                    letterboxController.destroyLetterboxSurface(
+                        key,
+                        startTransaction
+                    )
+                } else {
+                    val isTopActivityLetterboxed = ti.appCompatTaskInfo.isTopActivityLetterboxed
+                    if (isTopActivityLetterboxed) {
+                        letterboxController.createLetterboxSurface(
+                            key,
+                            startTransaction,
+                            change.leash
+                        )
+                        letterboxController.updateLetterboxSurfaceBounds(
+                            key,
+                            startTransaction,
+                            Rect(
+                                change.endRelOffset.x,
+                                change.endRelOffset.y,
+                                change.endAbsBounds.width(),
+                                change.endAbsBounds.height()
+                            )
+                        )
+                    }
+                    letterboxController.updateLetterboxSurfaceVisibility(
+                        key,
+                        startTransaction,
+                        isTopActivityLetterboxed
+                    )
+                }
+                letterboxController.dump()
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 601cf70..307e12e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -20,6 +20,7 @@
 import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT;
 import static android.window.DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.KeyguardManager;
 import android.content.Context;
@@ -62,6 +63,8 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.compatui.letterbox.LetterboxController;
+import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver;
 import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
 import com.android.wm.shell.dagger.pip.PipModule;
 import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
@@ -1229,8 +1232,23 @@
     @Provides
     static Object provideIndependentShellComponentsToCreate(
             DragAndDropController dragAndDropController,
+            @NonNull LetterboxTransitionObserver letterboxTransitionObserver,
             Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional,
             Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler) {
         return new Object();
     }
+
+    //
+    // App Compat
+    //
+
+    @WMSingleton
+    @Provides
+    static LetterboxTransitionObserver provideLetterboxTransitionObserver(
+            @NonNull ShellInit shellInit,
+            @NonNull Transitions transitions,
+            @NonNull LetterboxController letterboxController
+    ) {
+        return new LetterboxTransitionObserver(shellInit, transitions, letterboxController);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
index eb33ff4..35c90ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
@@ -52,6 +52,9 @@
     private final SurfaceControl.Transaction mStartTransaction;
     private final SurfaceControl.Transaction mFinishTransaction;
 
+    private final int mCornerRadius;
+    private final int mShadowRadius;
+
     // Bounds updated by the evaluator as animator is running.
     private final Rect mAnimatedRect = new Rect();
 
@@ -128,6 +131,8 @@
 
         final int enterAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipEnterAnimationDuration);
+        mCornerRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
+        mShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius);
         setDuration(enterAnimationDuration);
         setFloatValues(0f, 1f);
         setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
@@ -177,6 +182,8 @@
         mTransformTensor.postRotate(degrees);
         tx.setMatrix(mLeash, mTransformTensor, mMatrixTmp);
 
+        tx.setCornerRadius(mLeash, mCornerRadius).setShadowRadius(mLeash, mShadowRadius);
+
         if (mContentOverlay != null) {
             mContentOverlay.onAnimationUpdate(tx, 1f / scaleX, fraction, mEndBounds);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
index 4558a9f..06e8349 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
@@ -29,6 +29,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
 import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
 import com.android.wm.shell.shared.animation.Interpolators;
 
@@ -50,6 +51,9 @@
     private Runnable mAnimationEndCallback;
     private RectEvaluator mRectEvaluator;
 
+    private final int mCornerRadius;
+    private final int mShadowRadius;
+
     // Bounds relative to which scaling/cropping must be done.
     private final Rect mBaseBounds = new Rect();
 
@@ -74,7 +78,8 @@
                 mAnimationStartCallback.run();
             }
             if (mStartTx != null) {
-                setBoundsAndRotation(mStartTx, mLeash, mBaseBounds, mStartBounds, mDelta);
+                setBoundsAndRotation(mStartTx, mLeash, mBaseBounds, mStartBounds, mDelta,
+                        mCornerRadius, mShadowRadius);
                 mStartTx.apply();
             }
         }
@@ -83,7 +88,8 @@
         public void onAnimationEnd(Animator animation) {
             super.onAnimationEnd(animation);
             if (mFinishTx != null) {
-                setBoundsAndRotation(mFinishTx, mLeash, mBaseBounds, mEndBounds, 0f);
+                setBoundsAndRotation(mFinishTx, mLeash, mBaseBounds, mEndBounds, 0f,
+                        mCornerRadius, mShadowRadius);
             }
             if (mAnimationEndCallback != null) {
                 mAnimationEndCallback.run();
@@ -99,7 +105,8 @@
                             mSurfaceControlTransactionFactory.getTransaction();
                     final float fraction = getAnimatedFraction();
                     final float degrees = (1.0f - fraction) * mDelta;
-                    setBoundsAndRotation(tx, mLeash, mBaseBounds, mAnimatedRect, degrees);
+                    setBoundsAndRotation(tx, mLeash, mBaseBounds, mAnimatedRect, degrees,
+                            mCornerRadius, mShadowRadius);
                     tx.apply();
                 }
             };
@@ -128,6 +135,9 @@
 
         mRectEvaluator = new RectEvaluator(mAnimatedRect);
 
+        mCornerRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
+        mShadowRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius);
+
         setObjectValues(startBounds, endBounds);
         setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
         addListener(mAnimatorListener);
@@ -152,7 +162,7 @@
      * @param degrees degrees of rotation - counter-clockwise is positive by convention.
      */
     private static void setBoundsAndRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
-            Rect baseBounds, Rect targetBounds, float degrees) {
+            Rect baseBounds, Rect targetBounds, float degrees, int cornerRadius, int shadowRadius) {
         Matrix transformTensor = new Matrix();
         final float[] mMatrixTmp = new float[9];
         final float scaleX = (float) targetBounds.width() / baseBounds.width();
@@ -162,7 +172,9 @@
         transformTensor.postTranslate(targetBounds.left, targetBounds.top);
         transformTensor.postRotate(degrees, targetBounds.centerX(), targetBounds.centerY());
 
-        tx.setMatrix(leash, transformTensor, mMatrixTmp);
+        tx.setMatrix(leash, transformTensor, mMatrixTmp)
+                .setCornerRadius(leash, cornerRadius)
+                .setShadowRadius(leash, shadowRadius);
     }
 
     @VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 3738353..fd387d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -785,8 +785,16 @@
 
     private void handleFlingTransition(SurfaceControl.Transaction startTx,
             SurfaceControl.Transaction finishTx, Rect destinationBounds) {
-        startTx.setPosition(mPipTransitionState.getPinnedTaskLeash(),
-                destinationBounds.left, destinationBounds.top);
+        SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
+        int cornerRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
+        int shadowRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius);
+
+        // merge transactions so everything is done on startTx
+        startTx.merge(finishTx);
+
+        startTx.setPosition(pipLeash, destinationBounds.left, destinationBounds.top)
+                .setCornerRadius(pipLeash, cornerRadius)
+                .setShadowRadius(pipLeash, shadowRadius);
         startTx.apply();
 
         // All motion operations have actually finished, so make bounds cache updates.
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 99f3799..852eee5f 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
@@ -600,13 +600,25 @@
 
     private Rect calculateBoundingRectLocal(@NonNull OccludingCaptionElement element,
             int elementWidthPx, @NonNull Rect captionRect) {
+        final boolean isRtl =
+                mDecorWindowContext.getResources().getConfiguration().getLayoutDirection()
+                        == View.LAYOUT_DIRECTION_RTL;
         switch (element.mAlignment) {
             case START -> {
-                return new Rect(0, 0, elementWidthPx, captionRect.height());
+                if (isRtl) {
+                    return new Rect(captionRect.width() - elementWidthPx, 0,
+                            captionRect.width(), captionRect.height());
+                } else {
+                    return new Rect(0, 0, elementWidthPx, captionRect.height());
+                }
             }
             case END -> {
-                return new Rect(captionRect.width() - elementWidthPx, 0,
-                        captionRect.width(), captionRect.height());
+                if (isRtl) {
+                    return new Rect(0, 0, elementWidthPx, captionRect.height());
+                } else {
+                    return new Rect(captionRect.width() - elementWidthPx, 0,
+                            captionRect.width(), captionRect.height());
+                }
             }
         }
         throw new IllegalArgumentException("Unexpected alignment " + element.mAlignment);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 503ad92..5f25f42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -114,7 +114,7 @@
         // If handle is not in status bar region(i.e., bottom stage in vertical split),
         // do not create an input layer
         if (position.y >= SystemBarUtils.getStatusBarHeight(context)) return
-        if (!isCaptionVisible && statusBarInputLayerExists) {
+        if (!isCaptionVisible) {
             disposeStatusBarInputLayer()
             return
         }
@@ -227,6 +227,7 @@
      * is not visible.
      */
     fun disposeStatusBarInputLayer() {
+        if (!statusBarInputLayerExists) return
         statusBarInputLayerExists = false
         handler.post {
             statusBarInputLayer?.releaseView()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt
new file mode 100644
index 0000000..75025d90
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.annotation.ColorRes
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
+import com.android.internal.R
+import com.android.wm.shell.ShellTestCase
+import java.util.function.Consumer
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+
+/**
+ * Tests for [LetterboxConfiguration].
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:LetterboxConfigurationTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxConfigurationTest : ShellTestCase() {
+
+    companion object {
+        @JvmStatic
+        val COLOR_WHITE = Color.valueOf(Color.WHITE)
+        @JvmStatic
+        val COLOR_RED = Color.valueOf(Color.RED)
+        @JvmStatic
+        val COLOR_BLACK = Color.valueOf(Color.BLACK)
+        @JvmStatic
+        val COLOR_WHITE_RESOURCE_ID = android.R.color.white
+        @JvmStatic
+        val COLOR_BLACK_RESOURCE_ID = android.R.color.black
+    }
+
+    @Test
+    fun `default background color is used if override is not set`() {
+        runTestScenario { r ->
+            r.setDefaultBackgroundColorId(COLOR_WHITE_RESOURCE_ID)
+            r.loadConfiguration()
+            r.checkBackgroundColor(COLOR_WHITE)
+        }
+    }
+
+    @Test
+    fun `overridden background color is used if set`() {
+        runTestScenario { r ->
+            r.setDefaultBackgroundColorId(COLOR_WHITE_RESOURCE_ID)
+            r.loadConfiguration()
+            r.overrideBackgroundColor(COLOR_RED)
+            r.checkBackgroundColor(COLOR_RED)
+        }
+    }
+
+    @Test
+    fun `overridden background color resource is used if set without override`() {
+        runTestScenario { r ->
+            r.setDefaultBackgroundColorId(COLOR_WHITE_RESOURCE_ID)
+            r.loadConfiguration()
+            r.overrideBackgroundColorId(COLOR_BLACK_RESOURCE_ID)
+            r.checkBackgroundColor(COLOR_BLACK)
+        }
+    }
+
+    @Test
+    fun `overridden background color has precedence over color id`() {
+        runTestScenario { r ->
+            r.setDefaultBackgroundColorId(COLOR_WHITE_RESOURCE_ID)
+            r.loadConfiguration()
+            r.overrideBackgroundColor(COLOR_RED)
+            r.overrideBackgroundColorId(COLOR_BLACK_RESOURCE_ID)
+            r.checkBackgroundColor(COLOR_RED)
+        }
+    }
+
+    @Test
+    fun `reset background color`() {
+        runTestScenario { r ->
+            r.setDefaultBackgroundColorId(COLOR_WHITE_RESOURCE_ID)
+            r.loadConfiguration()
+            r.overrideBackgroundColor(COLOR_RED)
+            r.checkBackgroundColor(COLOR_RED)
+
+            r.resetBackgroundColor()
+            r.checkBackgroundColor(COLOR_WHITE)
+
+            r.overrideBackgroundColorId(COLOR_BLACK_RESOURCE_ID)
+            r.checkBackgroundColor(COLOR_BLACK)
+
+            r.resetBackgroundColor()
+            r.checkBackgroundColor(COLOR_WHITE)
+        }
+    }
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    fun runTestScenario(consumer: Consumer<LetterboxConfigurationRobotTest>) {
+        val robot = LetterboxConfigurationRobotTest(mContext)
+        consumer.accept(robot)
+    }
+
+    class LetterboxConfigurationRobotTest(private val ctx: Context) {
+
+        private val resources: Resources
+        private lateinit var letterboxConfig: LetterboxConfiguration
+
+        init {
+            resources = ctx.resources
+            spyOn(resources)
+        }
+
+        fun setDefaultBackgroundColorId(@ColorRes colorId: Int) {
+            doReturn(colorId).`when`(resources)
+                .getColor(R.color.config_letterboxBackgroundColor, null)
+        }
+
+        fun loadConfiguration() {
+            letterboxConfig = LetterboxConfiguration(ctx)
+        }
+
+        fun overrideBackgroundColor(color: Color) {
+            letterboxConfig.setLetterboxBackgroundColor(color)
+        }
+
+        fun resetBackgroundColor() {
+            letterboxConfig.resetLetterboxBackgroundColor()
+        }
+
+        fun overrideBackgroundColorId(@ColorRes colorId: Int) {
+            letterboxConfig.setLetterboxBackgroundColorResourceId(colorId)
+        }
+
+        fun checkBackgroundColor(expected: Color) {
+            val colorComponents = letterboxConfig.getBackgroundColorRgbArray()
+            val expectedComponents = expected.components
+            assert(expectedComponents.contentEquals(colorComponents))
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
new file mode 100644
index 0000000..1ae1c3f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import android.view.WindowManager.TRANSIT_CLOSE
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.util.TransitionObserverInputBuilder
+import com.android.wm.shell.util.executeTransitionObserverTest
+import java.util.function.Consumer
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.anyOrNull
+import org.mockito.verification.VerificationMode
+
+/**
+ * Tests for [LetterboxTransitionObserver].
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:LetterboxTransitionObserverTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxTransitionObserverTest : ShellTestCase() {
+
+    @get:Rule
+    val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
+    @Test
+    @DisableFlags(Flags.FLAG_APP_COMPAT_REFACTORING)
+    fun `when initialized and flag disabled the observer is not registered`() {
+        runTestScenario { r ->
+            executeTransitionObserverTest(observerFactory = r.observerFactory) {
+                r.invokeShellInit()
+                r.checkObservableIsRegistered(expected = false)
+            }
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_APP_COMPAT_REFACTORING)
+    fun `when initialized and flag enabled the observer is registered`() {
+        runTestScenario { r ->
+            executeTransitionObserverTest(observerFactory = r.observerFactory) {
+                r.invokeShellInit()
+                r.checkObservableIsRegistered(expected = true)
+            }
+        }
+    }
+
+    @Test
+    fun `LetterboxController not used without TaskInfos in Change`() {
+        runTestScenario { r ->
+            executeTransitionObserverTest(observerFactory = r.observerFactory) {
+                r.invokeShellInit()
+
+                inputBuilder {
+                    buildTransitionInfo()
+                    addChange(createChange())
+                    addChange(createChange())
+                    addChange(createChange())
+                }
+
+                validateOutput {
+                    r.creationEventDetected(expected = false)
+                    r.visibilityEventDetected(expected = false)
+                    r.destroyEventDetected(expected = false)
+                    r.boundsEventDetected(expected = false)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `When a topActivity is letterboxed surfaces creation is requested`() {
+        runTestScenario { r ->
+            executeTransitionObserverTest(observerFactory = r.observerFactory) {
+                r.invokeShellInit()
+
+                inputBuilder {
+                    buildTransitionInfo()
+                    r.createTopActivityChange(inputBuilder = this, isLetterboxed = true)
+                }
+
+                validateOutput {
+                    r.creationEventDetected(expected = true)
+                    r.visibilityEventDetected(expected = true, visible = true)
+                    r.destroyEventDetected(expected = false)
+                    r.boundsEventDetected(expected = true)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `When a topActivity is not letterboxed visibility is updated`() {
+        runTestScenario { r ->
+            executeTransitionObserverTest(observerFactory = r.observerFactory) {
+                r.invokeShellInit()
+
+                inputBuilder {
+                    buildTransitionInfo()
+                    r.createTopActivityChange(inputBuilder = this, isLetterboxed = false)
+                }
+
+                validateOutput {
+                    r.creationEventDetected(expected = false)
+                    r.visibilityEventDetected(expected = true, visible = false)
+                    r.destroyEventDetected(expected = false)
+                    r.boundsEventDetected(expected = false)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `When closing change letterbox surface destroy is triggered`() {
+        runTestScenario { r ->
+            executeTransitionObserverTest(observerFactory = r.observerFactory) {
+                r.invokeShellInit()
+
+                inputBuilder {
+                    buildTransitionInfo()
+                    r.createClosingChange(inputBuilder = this)
+                }
+
+                validateOutput {
+                    r.destroyEventDetected(expected = true)
+                    r.creationEventDetected(expected = false)
+                    r.visibilityEventDetected(expected = false, visible = false)
+                    r.boundsEventDetected(expected = false)
+                }
+            }
+        }
+    }
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    fun runTestScenario(consumer: Consumer<LetterboxTransitionObserverRobotTest>) {
+        val robot = LetterboxTransitionObserverRobotTest()
+        consumer.accept(robot)
+    }
+
+    class LetterboxTransitionObserverRobotTest {
+
+        companion object {
+            @JvmStatic
+            private val DISPLAY_ID = 1
+
+            @JvmStatic
+            private val TASK_ID = 20
+        }
+
+        private val executor: ShellExecutor
+        private val shellInit: ShellInit
+        private val transitions: Transitions
+        private val letterboxController: LetterboxController
+        private val letterboxObserver: LetterboxTransitionObserver
+
+        val observerFactory: () -> LetterboxTransitionObserver
+
+        init {
+            executor = Mockito.mock(ShellExecutor::class.java)
+            shellInit = ShellInit(executor)
+            transitions = Mockito.mock(Transitions::class.java)
+            letterboxController = Mockito.mock(LetterboxController::class.java)
+            letterboxObserver =
+                LetterboxTransitionObserver(shellInit, transitions, letterboxController)
+            observerFactory = { letterboxObserver }
+        }
+
+        fun invokeShellInit() = shellInit.init()
+
+        fun observer() = letterboxObserver
+
+        fun checkObservableIsRegistered(expected: Boolean) {
+            Mockito.verify(transitions, expected.asMode()).registerObserver(observer())
+        }
+
+        fun creationEventDetected(
+            expected: Boolean,
+            displayId: Int = DISPLAY_ID,
+            taskId: Int = TASK_ID
+        ) {
+            Mockito.verify(letterboxController, expected.asMode()).createLetterboxSurface(
+                toLetterboxKeyMatcher(displayId, taskId),
+                anyOrNull(),
+                anyOrNull()
+            )
+        }
+
+        fun visibilityEventDetected(
+            expected: Boolean,
+            displayId: Int = DISPLAY_ID,
+            taskId: Int = TASK_ID,
+            visible: Boolean? = null
+        ) {
+            Mockito.verify(letterboxController, expected.asMode()).updateLetterboxSurfaceVisibility(
+                toLetterboxKeyMatcher(displayId, taskId),
+                anyOrNull(),
+                visible.asMatcher()
+            )
+        }
+
+        fun destroyEventDetected(
+            expected: Boolean,
+            displayId: Int = DISPLAY_ID,
+            taskId: Int = TASK_ID
+        ) {
+            Mockito.verify(letterboxController, expected.asMode()).destroyLetterboxSurface(
+                toLetterboxKeyMatcher(displayId, taskId),
+                anyOrNull()
+            )
+        }
+
+        fun boundsEventDetected(
+            expected: Boolean,
+            displayId: Int = DISPLAY_ID,
+            taskId: Int = TASK_ID
+        ) {
+            Mockito.verify(letterboxController, expected.asMode()).updateLetterboxSurfaceBounds(
+                toLetterboxKeyMatcher(displayId, taskId),
+                anyOrNull(),
+                anyOrNull()
+            )
+        }
+
+        fun createTopActivityChange(
+            inputBuilder: TransitionObserverInputBuilder,
+            isLetterboxed: Boolean = true,
+            displayId: Int = DISPLAY_ID,
+            taskId: Int = TASK_ID
+        ) {
+            inputBuilder.addChange(changeTaskInfo = inputBuilder.createTaskInfo().apply {
+                appCompatTaskInfo.isTopActivityLetterboxed = isLetterboxed
+                this.taskId = taskId
+                this.displayId = displayId
+            })
+        }
+
+        fun createClosingChange(
+            inputBuilder: TransitionObserverInputBuilder,
+            displayId: Int = DISPLAY_ID,
+            taskId: Int = TASK_ID
+        ) {
+            inputBuilder.addChange(changeTaskInfo = inputBuilder.createTaskInfo().apply {
+                this.taskId = taskId
+                this.displayId = displayId
+            }, changeMode = TRANSIT_CLOSE)
+        }
+
+        private fun Boolean.asMode(): VerificationMode = if (this) times(1) else never()
+
+        private fun Boolean?.asMatcher(): Boolean =
+            if (this != null) eq(this) else any()
+
+        private fun toLetterboxKeyMatcher(displayId: Int, taskId: Int): LetterboxKey {
+            if (displayId < 0 || taskId < 0) {
+                return any()
+            } else {
+                return eq(LetterboxKey(displayId, taskId))
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
index a4008c1..72c4666 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
@@ -39,6 +40,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.wm.shell.R;
 import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip2.phone.PipAppIconOverlay;
 
@@ -49,33 +51,25 @@
 import org.mockito.MockitoAnnotations;
 
 /**
- * Unit test again {@link PipEnterAnimator}.
+ * Unit test against {@link PipEnterAnimator}.
  */
 @SmallTest
 @TestableLooper.RunWithLooper
 @RunWith(AndroidTestingRunner.class)
 public class PipEnterAnimatorTest {
+    private static final float TEST_CORNER_RADIUS = 1f;
+    private static final float TEST_SHADOW_RADIUS = 2f;
 
     @Mock private Context mMockContext;
-
     @Mock private Resources mMockResources;
-
     @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
-
     @Mock private SurfaceControl.Transaction mMockAnimateTransaction;
-
     @Mock private SurfaceControl.Transaction mMockStartTransaction;
-
     @Mock private SurfaceControl.Transaction mMockFinishTransaction;
-
     @Mock private Runnable mMockStartCallback;
-
     @Mock private Runnable mMockEndCallback;
-
     @Mock private PipAppIconOverlay mMockPipAppIconOverlay;
-
     @Mock private SurfaceControl mMockAppIconOverlayLeash;
-
     @Mock private ActivityInfo mMockActivityInfo;
 
     @Surface.Rotation private int mRotation;
@@ -89,13 +83,15 @@
         when(mMockContext.getResources()).thenReturn(mMockResources);
         when(mMockResources.getInteger(anyInt())).thenReturn(0);
         when(mMockFactory.getTransaction()).thenReturn(mMockAnimateTransaction);
-        when(mMockAnimateTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
-                .thenReturn(mMockAnimateTransaction);
-        when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
-                .thenReturn(mMockStartTransaction);
-        when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
-                .thenReturn(mMockFinishTransaction);
         when(mMockPipAppIconOverlay.getLeash()).thenReturn(mMockAppIconOverlayLeash);
+        when(mMockResources.getDimensionPixelSize(R.dimen.pip_corner_radius))
+                .thenReturn((int) TEST_CORNER_RADIUS);
+        when(mMockResources.getDimensionPixelSize(R.dimen.pip_shadow_radius))
+                .thenReturn((int) TEST_SHADOW_RADIUS);
+
+        prepareTransaction(mMockAnimateTransaction);
+        prepareTransaction(mMockStartTransaction);
+        prepareTransaction(mMockFinishTransaction);
 
         mTestLeash = new SurfaceControl.Builder()
                 .setContainerLayer()
@@ -122,6 +118,12 @@
 
         verify(mMockStartCallback).run();
         verifyZeroInteractions(mMockEndCallback);
+
+        // Check corner and shadow radii were set
+        verify(mMockAnimateTransaction, atLeastOnce())
+                .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+        verify(mMockAnimateTransaction, atLeastOnce())
+                .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
     }
 
     @Test
@@ -142,6 +144,12 @@
 
         verify(mMockStartCallback).run();
         verify(mMockEndCallback).run();
+
+        // Check corner and shadow radii were set
+        verify(mMockAnimateTransaction, atLeastOnce())
+                .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+        verify(mMockAnimateTransaction, atLeastOnce())
+                .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
     }
 
     @Test
@@ -197,5 +205,21 @@
 
         verify(mMockPipAppIconOverlay).onAnimationUpdate(
                 eq(mMockAnimateTransaction), anyFloat(), eq(fraction), eq(mEndBounds));
+
+        // Check corner and shadow radii were set
+        verify(mMockAnimateTransaction, atLeastOnce())
+                .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+        verify(mMockAnimateTransaction, atLeastOnce())
+                .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+    }
+
+    // set up transaction chaining
+    private void prepareTransaction(SurfaceControl.Transaction tx) {
+        when(tx.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(tx);
+        when(tx.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+                .thenReturn(tx);
+        when(tx.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+                .thenReturn(tx);
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
index 0adb50b..23fbad0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
@@ -16,15 +16,18 @@
 
 package com.android.wm.shell.pip2.animation;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 import static org.mockito.kotlin.MatchersKt.eq;
-import static org.junit.Assert.assertEquals;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
@@ -34,12 +37,14 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.wm.shell.R;
 import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -52,40 +57,40 @@
 public class PipResizeAnimatorTest {
 
     private static final float FLOAT_COMPARISON_DELTA = 0.001f;
+    private static final float TEST_CORNER_RADIUS = 1f;
+    private static final float TEST_SHADOW_RADIUS = 2f;
 
     @Mock private Context mMockContext;
-
+    @Mock private Resources mMockResources;
     @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
-
     @Mock private SurfaceControl.Transaction mMockTransaction;
-
     @Mock private SurfaceControl.Transaction mMockStartTransaction;
-
     @Mock private SurfaceControl.Transaction mMockFinishTransaction;
-
     @Mock private Runnable mMockStartCallback;
-
     @Mock private Runnable mMockEndCallback;
 
+    @Captor private ArgumentCaptor<Matrix> mArgumentCaptor;
+
     private PipResizeAnimator mPipResizeAnimator;
     private Rect mBaseBounds;
     private Rect mStartBounds;
     private Rect mEndBounds;
     private SurfaceControl mTestLeash;
-    private ArgumentCaptor<Matrix> mArgumentCaptor;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
-        when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
-                .thenReturn(mMockTransaction);
-        when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
-                .thenReturn(mMockStartTransaction);
-        when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
-                .thenReturn(mMockFinishTransaction);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockResources.getDimensionPixelSize(R.dimen.pip_corner_radius))
+                .thenReturn((int) TEST_CORNER_RADIUS);
+        when(mMockResources.getDimensionPixelSize(R.dimen.pip_shadow_radius))
+                .thenReturn((int) TEST_SHADOW_RADIUS);
 
-        mArgumentCaptor = ArgumentCaptor.forClass(Matrix.class);
+        prepareTransaction(mMockTransaction);
+        prepareTransaction(mMockStartTransaction);
+        prepareTransaction(mMockFinishTransaction);
+
         mTestLeash = new SurfaceControl.Builder()
                 .setContainerLayer()
                 .setName("PipResizeAnimatorTest")
@@ -187,6 +192,12 @@
         assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
         assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
         assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+
+        // Check corner and shadow radii were set
+        verify(mMockTransaction, atLeastOnce())
+                .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+        verify(mMockTransaction, atLeastOnce())
+                .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
     }
 
     @Test
@@ -237,6 +248,12 @@
         assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
         assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
         assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+
+        // Check corner and shadow radii were set
+        verify(mMockTransaction, atLeastOnce())
+                .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+        verify(mMockTransaction, atLeastOnce())
+                .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
     }
 
     @Test
@@ -272,5 +289,21 @@
         mArgumentCaptor.getValue().getValues(matrix);
         assertEquals(matrix[Matrix.MSKEW_X], 0f, FLOAT_COMPARISON_DELTA);
         assertEquals(matrix[Matrix.MSKEW_Y], 0f, FLOAT_COMPARISON_DELTA);
+
+        // Check corner and shadow radii were set
+        verify(mMockTransaction, atLeastOnce())
+                .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+        verify(mMockTransaction, atLeastOnce())
+                .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+    }
+
+    // set up transaction chaining
+    private void prepareTransaction(SurfaceControl.Transaction tx) {
+        when(tx.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(tx);
+        when(tx.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+                .thenReturn(tx);
+        when(tx.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+                .thenReturn(tx);
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt
new file mode 100644
index 0000000..3e26ee0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.util
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.content.ComponentName
+import android.content.Intent
+import android.os.IBinder
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.view.WindowManager.TRANSIT_NONE
+import android.view.WindowManager.TransitionFlags
+import android.view.WindowManager.TransitionType
+import android.window.IWindowContainerToken
+import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
+import android.window.TransitionInfo.ChangeFlags
+import android.window.TransitionInfo.FLAG_NONE
+import android.window.TransitionInfo.TransitionMode
+import android.window.WindowContainerToken
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
+import com.android.wm.shell.transition.Transitions.TransitionObserver
+import org.mockito.Mockito
+import org.mockito.kotlin.mock
+
+@DslMarker
+annotation class TransitionObserverTagMarker
+
+/**
+ * Abstraction for all the phases of the [TransitionObserver] test.
+ */
+interface TransitionObserverTestStep
+
+/**
+ * Encapsulates the values for the [TransitionObserver#onTransitionReady] input parameters.
+ */
+class TransitionObserverTransitionReadyInput(
+    val transition: IBinder,
+    val info: TransitionInfo,
+    val startTransaction: Transaction,
+    val finishTransaction: Transaction
+)
+
+@TransitionObserverTagMarker
+class TransitionObserverTestContext : TransitionObserverTestStep {
+
+    lateinit var transitionObserver: TransitionObserver
+    lateinit var transitionReadyInput: TransitionObserverTransitionReadyInput
+
+    fun inputBuilder(builderInput: TransitionObserverInputBuilder.() -> Unit) {
+        val inputFactoryObj = TransitionObserverInputBuilder()
+        inputFactoryObj.builderInput()
+        transitionReadyInput = inputFactoryObj.build()
+    }
+
+    fun validateOutput(
+        validate:
+        TransitionObserverResultValidation.() -> Unit
+    ) {
+        val validateObj = TransitionObserverResultValidation()
+        invokeObservable()
+        validateObj.validate()
+    }
+
+    fun invokeObservable() {
+        transitionObserver.onTransitionReady(
+            transitionReadyInput.transition,
+            transitionReadyInput.info,
+            transitionReadyInput.startTransaction,
+            transitionReadyInput.finishTransaction
+        )
+    }
+}
+
+/**
+ * Phase responsible for the input parameters for [TransitionObserver].
+ */
+class TransitionObserverInputBuilder : TransitionObserverTestStep {
+
+    private val transition = Mockito.mock(IBinder::class.java)
+    private var transitionInfo: TransitionInfo? = null
+    private val startTransaction = Mockito.mock(Transaction::class.java)
+    private val finishTransaction = Mockito.mock(Transaction::class.java)
+
+    fun buildTransitionInfo(
+        @TransitionType type: Int = TRANSIT_NONE,
+        @TransitionFlags flags: Int = 0
+    ) {
+        transitionInfo = TransitionInfo(type, flags)
+        spyOn(transitionInfo)
+    }
+
+    fun addChange(
+        token: WindowContainerToken? = mock(),
+        leash: SurfaceControl = mock(),
+        @TransitionMode changeMode: Int = TRANSIT_NONE,
+        parentToken: WindowContainerToken? = null,
+        changeTaskInfo: RunningTaskInfo? = null,
+        @ChangeFlags changeFlags: Int = FLAG_NONE
+    ) = addChange(Change(token, leash).apply {
+        mode = changeMode
+        parent = parentToken
+        taskInfo = changeTaskInfo
+        flags = changeFlags
+    })
+
+    fun createChange(
+        token: WindowContainerToken? = mock(),
+        leash: SurfaceControl = mock(),
+        @TransitionMode changeMode: Int = TRANSIT_NONE,
+        parentToken: WindowContainerToken? = null,
+        changeTaskInfo: RunningTaskInfo? = null,
+        @ChangeFlags changeFlags: Int = FLAG_NONE
+    ) = Change(token, leash).apply {
+        mode = changeMode
+        parent = parentToken
+        taskInfo = changeTaskInfo
+        flags = changeFlags
+    }
+
+    fun addChange(change: Change) {
+        transitionInfo!!.addChange(change)
+    }
+
+    fun createTaskInfo(id: Int = 0, windowingMode: Int = WINDOWING_MODE_FREEFORM) =
+        RunningTaskInfo().apply {
+            taskId = id
+            displayId = DEFAULT_DISPLAY
+            configuration.windowConfiguration.windowingMode = windowingMode
+            token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java))
+            baseIntent = Intent().apply {
+                component = ComponentName("package", "component.name")
+            }
+        }
+
+    fun build(): TransitionObserverTransitionReadyInput = TransitionObserverTransitionReadyInput(
+        transition = transition,
+        info = transitionInfo!!,
+        startTransaction = startTransaction,
+        finishTransaction = finishTransaction
+    )
+}
+
+/**
+ * Phase responsible for the execution of validation methods.
+ */
+class TransitionObserverResultValidation : TransitionObserverTestStep
+
+/**
+ * Allows to run a test about a specific [TransitionObserver] passing the specific
+ * implementation and input value as parameters for the [TransitionObserver#onTransitionReady]
+ * method.
+ * @param observerFactory    The Factory for the TransitionObserver
+ * @param inputFactory      The Builder for the onTransitionReady input parameters
+ * @param init  The test code itself.
+ */
+fun executeTransitionObserverTest(
+    observerFactory: () -> TransitionObserver,
+    init: TransitionObserverTestContext.() -> Unit
+): TransitionObserverTestContext {
+    val testContext = TransitionObserverTestContext().apply {
+        transitionObserver = observerFactory()
+    }
+    testContext.init()
+    return testContext
+}
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 f7b190c..f653622 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
@@ -220,7 +220,7 @@
     @Captor
     private ArgumentCaptor<Runnable> mCloseMaxMenuRunnable;
 
-    private final InsetsState mInsetsState = new InsetsState();
+    private final InsetsState mInsetsState = createInsetsState(statusBars(), /* visible= */true);
     private SurfaceControl.Transaction mMockTransaction;
     private StaticMockitoSession mMockitoSession;
     private TestableContext mTestableContext;
@@ -1433,8 +1433,6 @@
     public void notifyCaptionStateChanged_flagDisabled_doNoNotify() {
         when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
-        when(mMockDisplayController.getInsetsState(taskInfo.displayId))
-                .thenReturn(createInsetsState(statusBars(), /* visible= */true));
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
 
@@ -1448,8 +1446,6 @@
     public void notifyCaptionStateChanged_inFullscreenMode_notifiesAppHandleVisible() {
         when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
-        when(mMockDisplayController.getInsetsState(taskInfo.displayId))
-                .thenReturn(createInsetsState(statusBars(), /* visible= */true));
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
@@ -1469,8 +1465,6 @@
     public void notifyCaptionStateChanged_inWindowingMode_notifiesAppHeaderVisible() {
         when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
-        when(mMockDisplayController.getInsetsState(taskInfo.displayId))
-                .thenReturn(createInsetsState(statusBars(), /* visible= */true));
         when(mMockAppHeaderViewHolder.getAppChipLocationInWindow()).thenReturn(
                 new Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3));
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -1498,8 +1492,6 @@
     public void notifyCaptionStateChanged_taskNotVisible_notifiesNoCaptionVisible() {
         when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ false);
-        when(mMockDisplayController.getInsetsState(taskInfo.displayId))
-                .thenReturn(createInsetsState(statusBars(), /* visible= */true));
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
         ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
@@ -1518,8 +1510,6 @@
     public void notifyCaptionStateChanged_captionHandleExpanded_notifiesHandleMenuExpanded() {
         when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
-        when(mMockDisplayController.getInsetsState(taskInfo.displayId))
-                .thenReturn(createInsetsState(statusBars(), /* visible= */true));
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
@@ -1543,8 +1533,6 @@
     public void notifyCaptionStateChanged_captionHandleClosed_notifiesHandleMenuClosed() {
         when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
-        when(mMockDisplayController.getInsetsState(taskInfo.displayId))
-                .thenReturn(createInsetsState(statusBars(), /* visible= */true));
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
diff --git a/libs/hwui/jni/RuntimeEffectUtils.cpp b/libs/hwui/jni/RuntimeEffectUtils.cpp
index 46db863..ad0e540 100644
--- a/libs/hwui/jni/RuntimeEffectUtils.cpp
+++ b/libs/hwui/jni/RuntimeEffectUtils.cpp
@@ -90,7 +90,7 @@
                  SkFlattenable* childEffect) {
     SkRuntimeShaderBuilder::BuilderChild builderChild = builder->child(childName);
     if (builderChild.fChild == nullptr) {
-        ThrowIAEFmt(env, "unable to find shader named %s", childName);
+        ThrowIAEFmt(env, "unable to find child named %s", childName);
         return;
     }
 
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 2a057e7..018c2b13 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -2,6 +2,7 @@
 
 #include "Gainmap.h"
 #include "GraphicsJNI.h"
+#include "RuntimeEffectUtils.h"
 #include "SkBitmap.h"
 #include "SkBlendMode.h"
 #include "SkColor.h"
@@ -280,50 +281,6 @@
     return ret;
 }
 
-static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
-    switch (type) {
-        case SkRuntimeEffect::Uniform::Type::kFloat:
-        case SkRuntimeEffect::Uniform::Type::kFloat2:
-        case SkRuntimeEffect::Uniform::Type::kFloat3:
-        case SkRuntimeEffect::Uniform::Type::kFloat4:
-        case SkRuntimeEffect::Uniform::Type::kFloat2x2:
-        case SkRuntimeEffect::Uniform::Type::kFloat3x3:
-        case SkRuntimeEffect::Uniform::Type::kFloat4x4:
-            return false;
-        case SkRuntimeEffect::Uniform::Type::kInt:
-        case SkRuntimeEffect::Uniform::Type::kInt2:
-        case SkRuntimeEffect::Uniform::Type::kInt3:
-        case SkRuntimeEffect::Uniform::Type::kInt4:
-            return true;
-    }
-}
-
-static void UpdateFloatUniforms(JNIEnv* env, SkRuntimeShaderBuilder* builder,
-                                const char* uniformName, const float values[], int count,
-                                bool isColor) {
-    SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(uniformName);
-    if (uniform.fVar == nullptr) {
-        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
-    } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
-        if (isColor) {
-            jniThrowExceptionFmt(
-                    env, "java/lang/IllegalArgumentException",
-                    "attempting to set a color uniform using the non-color specific APIs: %s %x",
-                    uniformName, uniform.fVar->flags);
-        } else {
-            ThrowIAEFmt(env,
-                        "attempting to set a non-color uniform using the setColorUniform APIs: %s",
-                        uniformName);
-        }
-    } else if (isIntUniformType(uniform.fVar->type)) {
-        ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
-                    uniformName);
-    } else if (!uniform.set<float>(values, count)) {
-        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
-                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
-    }
-}
-
 static void RuntimeShader_updateFloatUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
                                               jstring jUniformName, jfloat value1, jfloat value2,
                                               jfloat value3, jfloat value4, jint count) {
@@ -342,20 +299,6 @@
     UpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(), isColor);
 }
 
-static void UpdateIntUniforms(JNIEnv* env, SkRuntimeShaderBuilder* builder, const char* uniformName,
-                              const int values[], int count) {
-    SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(uniformName);
-    if (uniform.fVar == nullptr) {
-        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
-    } else if (!isIntUniformType(uniform.fVar->type)) {
-        ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
-                    uniformName);
-    } else if (!uniform.set<int>(values, count)) {
-        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
-                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
-    }
-}
-
 static void RuntimeShader_updateIntUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
                                             jstring jUniformName, jint value1, jint value2,
                                             jint value3, jint value4, jint count) {
@@ -388,6 +331,15 @@
     builder->child(name.c_str()) = sk_ref_sp(shader);
 }
 
+static void RuntimeShader_updateChild(JNIEnv* env, jobject, jlong shaderBuilder,
+                                      jstring jUniformName, jlong childHandle) {
+    SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
+    ScopedUtfChars name(env, jUniformName);
+    auto* childEffect = reinterpret_cast<SkFlattenable*>(childHandle);
+
+    UpdateChild(env, builder, name.c_str(), childEffect);
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
 static const JNINativeMethod gShaderMethods[] = {
@@ -428,6 +380,7 @@
         {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V",
          (void*)RuntimeShader_updateIntUniforms},
         {"nativeUpdateShader", "(JLjava/lang/String;J)V", (void*)RuntimeShader_updateShader},
+        {"nativeUpdateChild", "(JLjava/lang/String;J)V", (void*)RuntimeShader_updateChild},
 };
 
 int register_android_graphics_Shader(JNIEnv* env)
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/quality/AmbientBacklightEvent.java b/media/java/android/media/quality/AmbientBacklightEvent.java
index 5c11def..273f21e 100644
--- a/media/java/android/media/quality/AmbientBacklightEvent.java
+++ b/media/java/android/media/quality/AmbientBacklightEvent.java
@@ -40,7 +40,7 @@
     @IntDef({AMBIENT_BACKLIGHT_EVENT_ENABLED, AMBIENT_BACKLIGHT_EVENT_DISABLED,
             AMBIENT_BACKLIGHT_EVENT_METADATA,
             AMBIENT_BACKLIGHT_EVENT_INTERRUPTED})
-    public @interface AmbientBacklightEventTypes {}
+    public @interface Type {}
 
     /**
      * Event type for ambient backlight events. The ambient backlight is enabled.
@@ -69,9 +69,9 @@
     private final AmbientBacklightMetadata mMetadata;
 
     /**
-     * Constructor of AmbientBacklightEvent.
+     * Constructs AmbientBacklightEvent.
      */
-    public AmbientBacklightEvent(int eventType,
+    public AmbientBacklightEvent(@Type int eventType,
             @Nullable AmbientBacklightMetadata metadata) {
         mEventType = eventType;
         mMetadata = metadata;
@@ -85,6 +85,7 @@
     /**
      * Gets event type.
      */
+    @Type
     public int getEventType() {
         return mEventType;
     }
diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.java b/media/java/android/media/quality/AmbientBacklightMetadata.java
index 9c11f9a..5cea10d 100644
--- a/media/java/android/media/quality/AmbientBacklightMetadata.java
+++ b/media/java/android/media/quality/AmbientBacklightMetadata.java
@@ -29,6 +29,9 @@
 
 /**
  * Metadata of ambient backlight.
+ *
+ * <p>A metadata instance is sent from ambient backlight hardware in a {@link AmbientBacklightEvent}
+ * with {@link AmbientBacklightEvent#AMBIENT_BACKLIGHT_EVENT_METADATA}.
  * @hide
  */
 @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
@@ -44,10 +47,15 @@
     private final int[] mZonesColors;
 
     /**
-     * Constructor of AmbientBacklightMetadata.
+     * Constructs AmbientBacklightMetadata.
      */
-    public AmbientBacklightMetadata(@NonNull String packageName, int compressAlgorithm,
-            int source, int colorFormat, int horizontalZonesNumber, int verticalZonesNumber,
+    public AmbientBacklightMetadata(
+            @NonNull String packageName,
+            @AmbientBacklightSettings.CompressAlgorithm int compressAlgorithm,
+            @AmbientBacklightSettings.Source int source,
+            @PixelFormat.Format int colorFormat,
+            int horizontalZonesNumber,
+            int verticalZonesNumber,
             @NonNull int[] zonesColors) {
         mPackageName = packageName;
         mCompressAlgorithm = compressAlgorithm;
@@ -69,7 +77,7 @@
     }
 
     /**
-     * Gets package name.
+     * Gets package name of the metadata.
      * @hide
      */
     @NonNull
@@ -102,7 +110,9 @@
     }
 
     /**
-     * Gets the number of lights in each horizontal zone.
+     * Gets the number of horizontal color zones.
+     *
+     * <p>A color zone is a group of lights that always display the same color.
      */
     @IntRange(from = 0)
     public int getHorizontalZonesNumber() {
@@ -110,7 +120,9 @@
     }
 
     /**
-     * Gets the number of lights in each vertical zone.
+     * Gets the number of vertical color zones.
+     *
+     * <p>A color zone is a group of lights that always display the same color.
      */
     @IntRange(from = 0)
     public int getVerticalZonesNumber() {
@@ -118,10 +130,11 @@
     }
 
     /**
+     * Gets color data of vertical color zones.
      * @hide
      */
     @NonNull
-    public int[] getZonesColors() {
+    public int[] getVerticalZonesColors() {
         return mZonesColors;
     }
 
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.java b/media/java/android/media/quality/AmbientBacklightSettings.java
index 4ed7bc7..d904cf7 100644
--- a/media/java/android/media/quality/AmbientBacklightSettings.java
+++ b/media/java/android/media/quality/AmbientBacklightSettings.java
@@ -30,7 +30,7 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Settings for ambient backlight.
+ * Settings to configure ambient backlight hardware.
  * @hide
  */
 @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
@@ -124,8 +124,13 @@
     /**
      * Constructs AmbientBacklightSettings.
      */
-    public AmbientBacklightSettings(int source, int maxFps, int colorFormat,
-            int horizontalZonesNumber, int verticalZonesNumber, boolean isLetterboxOmitted,
+    public AmbientBacklightSettings(
+            @Source int source,
+            int maxFps,
+            @PixelFormat.Format int colorFormat,
+            int horizontalZonesNumber,
+            int verticalZonesNumber,
+            boolean isLetterboxOmitted,
             int threshold) {
         mSource = source;
         mMaxFps = maxFps;
@@ -171,7 +176,9 @@
     }
 
     /**
-     * Gets the number of lights in each horizontal zone.
+     * Gets the number of horizontal color zones.
+     *
+     * <p>A color zone is a group of lights that always display the same color.
      */
     @IntRange(from = 0)
     public int getHorizontalZonesNumber() {
@@ -179,7 +186,9 @@
     }
 
     /**
-     * Gets the number of lights in each vertical zone.
+     * Gets the number of vertical color zones.
+     *
+     * <p>A color zone is a group of lights that always display the same color.
      */
     @IntRange(from = 0)
     public int getVerticalZonesNumber() {
@@ -187,7 +196,11 @@
     }
 
     /**
-     * Returns {@code true} if letter box is omitted; {@code false} otherwise.
+     * Returns {@code true} if the black portion of the screen in letter box mode is omitted;
+     * {@code false} otherwise.
+     *
+     * <p>Letter-box is a technique to keep the original aspect ratio when displayed on a screen
+     * with different aspect ratio. Black bars are added to the top and bottom.
      * @hide
      */
     public boolean isLetterboxOmitted() {
@@ -195,6 +208,10 @@
     }
 
     /**
+     * Gets the detection threshold of the ambient light.
+     *
+     * <p>If the color of a color zone is changed by the difference is smaller than the threshold,
+     * the change is ignored.
      * @hide
      */
     public int getThreshold() {
diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl
index 250d59b..aaedf21 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -42,10 +42,12 @@
     SoundProfile createSoundProfile(in SoundProfile pp);
     void updateSoundProfile(in String id, in SoundProfile pp);
     void removeSoundProfile(in String id);
-    SoundProfile getSoundProfileById(in String id);
+    SoundProfile getSoundProfile(in int type, in String name);
     List<SoundProfile> getSoundProfilesByPackage(in String packageName);
     List<SoundProfile> getAvailableSoundProfiles();
     List<String> getSoundProfilePackageNames();
+    List<String> getSoundProfileAllowList();
+    void setSoundProfileAllowList(in List<String> packages);
 
     void registerPictureProfileCallback(in IPictureProfileCallback cb);
     void registerSoundProfileCallback(in ISoundProfileCallback cb);
diff --git a/media/java/android/media/quality/ISoundProfileCallback.aidl b/media/java/android/media/quality/ISoundProfileCallback.aidl
index 72d1524..9043757 100644
--- a/media/java/android/media/quality/ISoundProfileCallback.aidl
+++ b/media/java/android/media/quality/ISoundProfileCallback.aidl
@@ -17,6 +17,7 @@
 
 package android.media.quality;
 
+import android.media.quality.ParamCapability;
 import android.media.quality.SoundProfile;
 
 /**
@@ -24,7 +25,9 @@
  * @hide
  */
 oneway interface ISoundProfileCallback {
-    void onSoundProfileAdded(in long id, in SoundProfile p);
-    void onSoundProfileUpdated(in long id, in SoundProfile p);
-    void onSoundProfileRemoved(in long id, in SoundProfile p);
+    void onSoundProfileAdded(in String id, in SoundProfile p);
+    void onSoundProfileUpdated(in String id, in SoundProfile p);
+    void onSoundProfileRemoved(in String id, in SoundProfile p);
+    void onParamCapabilitiesChanged(in String id, in List<ParamCapability> caps);
+    void onError(in int err);
 }
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 4d4526c..dcf4971 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -111,7 +111,7 @@
         };
         ISoundProfileCallback spCallback = new ISoundProfileCallback.Stub() {
             @Override
-            public void onSoundProfileAdded(long profileId, SoundProfile profile) {
+            public void onSoundProfileAdded(String profileId, SoundProfile profile) {
                 synchronized (mLock) {
                     for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
                         // TODO: filter callback record
@@ -120,7 +120,7 @@
                 }
             }
             @Override
-            public void onSoundProfileUpdated(long profileId, SoundProfile profile) {
+            public void onSoundProfileUpdated(String profileId, SoundProfile profile) {
                 synchronized (mLock) {
                     for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
                         // TODO: filter callback record
@@ -129,7 +129,7 @@
                 }
             }
             @Override
-            public void onSoundProfileRemoved(long profileId, SoundProfile profile) {
+            public void onSoundProfileRemoved(String profileId, SoundProfile profile) {
                 synchronized (mLock) {
                     for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
                         // TODO: filter callback record
@@ -137,6 +137,24 @@
                     }
                 }
             }
+            @Override
+            public void onParamCapabilitiesChanged(String profileId, List<ParamCapability> caps) {
+                synchronized (mLock) {
+                    for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postParamCapabilitiesChanged(profileId, caps);
+                    }
+                }
+            }
+            @Override
+            public void onError(int err) {
+                synchronized (mLock) {
+                    for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postError(err);
+                    }
+                }
+            }
         };
         IAmbientBacklightCallback abCallback = new IAmbientBacklightCallback.Stub() {
             @Override
@@ -331,14 +349,17 @@
 
 
     /**
-     * Gets sound profile by given profile ID.
-     * @return the corresponding sound profile if available; {@code null} if the ID doesn't
-     *         exist or the profile is not accessible to the caller.
+     * Gets sound profile by given profile type and name.
+     *
+     * @return the corresponding sound profile if available; {@code null} if the name doesn't
+     *         exist.
      * @hide
      */
-    public SoundProfile getSoundProfileById(String profileId) {
+    @Nullable
+    public SoundProfile getSoundProfile(
+            @SoundProfile.ProfileType int type, @NonNull String name) {
         try {
-            return mService.getSoundProfileById(profileId);
+            return mService.getSoundProfile(type, name);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -349,8 +370,9 @@
      * @SystemApi gets profiles that available to the given package
      * @hide
      */
+    @NonNull
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
-    public List<SoundProfile> getSoundProfilesByPackage(String packageName) {
+    public List<SoundProfile> getSoundProfilesByPackage(@NonNull String packageName) {
         try {
             return mService.getSoundProfilesByPackage(packageName);
         } catch (RemoteException e) {
@@ -362,6 +384,7 @@
      * Gets profiles that available to the caller package
      * @hide
      */
+    @NonNull
     public List<SoundProfile> getAvailableSoundProfiles() {
         try {
             return mService.getAvailableSoundProfiles();
@@ -371,9 +394,12 @@
     }
 
     /**
-     * @SystemApi all stored sound profiles of all packages
+     * @SystemApi Gets all package names whose sound profiles are available.
+     *
+     * @see #getSoundProfilesByPackage(String)
      * @hide
      */
+    @NonNull
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
     public List<String> getSoundProfilePackageNames() {
         try {
@@ -387,12 +413,13 @@
     /**
      * Creates a sound profile and store it in the system.
      *
-     * @return the stored profile with an assigned profile ID.
+     * <p>If the profile is created successfully,
+     * {@link SoundProfileCallback#onSoundProfileAdded(long, SoundProfile)} is invoked.
      * @hide
      */
-    public SoundProfile createSoundProfile(SoundProfile sp) {
+    public void createSoundProfile(@NonNull SoundProfile sp) {
         try {
-            return mService.createSoundProfile(sp);
+            mService.createSoundProfile(sp);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -403,7 +430,7 @@
      * Updates an existing sound profile and store it in the system.
      * @hide
      */
-    public void updateSoundProfile(String profileId, SoundProfile sp) {
+    public void updateSoundProfile(@NonNull String profileId, @NonNull SoundProfile sp) {
         try {
             mService.updateSoundProfile(profileId, sp);
         } catch (RemoteException e) {
@@ -416,7 +443,7 @@
      * Removes a sound profile from the system.
      * @hide
      */
-    public void removeSoundProfile(String profileId) {
+    public void removeSoundProfile(@NonNull String profileId) {
         try {
             mService.removeSoundProfile(profileId);
         } catch (RemoteException e) {
@@ -468,6 +495,36 @@
     }
 
     /**
+     * Gets the allowlist of packages that can create and removed sound profiles
+     *
+     * @see #createSoundProfile(SoundProfile)
+     * @see #removeSoundProfile(String)
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+    @NonNull
+    public List<String> getSoundProfileAllowList() {
+        try {
+            return mService.getSoundProfileAllowList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets the allowlist of packages that can create and removed sound profiles
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+    public void setSoundProfileAllowList(@NonNull List<String> packageNames) {
+        try {
+            mService.setSoundProfileAllowList(packageNames);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns {@code true} if media quality HAL is implemented; {@code false} otherwise.
      * @hide
      */
@@ -567,6 +624,7 @@
 
     /**
      * Registers a {@link AmbientBacklightCallback}.
+     * @hide
      */
     public void registerAmbientBacklightCallback(
             @NonNull @CallbackExecutor Executor executor,
@@ -580,6 +638,7 @@
 
     /**
      * Unregisters the existing {@link AmbientBacklightCallback}.
+     * @hide
      */
     public void unregisterAmbientBacklightCallback(
             @NonNull final AmbientBacklightCallback callback) {
@@ -600,6 +659,7 @@
      * Set the ambient backlight settings.
      *
      * @param settings The settings to use for the backlight detector.
+     * @hide
      */
     public void setAmbientBacklightSettings(
             @NonNull AmbientBacklightSettings settings) {
@@ -615,6 +675,7 @@
      * Enables or disables the ambient backlight detection.
      *
      * @param enabled {@code true} to enable, {@code false} to disable.
+     * @hide
      */
     public void setAmbientBacklightEnabled(boolean enabled) {
         try {
@@ -698,7 +759,7 @@
             return mCallback;
         }
 
-        public void postSoundProfileAdded(final long id, SoundProfile profile) {
+        public void postSoundProfileAdded(final String id, SoundProfile profile) {
 
             mExecutor.execute(new Runnable() {
                 @Override
@@ -708,7 +769,7 @@
             });
         }
 
-        public void postSoundProfileUpdated(final long id, SoundProfile profile) {
+        public void postSoundProfileUpdated(final String id, SoundProfile profile) {
             mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
@@ -717,7 +778,7 @@
             });
         }
 
-        public void postSoundProfileRemoved(final long id, SoundProfile profile) {
+        public void postSoundProfileRemoved(final String id, SoundProfile profile) {
             mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
@@ -725,6 +786,24 @@
                 }
             });
         }
+
+        public void postParamCapabilitiesChanged(final String id, List<ParamCapability> caps) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onParamCapabilitiesChanged(id, caps);
+                }
+            });
+        }
+
+        public void postError(int error) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onError(error);
+                }
+            });
+        }
     }
 
     private static final class AmbientBacklightCallbackRecord {
@@ -801,12 +880,13 @@
          * This is invoked when parameter capabilities has been changed due to status changes of the
          * content.
          *
-         * @param profileId the ID of the profile used by the media content.
+         * @param profileId the ID of the profile used by the media content. {@code null} if there
+         *                  is no associated profile
          * @param updatedCaps the updated capabilities.
          * @hide
          */
         public void onParamCapabilitiesChanged(
-                @NonNull String profileId, @NonNull List<ParamCapability> updatedCaps) {
+                @Nullable String profileId, @NonNull List<ParamCapability> updatedCaps) {
         }
     }
 
@@ -816,29 +896,64 @@
      */
     public abstract static class SoundProfileCallback {
         /**
+         * This is invoked when a sound profile has been added.
+         *
+         * @param profileId the ID of the profile.
+         * @param profile the newly added profile.
          * @hide
          */
-        public void onSoundProfileAdded(long id, SoundProfile profile) {
+        public void onSoundProfileAdded(
+                @NonNull String profileId, @NonNull SoundProfile profile) {
         }
+
         /**
+         * This is invoked when a sound profile has been updated.
+         *
+         * @param profileId the ID of the profile.
+         * @param profile the profile with updated info.
          * @hide
          */
-        public void onSoundProfileUpdated(long id, SoundProfile profile) {
+        public void onSoundProfileUpdated(
+                @NonNull String profileId, @NonNull SoundProfile profile) {
         }
+
         /**
+         * This is invoked when a sound profile has been removed.
+         *
+         * @param profileId the ID of the profile.
+         * @param profile the removed profile.
          * @hide
          */
-        public void onSoundProfileRemoved(long id, SoundProfile profile) {
+        public void onSoundProfileRemoved(
+                @NonNull String profileId, @NonNull SoundProfile profile) {
         }
+
         /**
+         * This is invoked when an issue has occurred.
+         *
+         * @param errorCode the error code
          * @hide
          */
-        public void onError(int errorCode) {
+        public void onError(@SoundProfile.ErrorCode int errorCode) {
+        }
+
+        /**
+         * This is invoked when parameter capabilities has been changed due to status changes of the
+         * content.
+         *
+         * @param profileId the ID of the profile used by the media content. {@code null} if there
+         *                  is no associated profile
+         * @param updatedCaps the updated capabilities.
+         * @hide
+         */
+        public void onParamCapabilitiesChanged(
+                @Nullable String profileId, @NonNull List<ParamCapability> updatedCaps) {
         }
     }
 
     /**
      * Callback used to monitor status of ambient backlight.
+     * @hide
      */
     public abstract static class AmbientBacklightCallback {
         /**
diff --git a/media/java/android/media/quality/SoundProfile.java b/media/java/android/media/quality/SoundProfile.java
index 20d117b..de93afe 100644
--- a/media/java/android/media/quality/SoundProfile.java
+++ b/media/java/android/media/quality/SoundProfile.java
@@ -17,54 +17,119 @@
 package android.media.quality;
 
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.media.tv.TvInputInfo;
 import android.media.tv.flags.Flags;
-import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresPermission;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
+ * Profile for sound quality.
  * @hide
  */
 @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
 public class SoundProfile implements Parcelable {
     @Nullable
-    private Long mId;
+    private String mId;
+    private final int mType;
     @NonNull
     private final String mName;
     @Nullable
     private final String mInputId;
-    @Nullable
+    @NonNull
     private final String mPackageName;
     @NonNull
-    private final Bundle mParams;
+    private final PersistableBundle mParams;
 
-    protected SoundProfile(Parcel in) {
-        if (in.readByte() == 0) {
-            mId = null;
-        } else {
-            mId = in.readLong();
-        }
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = false, prefix = "TYPE_", value = {
+            TYPE_SYSTEM,
+            TYPE_APPLICATION})
+    public @interface ProfileType {}
+
+    /**
+     * System profile type.
+     *
+     * <p>A profile of system type is managed by the system, and readable to the package returned by
+     * {@link #getPackageName()}.
+     */
+    public static final int TYPE_SYSTEM = 1;
+    /**
+     * Application profile type.
+     *
+     * <p>A profile of application type is managed by the package returned by
+     * {@link #getPackageName()}.
+     */
+    public static final int TYPE_APPLICATION = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = false, prefix = "ERROR_", value = {
+            ERROR_UNKNOWN,
+            ERROR_NO_PERMISSION,
+            ERROR_DUPLICATE,
+            ERROR_INVALID_ARGUMENT,
+            ERROR_NOT_ALLOWLISTED
+    })
+    public @interface ErrorCode {}
+
+    /**
+     * Error code for unknown errors.
+     */
+    public static final int ERROR_UNKNOWN = 0;
+
+    /**
+     * Error code for missing necessary permission to handle the profiles.
+     */
+    public static final int ERROR_NO_PERMISSION = 1;
+
+    /**
+     * Error code for creating a profile with existing profile type and name.
+     *
+     * @see #getProfileType()
+     * @see #getName()
+     */
+    public static final int ERROR_DUPLICATE = 2;
+
+    /**
+     * Error code for invalid argument.
+     */
+    public static final int ERROR_INVALID_ARGUMENT = 3;
+
+    /**
+     * Error code for the case when an operation requires an allowlist but the caller is not in the
+     * list.
+     *
+     * @see MediaQualityManager#getSoundProfileAllowList()
+     */
+    public static final int ERROR_NOT_ALLOWLISTED = 4;
+
+    protected SoundProfile(@NonNull Parcel in) {
+        mId = in.readString();
+        mType = in.readInt();
         mName = in.readString();
         mInputId = in.readString();
         mPackageName = in.readString();
-        mParams = in.readBundle();
+        mParams = in.readPersistableBundle();
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        if (mId == null) {
-            dest.writeByte((byte) 0);
-        } else {
-            dest.writeByte((byte) 1);
-            dest.writeLong(mId);
-        }
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mId);
+        dest.writeInt(mType);
         dest.writeString(mName);
         dest.writeString(mInputId);
         dest.writeString(mPackageName);
-        dest.writeBundle(mParams);
+        dest.writePersistableBundle(mParams);
     }
 
     @Override
@@ -72,6 +137,7 @@
         return 0;
     }
 
+    @NonNull
     public static final Creator<SoundProfile> CREATOR = new Creator<SoundProfile>() {
         @Override
         public SoundProfile createFromParcel(Parcel in) {
@@ -91,93 +157,164 @@
      * @hide
      */
     public SoundProfile(
-            @Nullable Long id,
+            @Nullable String id,
+            int type,
             @NonNull String name,
             @Nullable String inputId,
-            @Nullable String packageName,
-            @NonNull Bundle params) {
+            @NonNull String packageName,
+            @NonNull PersistableBundle params) {
         this.mId = id;
+        this.mType = type;
         this.mName = name;
-        com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name);
         this.mInputId = inputId;
         this.mPackageName = packageName;
         this.mParams = params;
     }
 
+    /**
+     * Gets profile ID.
+     *
+     * <p>A profile ID is a globally unique ID generated and assigned by the system. For profile
+     * objects retrieved from system (e.g {@link MediaQualityManager#getAvailableSoundProfiles()})
+     * this profile ID is non-null; For profiles built locally with {@link Builder}, it's
+     * {@code null}.
+     *
+     * @return the unique profile ID; {@code null} if the profile is built locally with
+     * {@link Builder}.
+     */
     @Nullable
-    public Long getProfileId() {
+    public String getProfileId() {
         return mId;
     }
 
+    /**
+     * Only used by system to assign the ID.
+     * @hide
+     */
+    public void setProfileId(String id) {
+        mId = id;
+    }
+
+    /**
+     * Gets profile type.
+     */
+    @ProfileType
+    public int getProfileType() {
+        return mType;
+    }
+
+    /**
+     * Gets the profile name.
+     */
     @NonNull
     public String getName() {
         return mName;
     }
 
+    /**
+     * Gets the input ID if the profile is for a TV input.
+     *
+     * @return the corresponding TV input ID; {@code null} if the profile is not associated with a
+     * TV input.
+     *
+     * @see TvInputInfo#getId()
+     */
     @Nullable
     public String getInputId() {
         return mInputId;
     }
 
+    /**
+     * Gets the package name of this profile.
+     *
+     * <p>The package name defines the user of a profile. Only this specific package and system app
+     * can access to this profile.
+     *
+     * @return the package name; {@code null} if the profile is built locally using
+     * {@link Builder} and the package is not set.
+     */
     @Nullable
     public String getPackageName() {
         return mPackageName;
     }
+
+    /**
+     * Gets the parameters of this profile.
+     *
+     * <p>The keys of commonly used parameters can be found in
+     * {@link MediaQualityContract.SoundQuality}.
+     */
     @NonNull
-    public Bundle getParameters() {
-        return new Bundle(mParams);
+    public PersistableBundle getParameters() {
+        return new PersistableBundle(mParams);
     }
 
     /**
      * A builder for {@link SoundProfile}
+     * @hide
      */
     public static class Builder {
         @Nullable
-        private Long mId;
+        private String mId;
+        private int mType = TYPE_APPLICATION;
         @NonNull
         private String mName;
         @Nullable
         private String mInputId;
-        @Nullable
+        @NonNull
         private String mPackageName;
         @NonNull
-        private Bundle mParams;
+        private PersistableBundle mParams;
 
         /**
          * Creates a new Builder.
-         *
-         * @hide
          */
         public Builder(@NonNull String name) {
             mName = name;
-            com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name);
         }
 
         /**
-         * Copy constructor.
-         *
-         * @hide
+         * Copy constructor of builder.
          */
         public Builder(@NonNull SoundProfile p) {
             mId = null; // ID needs to be reset
+            mType = p.getProfileType();
             mName = p.getName();
             mPackageName = p.getPackageName();
             mInputId = p.getInputId();
+            mParams = p.getParameters();
         }
 
         /**
-         * Sets profile ID.
-         * @hide using by MediaQualityService
+         * Only used by system to assign the ID.
+         * @hide
          */
         @NonNull
-        public Builder setProfileId(@Nullable Long id) {
+        public Builder setProfileId(@Nullable String id) {
             mId = id;
             return this;
         }
 
         /**
-         * Sets input ID.
+         * Sets profile type.
+         *
+         * @hide
          */
+        @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+        @NonNull
+        public Builder setProfileType(@ProfileType int value) {
+            mType = value;
+            return this;
+        }
+
+        /**
+         * Sets input ID.
+         *
+         * @see SoundProfile#getInputId()
+         *
+         * @hide
+         */
+        @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
         @NonNull
         public Builder setInputId(@NonNull String value) {
             mInputId = value;
@@ -186,7 +323,12 @@
 
         /**
          * Sets package name of the profile.
+         *
+         * @see SoundProfile#getPackageName()
+         *
+         * @hide
          */
+        @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
         @NonNull
         public Builder setPackageName(@NonNull String value) {
             mPackageName = value;
@@ -195,12 +337,15 @@
 
         /**
          * Sets profile parameters.
+         *
+         * @see SoundProfile#getParameters()
          */
         @NonNull
-        public Builder setParameters(@NonNull Bundle params) {
-            mParams = new Bundle(params);
+        public Builder setParameters(@NonNull PersistableBundle params) {
+            mParams = new PersistableBundle(params);
             return this;
         }
+
         /**
          * Builds the instance.
          */
@@ -209,6 +354,7 @@
 
             SoundProfile o = new SoundProfile(
                     mId,
+                    mType,
                     mName,
                     mInputId,
                     mPackageName,
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 23dd9b7..4180710 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -45,8 +45,10 @@
 
     header_libs: [
         "jni_headers",
+        "native_headers",
         "libhwui_internal_headers",
     ],
+    export_header_lib_headers: ["native_headers"],
 
     static_libs: [
         "libarect",
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 79a0607..f587660 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -57,6 +57,7 @@
 
   @FlaggedApi("android.nfc.nfc_oem_extension") public final class NfcOemExtension {
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearPreference();
+    method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int forceRoutingTableCommit();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.List<java.lang.String> getActiveNfceeList();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.nfc.RoutingStatus getRoutingStatus();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.List<android.nfc.NfcRoutingTableEntry> getRoutingTable();
@@ -73,6 +74,9 @@
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void triggerInitialization();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterCallback(@NonNull android.nfc.NfcOemExtension.Callback);
+    field public static final int COMMIT_ROUTING_STATUS_FAILED = 3; // 0x3
+    field public static final int COMMIT_ROUTING_STATUS_FAILED_UPDATE_IN_PROGRESS = 6; // 0x6
+    field public static final int COMMIT_ROUTING_STATUS_OK = 0; // 0x0
     field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int DISABLE = 0; // 0x0
     field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_DEFAULT = 1; // 0x1
     field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_EE = 3; // 0x3
@@ -96,6 +100,7 @@
     method public void onEnableFinished(int);
     method public void onEnableRequested(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onEnableStarted();
+    method public void onExtractOemPackages(@NonNull android.nfc.NdefMessage, @NonNull java.util.function.Consumer<java.util.List<java.lang.String>>);
     method public void onGetOemAppSearchIntent(@NonNull java.util.List<java.lang.String>, @NonNull java.util.function.Consumer<android.content.Intent>);
     method public void onHceEventReceived(int);
     method public void onLaunchHceAppChooserActivity(@NonNull String, @NonNull java.util.List<android.nfc.cardemulation.ApduServiceInfo>, @NonNull android.content.ComponentName, @NonNull String);
@@ -106,7 +111,7 @@
     method public void onReaderOptionChanged(boolean);
     method public void onRfDiscoveryStarted(boolean);
     method public void onRfFieldActivated(boolean);
-    method public void onRoutingChanged();
+    method public void onRoutingChanged(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onRoutingTableFull();
     method public void onStateUpdated(int);
     method public void onTagConnected(boolean);
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 40fd068..31514a0 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -120,4 +120,5 @@
     boolean isTagPresent();
     List<Entry> getRoutingTableEntryList();
     void indicateDataMigration(boolean inProgress, String pkg);
+    int commitRouting();
 }
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index fb793b0..1a21c0b 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -41,7 +41,7 @@
    void onEnableFinished(int status);
    void onDisableFinished(int status);
    void onTagDispatch(in ResultReceiver isSkipped);
-   void onRoutingChanged();
+   void onRoutingChanged(in ResultReceiver isSkipped);
    void onHceEventReceived(int action);
    void onReaderOptionChanged(boolean enabled);
    void onCardEmulationActivated(boolean isActivated);
@@ -54,4 +54,5 @@
    void onLaunchHceTapAgainActivity(in ApduServiceInfo service, in String category);
    void onRoutingTableFull();
    void onLogEventNotified(in OemLogItems item);
+   void onExtractOemPackages(in NdefMessage message, in ResultReceiver packageReceiver);
 }
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index fd131b8..326ca64 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -194,6 +194,30 @@
     public @interface StatusCode {}
 
     /**
+     * Routing commit succeeded.
+     */
+    public static final int COMMIT_ROUTING_STATUS_OK = 0;
+    /**
+     * Routing commit failed.
+     */
+    public static final int COMMIT_ROUTING_STATUS_FAILED = 3;
+    /**
+     * Routing commit failed due to the update is in progress.
+     */
+    public static final int COMMIT_ROUTING_STATUS_FAILED_UPDATE_IN_PROGRESS = 6;
+
+    /**
+     * Status codes returned when calling {@link #forceRoutingTableCommit()}
+     * @hide
+     */
+    @IntDef(prefix = "COMMIT_ROUTING_STATUS_", value = {
+            COMMIT_ROUTING_STATUS_OK,
+            COMMIT_ROUTING_STATUS_FAILED,
+            COMMIT_ROUTING_STATUS_FAILED_UPDATE_IN_PROGRESS,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CommitRoutingStatusCode {}
+    /**
      * Interface for Oem extensions for NFC.
      */
     public interface Callback {
@@ -286,8 +310,12 @@
 
         /**
          * Notifies routing configuration is changed.
+         * @param isCommitRoutingSkipped The {@link Consumer} to be
+         * completed. If routing commit should be skipped,
+         * the {@link Consumer#accept(Object)} should be called with
+         * {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}.
          */
-        void onRoutingChanged();
+        void onRoutingChanged(@NonNull Consumer<Boolean> isCommitRoutingSkipped);
 
         /**
          * API to activate start stop cpu boost on hce event.
@@ -404,6 +432,19 @@
          * @param item the log items that contains log information of NFC event.
          */
         void onLogEventNotified(@NonNull OemLogItems item);
+
+        /**
+         * Callback to to extract OEM defined packages from given NDEF message when
+         * a NFC tag is detected. These are used to handle NFC tags encoded with a
+         * proprietary format for storing app name (Android native app format).
+         *
+         * @param message NDEF message containing OEM package names
+         * @param packageConsumer The {@link Consumer} to be completed.
+         *                        The {@link Consumer#accept(Object)} should be called with
+         *                        the list of package names.
+         */
+        void onExtractOemPackages(@NonNull NdefMessage message,
+                @NonNull Consumer<List<String>> packageConsumer);
     }
 
 
@@ -740,6 +781,18 @@
         return result;
     }
 
+    /**
+     * API to force a routing table commit.
+     * @return a {@link StatusCode} to indicate if commit routing succeeded or not
+     */
+    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+    @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+    @CommitRoutingStatusCode
+    public int forceRoutingTableCommit() {
+        return NfcAdapter.callServiceReturn(
+                () -> NfcAdapter.sService.commitRouting(), COMMIT_ROUTING_STATUS_FAILED);
+    }
+
     private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
 
         @Override
@@ -843,9 +896,10 @@
                         new ReceiverWrapper<>(isSkipped), cb::onTagDispatch, ex));
         }
         @Override
-        public void onRoutingChanged() throws RemoteException {
+        public void onRoutingChanged(ResultReceiver isSkipped) throws RemoteException {
             mCallbackMap.forEach((cb, ex) ->
-                    handleVoidCallback(null, (Object input) -> cb.onRoutingChanged(), ex));
+                    handleVoidCallback(
+                            new ReceiverWrapper<>(isSkipped), cb::onRoutingChanged, ex));
         }
         @Override
         public void onHceEventReceived(int action) throws RemoteException {
@@ -924,6 +978,15 @@
                     handleVoidCallback(item, cb::onLogEventNotified, ex));
         }
 
+        @Override
+        public void onExtractOemPackages(NdefMessage message, ResultReceiver packageConsumer)
+                throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoid2ArgCallback(message,
+                            new ReceiverWrapper<>(packageConsumer),
+                            cb::onExtractOemPackages, ex));
+        }
+
         private <T> void handleVoidCallback(
                 T input, Consumer<T> callbackMethod, Executor executor) {
             synchronized (mLock) {
@@ -1034,8 +1097,14 @@
                 Bundle bundle = new Bundle();
                 bundle.putParcelable("intent", (Intent) result);
                 mResultReceiver.send(0, bundle);
+            } else if (result instanceof List<?> list) {
+                if (list.stream().allMatch(String.class::isInstance)) {
+                    Bundle bundle = new Bundle();
+                    bundle.putStringArray("packageNames",
+                            list.stream().map(pkg -> (String) pkg).toArray(String[]::new));
+                    mResultReceiver.send(0, bundle);
+                }
             }
-
         }
 
         @Override
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/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
index 163f4b3..78e6056 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
@@ -307,6 +307,6 @@
 
 private object DraggableBottomSheet {
     val DefaultTopPadding = 64.dp
-    val LargeScreenTopPadding = 72.dp
+    val LargeScreenTopPadding = 56.dp
     val MaxWidth = 640.dp
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 44f60cb..eb2a016 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -350,8 +350,7 @@
             val placeInThisContent =
                 elementContentWhenIdle(
                     layoutImpl,
-                    currentState.currentScene,
-                    currentState.currentOverlays,
+                    currentState,
                     isInContent = { it in element.stateByContent },
                 ) == content.key
 
@@ -639,20 +638,11 @@
 
 internal inline fun elementContentWhenIdle(
     layoutImpl: SceneTransitionLayoutImpl,
-    idle: TransitionState.Idle,
+    currentState: TransitionState,
     isInContent: (ContentKey) -> Boolean,
 ): ContentKey {
-    val currentScene = idle.currentScene
-    val overlays = idle.currentOverlays
-    return elementContentWhenIdle(layoutImpl, currentScene, overlays, isInContent)
-}
-
-private inline fun elementContentWhenIdle(
-    layoutImpl: SceneTransitionLayoutImpl,
-    currentScene: SceneKey,
-    overlays: Set<OverlayKey>,
-    isInContent: (ContentKey) -> Boolean,
-): ContentKey {
+    val currentScene = currentState.currentScene
+    val overlays = currentState.currentOverlays
     if (overlays.isEmpty()) {
         return currentScene
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index c790ff0..509a16c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -188,7 +188,9 @@
     return when (
         val elementState = movableElementState(element, layoutImpl.state.transitionStates)
     ) {
-        null -> false
+        null ->
+            movableElementContentWhenIdle(layoutImpl, element, layoutImpl.state.transitionState) ==
+                content
         is TransitionState.Idle ->
             movableElementContentWhenIdle(layoutImpl, element, elementState) == content
         is TransitionState.Transition -> {
@@ -217,7 +219,7 @@
 private fun movableElementContentWhenIdle(
     layoutImpl: SceneTransitionLayoutImpl,
     element: MovableElementKey,
-    elementState: TransitionState.Idle,
+    elementState: TransitionState,
 ): ContentKey {
     val contents = element.contentPicker.contents
     return elementContentWhenIdle(layoutImpl, elementState, isInContent = { contents.contains(it) })
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 09b5939..b4c8ad7 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -35,6 +35,7 @@
 import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.assertPositionInRootIsEqualTo
 import androidx.compose.ui.test.hasParent
+import androidx.compose.ui.test.hasTestTag
 import androidx.compose.ui.test.hasText
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onAllNodesWithText
@@ -404,4 +405,40 @@
         rule.waitForIdle()
         rule.onNodeWithTag(fooParentInOverlayTag).assertSizeIsEqualTo(fooSize)
     }
+
+    @Test
+    fun movableElementInOverlayShouldBeComposed() {
+        val fooKey = MovableElementKey("foo", contents = setOf(OverlayA))
+        val fooContentTag = "fooContentTag"
+
+        @Composable
+        fun ContentScope.MovableFoo(modifier: Modifier = Modifier) {
+            MovableElement(fooKey, modifier) {
+                content { Box(Modifier.testTag(fooContentTag).size(100.dp)) }
+            }
+        }
+
+        val state =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    initialScene = SceneA,
+                    initialOverlays = setOf(OverlayA),
+                )
+            }
+
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayout(state) {
+                    scene(SceneA) { Box(Modifier.fillMaxSize()) }
+                    overlay(OverlayA) { MovableFoo() }
+                    overlay(OverlayB) { Box(Modifier.size(50.dp)) }
+                }
+            }
+
+        rule.onNode(hasTestTag(fooContentTag)).assertIsDisplayed().assertSizeIsEqualTo(100.dp)
+
+        // Show overlay B. This shouldn't have any impact on Foo that should still be composed in A.
+        scope.launch { state.startTransition(transition(SceneA, OverlayB)) }
+        rule.onNode(hasTestTag(fooContentTag)).assertIsDisplayed().assertSizeIsEqualTo(100.dp)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 41e6929..3666de4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -72,6 +72,7 @@
 import androidx.compose.material3.Text
 import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -90,6 +91,7 @@
 import androidx.compose.ui.input.key.key
 import androidx.compose.ui.input.key.onKeyEvent
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
@@ -99,6 +101,7 @@
 import androidx.compose.ui.text.SpanStyle
 import androidx.compose.ui.text.buildAnnotatedString
 import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
@@ -377,15 +380,19 @@
 
     Column(modifier = modifier.fillMaxSize().padding(horizontal = 24.dp)) {
         Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
-            Box(modifier = Modifier.padding(start = 202.dp).width(412.dp)) {
-                TitleBar(isCustomizing)
-            }
-            Spacer(modifier = Modifier.weight(1f))
-            if (isShortcutCustomizerFlagEnabled) {
-                if (isCustomizing) {
-                    DoneButton(onClick = { isCustomizing = false })
+            // Keep title centered whether customize button is visible or not.
+            Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) {
+                Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
+                    TitleBar(isCustomizing)
+                }
+                if (isShortcutCustomizerFlagEnabled) {
+                    if (isCustomizing) {
+                        DoneButton(onClick = { isCustomizing = false })
+                    } else {
+                        CustomizeButton(onClick = { isCustomizing = true })
+                    }
                 } else {
-                    CustomizeButton(onClick = { isCustomizing = true })
+                    Spacer(modifier = Modifier.width(if (isCustomizing) 69.dp else 133.dp))
                 }
             }
         }
@@ -550,7 +557,7 @@
             .padding(8.dp)
     ) {
         Row(
-            modifier = Modifier.width(128.dp).align(Alignment.CenterVertically),
+            modifier = Modifier.width(128.dp).align(Alignment.CenterVertically).weight(0.333f),
             horizontalArrangement = Arrangement.spacedBy(16.dp),
             verticalAlignment = Alignment.CenterVertically,
         ) {
@@ -561,7 +568,7 @@
         }
         Spacer(modifier = Modifier.width(24.dp))
         ShortcutKeyCombinations(
-            modifier = Modifier.weight(1f),
+            modifier = Modifier.weight(.666f),
             shortcut = shortcut,
             isCustomizing = isCustomizing,
             onAddShortcutRequested = { onCustomizationRequested(shortcut.label) },
@@ -791,16 +798,25 @@
     selectedCategory: ShortcutCategoryType?,
     onCategoryClicked: (ShortcutCategoryUi) -> Unit,
 ) {
-    Column(modifier) {
-        ShortcutsSearchBar(onSearchQueryChanged)
-        Spacer(modifier = Modifier.heightIn(8.dp))
-        CategoriesPanelTwoPane(categories, selectedCategory, onCategoryClicked)
-        Spacer(modifier = Modifier.weight(1f))
-        KeyboardSettings(
-            horizontalPadding = 24.dp,
-            verticalPadding = 24.dp,
-            onKeyboardSettingsClicked,
-        )
+    CompositionLocalProvider(
+        // Restrict system font scale increases up to a max so categories display correctly.
+        LocalDensity provides
+            Density(
+                density = LocalDensity.current.density,
+                fontScale = LocalDensity.current.fontScale.coerceIn(1f, 1.5f),
+            )
+    ) {
+        Column(modifier) {
+            ShortcutsSearchBar(onSearchQueryChanged)
+            Spacer(modifier = Modifier.heightIn(8.dp))
+            CategoriesPanelTwoPane(categories, selectedCategory, onCategoryClicked)
+            Spacer(modifier = Modifier.weight(1f))
+            KeyboardSettings(
+                horizontalPadding = 24.dp,
+                verticalPadding = 24.dp,
+                onKeyboardSettingsClicked,
+            )
+        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelperUtils.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelperUtils.kt
index e295564..f9904f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelperUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelperUtils.kt
@@ -49,5 +49,5 @@
 object ShortcutHelperBottomSheet {
     val DefaultWidth = 412.dp
     val LargeScreenWidthPortrait = 704.dp
-    val LargeScreenWidthLandscape = 864.dp
+    val LargeScreenWidthLandscape = 960.dp
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 9166e7e..786d7d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -573,8 +573,29 @@
             return;
         }
 
-        mUiEventLogger.log(
-                NotificationCompactHeadsUpEvent.NOTIFICATION_COMPACT_HUN_SHOWN);
+        final StatusBarNotification containingRowSbn = getContainingRowSbn();
+        if (containingRowSbn == null) {
+            return;
+        }
+
+        mUiEventLogger.logWithInstanceId(
+                NotificationCompactHeadsUpEvent.NOTIFICATION_COMPACT_HUN_SHOWN,
+                containingRowSbn.getUid(),
+                containingRowSbn.getPackageName(),
+                containingRowSbn.getInstanceId());
+    }
+
+    @Nullable
+    private StatusBarNotification getContainingRowSbn() {
+        if (mContainingNotification == null) {
+            return null;
+        }
+        final NotificationEntry entry = mContainingNotification.getEntry();
+        if (entry == null) {
+            return null;
+        }
+
+        return entry.getSbn();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 13ac321..f3d5139 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -113,34 +113,38 @@
         }
 
     private val showIcon =
-        canShowIcon
-            .flatMapLatest { canShow ->
-                if (!canShow) {
-                    flowOf(false)
-                } else {
-                    combine(
-                        shouldShowIconForOosAfterHysteresis,
-                        interactor.connectionState,
-                        interactor.isWifiActive,
-                        airplaneModeRepository.isAirplaneMode,
-                    ) { showForOos, connectionState, isWifiActive, isAirplaneMode ->
-                        if (isWifiActive || isAirplaneMode) {
-                            false
-                        } else {
-                            showForOos ||
-                                connectionState == SatelliteConnectionState.On ||
-                                connectionState == SatelliteConnectionState.Connected
+        if (interactor.isOpportunisticSatelliteIconEnabled) {
+            canShowIcon
+                .flatMapLatest { canShow ->
+                    if (!canShow) {
+                        flowOf(false)
+                    } else {
+                        combine(
+                            shouldShowIconForOosAfterHysteresis,
+                            interactor.connectionState,
+                            interactor.isWifiActive,
+                            airplaneModeRepository.isAirplaneMode,
+                        ) { showForOos, connectionState, isWifiActive, isAirplaneMode ->
+                            if (isWifiActive || isAirplaneMode) {
+                                false
+                            } else {
+                                showForOos ||
+                                    connectionState == SatelliteConnectionState.On ||
+                                    connectionState == SatelliteConnectionState.Connected
+                            }
                         }
                     }
                 }
+                .distinctUntilChanged()
+                .logDiffsForTable(
+                    tableLog,
+                    columnPrefix = "vm",
+                    columnName = COL_VISIBLE,
+                    initialValue = false,
+                )
+            } else {
+                flowOf(false)
             }
-            .distinctUntilChanged()
-            .logDiffsForTable(
-                tableLog,
-                columnPrefix = "vm",
-                columnName = COL_VISIBLE,
-                initialValue = false,
-            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val icon: StateFlow<Icon?> =
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..d880bce 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) {
@@ -18013,14 +18003,6 @@
         @Override
         public void addStartInfoTimestamp(int key, long timestampNs, int uid, int pid,
                 int userId) {
-            // For the simplification, we don't support USER_ALL nor USER_CURRENT here.
-            if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) {
-                throw new IllegalArgumentException("Unsupported userId");
-            }
-
-            mUserController.handleIncomingUser(pid, uid, userId, true,
-                    ALLOW_NON_FULL, "addStartInfoTimestampSystem", null);
-
             addStartInfoTimestampInternal(key, timestampNs, userId, uid);
         }
 
@@ -19353,4 +19335,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/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/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 3644974..14d3fbc 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -393,6 +393,8 @@
                 adj = ProcessList.PERCEPTIBLE_APP_ADJ;
             } else if (adj < ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
                 adj = ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
+            } else if (Flags.addModifyRawOomAdjServiceLevel() && adj < ProcessList.SERVICE_ADJ) {
+                adj = ProcessList.SERVICE_ADJ;
             } else if (adj < ProcessList.CACHED_APP_MIN_ADJ) {
                 adj = ProcessList.CACHED_APP_MIN_ADJ;
             } else if (adj < ProcessList.CACHED_APP_MAX_ADJ) {
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 711b163..c59c40f 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -260,3 +260,13 @@
     description: "Use PROCESS_CAPABILITY_CPU_TIME to control unfreeze state."
     bug: "370817323"
 }
+
+flag {
+    name: "add_modify_raw_oom_adj_service_level"
+    namespace: "backstage_power"
+    description: "Add a SERVICE_ADJ level to the modifyRawOomAdj method"
+    bug: "374810368"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6ba3569..5f71660 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9224,6 +9224,9 @@
                 return;
             }
 
+            // index values sent to APM are in the stream type SDK range, not *10
+            int indexMinVolCurve = MIN_STREAM_VOLUME[mStreamType];
+            int indexMaxVolCurve = MAX_STREAM_VOLUME[mStreamType];
             synchronized (this) {
                 if (mStreamType == AudioSystem.STREAM_VOICE_CALL) {
                     if (MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO]
@@ -9234,11 +9237,15 @@
                     if (!equalScoLeaVcIndexRange() && isStreamBluetoothSco(mStreamType)) {
                         // SCO devices have a different min index
                         mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
+                        indexMinVolCurve = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO];
+                        indexMaxVolCurve = MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO];
                         mIndexStepFactor = 1.f;
                     } else if (equalScoLeaVcIndexRange() && isStreamBluetoothComm(mStreamType)) {
                         // For non SCO devices the stream state does not change the min index
                         if (mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
                             mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
+                            indexMinVolCurve = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO];
+                            indexMaxVolCurve = MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO];
                         } else {
                             mIndexMin = MIN_STREAM_VOLUME[mStreamType] * 10;
                         }
@@ -9259,7 +9266,7 @@
             }
 
             final int status = AudioSystem.initStreamVolume(
-                    mStreamType, mIndexMin / 10, mIndexMax / 10);
+                    mStreamType, indexMinVolCurve, indexMaxVolCurve);
             sVolumeLogger.enqueue(new EventLogger.StringEvent(
                     "updateIndexFactors() stream:" + mStreamType + " index min/max:"
                             + mIndexMin / 10 + "/" + mIndexMax / 10 + " indexStepFactor:"
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index b63b07f..e92b518 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -20,6 +20,7 @@
 import static android.media.AudioPlaybackConfiguration.MUTED_BY_APP_OPS;
 import static android.media.AudioPlaybackConfiguration.MUTED_BY_CLIENT_VOLUME;
 import static android.media.AudioPlaybackConfiguration.MUTED_BY_MASTER;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_PORT_VOLUME;
 import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_MUTED;
 import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_VOLUME;
 import static android.media.AudioPlaybackConfiguration.MUTED_BY_VOLUME_SHAPER;
@@ -444,7 +445,7 @@
         }
 
         if (DEBUG) {
-            Log.v(TAG, TextUtils.formatSimple("BLA portEvent(portId=%d, event=%s, extras=%s)",
+            Log.v(TAG, TextUtils.formatSimple("portEvent(portId=%d, event=%s, extras=%s)",
                     portId, AudioPlaybackConfiguration.playerStateToString(event), extras));
         }
 
@@ -1381,6 +1382,9 @@
                         if ((mEventValue & MUTED_BY_VOLUME_SHAPER) != 0) {
                             builder.append("volumeShaper ");
                         }
+                        if ((mEventValue & MUTED_BY_PORT_VOLUME) != 0) {
+                            builder.append("portVolume ");
+                        }
                     }
                     return builder.toString();
                 default:
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 586d594..e7ea868 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -4,6 +4,14 @@
 # Important: Flags must be accessed through DisplayManagerFlags.
 
 flag {
+    name: "is_always_on_available_api"
+    namespace: "display_manager"
+    description: "Allows querying of AOD availability"
+    bug: "324046664"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "enable_port_in_display_layout"
     namespace: "display_manager"
     description: "Allows refering to displays by port in display layout"
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 c460465..fce008c 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -175,7 +175,7 @@
 
     private void maybeStopMediaProjection(int reason) {
         synchronized (mLock) {
-            if (!mMediaProjectionStopController.isExemptFromStopping(mProjectionGrant)) {
+            if (!mMediaProjectionStopController.isExemptFromStopping(mProjectionGrant, reason)) {
                 Slog.d(TAG, "Content Recording: Stopping MediaProjection due to "
                         + MediaProjectionStopController.stopReasonToString(reason));
                 mProjectionGrant.stop();
@@ -1272,6 +1272,10 @@
             return mDisplayId;
         }
 
+        long getCreateTimeMillis() {
+            return mCreateTimeMs;
+        }
+
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
         @Override
         public boolean isValid() {
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java
index f5b26c4..c018e6b 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java
@@ -27,6 +27,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Binder;
+import android.os.SystemClock;
 import android.provider.Settings;
 import android.telecom.TelecomManager;
 import android.telephony.TelephonyCallback;
@@ -46,6 +47,8 @@
 
     private static final String TAG = "MediaProjectionStopController";
     @VisibleForTesting
+    static final int STOP_REASON_UNKNOWN = 0;
+    @VisibleForTesting
     static final int STOP_REASON_KEYGUARD = 1;
     @VisibleForTesting
     static final int STOP_REASON_CALL_END = 2;
@@ -61,6 +64,7 @@
     private final ContentResolver mContentResolver;
 
     private boolean mIsInCall;
+    private long mLastCallStartTimeMillis;
 
     public MediaProjectionStopController(Context context, Consumer<Integer> stopReasonConsumer) {
         mStopReasonConsumer = stopReasonConsumer;
@@ -95,8 +99,8 @@
      * Checks whether the given projection grant is exempt from stopping restrictions.
      */
     public boolean isExemptFromStopping(
-            MediaProjectionManagerService.MediaProjection projectionGrant) {
-        return isExempt(projectionGrant, false);
+            MediaProjectionManagerService.MediaProjection projectionGrant, int stopReason) {
+        return isExempt(projectionGrant, stopReason, false);
     }
 
     /**
@@ -110,7 +114,8 @@
      * MediaProjection session
      */
     private boolean isExempt(
-            MediaProjectionManagerService.MediaProjection projectionGrant, boolean forStart) {
+            MediaProjectionManagerService.MediaProjection projectionGrant, int stopReason,
+            boolean forStart) {
         if (projectionGrant == null || projectionGrant.packageName == null) {
             return true;
         }
@@ -151,6 +156,14 @@
             return true;
         }
 
+        if (stopReason == STOP_REASON_CALL_END
+                && projectionGrant.getCreateTimeMillis() < mLastCallStartTimeMillis) {
+            Slog.v(TAG,
+                    "Continuing MediaProjection as (phone) call started after MediaProjection was"
+                            + " created.");
+            return true;
+        }
+
         return false;
     }
 
@@ -167,7 +180,7 @@
             return false;
         }
 
-        if (isExempt(projectionGrant, true)) {
+        if (isExempt(projectionGrant, STOP_REASON_UNKNOWN, true)) {
             return false;
         }
         return true;
@@ -188,9 +201,13 @@
             return;
         }
         boolean isInCall = mTelecomManager.isInCall();
+        if (isInCall) {
+            mLastCallStartTimeMillis = SystemClock.uptimeMillis();
+        }
         if (isInCall == mIsInCall) {
             return;
         }
+
         if (mIsInCall && !isInCall) {
             mStopReasonConsumer.accept(STOP_REASON_CALL_END);
         }
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index c5c8a5e..1f8a200 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -262,7 +262,7 @@
             // TODO: implement
         }
         @Override
-        public SoundProfile getSoundProfileById(String id) {
+        public SoundProfile getSoundProfile(int type, String id) {
             return null;
         }
         @Override
@@ -313,6 +313,15 @@
         }
 
         @Override
+        public List<String> getSoundProfileAllowList() {
+            return new ArrayList<>();
+        }
+
+        @Override
+        public void setSoundProfileAllowList(List<String> packages) {
+        }
+
+        @Override
         public boolean isSupported() {
             return false;
         }
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 9f4b9f1..6d54be8 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -58,6 +58,7 @@
 import android.os.storage.VolumeInfo;
 import android.provider.DeviceConfig;
 import android.stats.storage.StorageEnums;
+import android.text.TextUtils;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.Pair;
@@ -355,7 +356,8 @@
             @Nullable int[] userIds,
             @Nullable int[] instantUserIds,
             @Nullable SparseArray<int[]> broadcastAllowList,
-            @NonNull AndroidPackage pkg) {
+            @NonNull AndroidPackage pkg,
+            @NonNull String[] sharedUidPackages) {
         final boolean isForWholeApp = componentNames.contains(packageName);
         if (isForWholeApp || !android.content.pm.Flags.reduceBroadcastsForComponentStateChanges()) {
             sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, componentNames,
@@ -374,20 +376,36 @@
         exportedComponentNames.removeAll(notExportedComponentNames);
 
         if (!notExportedComponentNames.isEmpty()) {
-            // Limit sending of the PACKAGE_CHANGED broadcast to only the system and the
-            // application itself when the component is not exported.
+            // Limit sending of the PACKAGE_CHANGED broadcast to only the system, the application
+            // itself and applications with the same UID when the component is not exported.
 
             // First, send the PACKAGE_CHANGED broadcast to the system.
-            sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
-                    notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
-                    broadcastAllowList, "android" /* targetPackageName */,
-                    new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
+            if (!TextUtils.equals(packageName, "android")) {
+                sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
+                        notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
+                        broadcastAllowList, "android" /* targetPackageName */,
+                        new String[]{
+                                PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
+            }
 
             // Second, send the PACKAGE_CHANGED broadcast to the application itself.
             sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
                     notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
                     broadcastAllowList, packageName /* targetPackageName */,
                     null /* requiredPermissions */);
+
+            // Third, send the PACKAGE_CHANGED broadcast to the applications with the same UID.
+            for (int i = 0; i < sharedUidPackages.length; i++) {
+                final String sharedPackage = sharedUidPackages[i];
+                if (TextUtils.equals(packageName, sharedPackage)) {
+                    continue;
+                }
+                sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
+                        notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
+                        broadcastAllowList, sharedPackage /* targetPackageName */,
+                        null /* requiredPermissions */);
+            }
+
         }
 
         if (!exportedComponentNames.isEmpty()) {
@@ -936,7 +954,8 @@
                 isInstantApp ? null : snapshot.getVisibilityAllowLists(packageName, userIds);
         mHandler.post(() -> sendPackageChangedBroadcastInternal(
                 packageName, dontKillApp, componentNames, packageUid, reason, userIds,
-                instantUserIds, broadcastAllowList, setting.getPkg()));
+                instantUserIds, broadcastAllowList, setting.getPkg(),
+                snapshot.getSharedUserPackagesForPackage(packageName, userId)));
         mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames,
                 packageUid, reason, userIds, instantUserIds, broadcastAllowList, mHandler);
     }
diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
index 745665b..527d680 100644
--- a/services/core/java/com/android/server/pm/InstallDependencyHelper.java
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -17,51 +17,240 @@
 package com.android.server.pm;
 
 import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+import static android.os.Process.SYSTEM_UID;
 
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
 import android.content.pm.SharedLibraryInfo;
+import android.content.pm.dependencyinstaller.DependencyInstallerCallback;
+import android.content.pm.dependencyinstaller.IDependencyInstallerCallback;
+import android.content.pm.dependencyinstaller.IDependencyInstallerService;
 import android.content.pm.parsing.PackageLite;
+import android.os.Handler;
 import android.os.OutcomeReceiver;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
 
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Helper class to interact with SDK Dependency Installer service.
  */
 public class InstallDependencyHelper {
-    private final SharedLibrariesImpl mSharedLibraries;
+    private static final String TAG = InstallDependencyHelper.class.getSimpleName();
+    private static final boolean DEBUG = true;
+    private static final String ACTION_INSTALL_DEPENDENCY =
+            "android.intent.action.INSTALL_DEPENDENCY";
+    // The maximum amount of time to wait before the system unbinds from the verifier.
+    private static final long UNBIND_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(6);
+    private static final long REQUEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(1);
 
-    InstallDependencyHelper(SharedLibrariesImpl sharedLibraries) {
+    private final SharedLibrariesImpl mSharedLibraries;
+    private final Context mContext;
+    private final Object mRemoteServiceLock = new Object();
+
+    @GuardedBy("mRemoteServiceLock")
+    private ServiceConnector<IDependencyInstallerService> mRemoteService = null;
+
+    InstallDependencyHelper(Context context, SharedLibrariesImpl sharedLibraries) {
+        mContext = context;
         mSharedLibraries = sharedLibraries;
     }
 
-    void resolveLibraryDependenciesIfNeeded(PackageLite pkg,
-            OutcomeReceiver<Void, PackageManagerException> callback) {
-        final List<SharedLibraryInfo> missing;
+    void resolveLibraryDependenciesIfNeeded(PackageLite pkg, Computer snapshot, int userId,
+            Handler handler, OutcomeReceiver<Void, PackageManagerException> origCallback) {
+        CallOnceProxy callback = new CallOnceProxy(handler, origCallback);
         try {
-            missing = mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
+            resolveLibraryDependenciesIfNeededInternal(pkg, snapshot, userId, handler, callback);
         } catch (PackageManagerException e) {
             callback.onError(e);
-            return;
+        } catch (Exception e) {
+            onError(callback, e.getMessage());
         }
+    }
+
+
+    private void resolveLibraryDependenciesIfNeededInternal(PackageLite pkg, Computer snapshot,
+            int userId, Handler handler, CallOnceProxy callback) throws PackageManagerException {
+        final List<SharedLibraryInfo> missing =
+                mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
 
         if (missing.isEmpty()) {
+            if (DEBUG) {
+                Slog.i(TAG, "No missing dependency for " + pkg);
+            }
             // No need for dependency resolution. Move to installation directly.
             callback.onResult(null);
             return;
         }
 
-        try {
-            bindToDependencyInstaller();
-        } catch (Exception e) {
-            PackageManagerException pe = new PackageManagerException(
-                    INSTALL_FAILED_MISSING_SHARED_LIBRARY, e.getMessage());
-            callback.onError(pe);
+        if (!bindToDependencyInstallerIfNeeded(userId, handler, snapshot)) {
+            onError(callback, "Dependency Installer Service not found");
+            return;
+        }
+
+        IDependencyInstallerCallback serviceCallback = new IDependencyInstallerCallback.Stub() {
+            @Override
+            public void onAllDependenciesResolved(int[] sessionIds) throws RemoteException {
+                // TODO(b/372862145): Implement waiting for sessions to finish installation
+                callback.onResult(null);
+            }
+
+            @Override
+            public void onFailureToResolveAllDependencies() throws RemoteException {
+                onError(callback, "Failed to resolve all dependencies automatically");
+            }
+        };
+
+        boolean scheduleSuccess;
+        synchronized (mRemoteServiceLock) {
+            scheduleSuccess = mRemoteService.run(service -> {
+                service.onDependenciesRequired(missing,
+                        new DependencyInstallerCallback(serviceCallback.asBinder()));
+            });
+        }
+        if (!scheduleSuccess) {
+            onError(callback, "Failed to schedule job on Dependency Installer Service");
         }
     }
 
-    private void bindToDependencyInstaller() {
-        throw new IllegalStateException("Failed to bind to Dependency Installer");
+    private void onError(CallOnceProxy callback, String msg) {
+        PackageManagerException pe = new PackageManagerException(
+                INSTALL_FAILED_MISSING_SHARED_LIBRARY, msg);
+        callback.onError(pe);
     }
 
+    private boolean bindToDependencyInstallerIfNeeded(int userId, Handler handler,
+            Computer snapshot) {
+        synchronized (mRemoteServiceLock) {
+            if (mRemoteService != null) {
+                if (DEBUG) {
+                    Slog.i(TAG, "DependencyInstallerService already bound");
+                }
+                return true;
+            }
+        }
 
+        Intent serviceIntent = new Intent(ACTION_INSTALL_DEPENDENCY);
+        // TODO(b/372862145): Use RoleManager to find the package name
+        List<ResolveInfo> resolvedIntents = snapshot.queryIntentServicesInternal(
+                serviceIntent, /*resolvedType=*/ null, /*flags=*/0,
+                userId, SYSTEM_UID, Process.INVALID_PID,
+                /*includeInstantApps*/ false, /*resolveForStart*/ false);
+
+        if (resolvedIntents.isEmpty()) {
+            return false;
+        }
+
+
+        ResolveInfo resolveInfo = resolvedIntents.getFirst();
+        ComponentName componentName = resolveInfo.getComponentInfo().getComponentName();
+        serviceIntent.setComponent(componentName);
+
+        ServiceConnector<IDependencyInstallerService> serviceConnector =
+                new ServiceConnector.Impl<IDependencyInstallerService>(mContext, serviceIntent,
+                    Context.BIND_AUTO_CREATE, userId,
+                    IDependencyInstallerService.Stub::asInterface) {
+                    @Override
+                    protected Handler getJobHandler() {
+                        return handler;
+                    }
+
+                    @Override
+                    protected long getRequestTimeoutMs() {
+                        return REQUEST_TIMEOUT_MILLIS;
+                    }
+
+                    @Override
+                    protected long getAutoDisconnectTimeoutMs() {
+                        return UNBIND_TIMEOUT_MILLIS;
+                    }
+                };
+
+
+        synchronized (mRemoteServiceLock) {
+            // Some other thread managed to connect to the service first
+            if (mRemoteService != null) {
+                return true;
+            }
+            mRemoteService = serviceConnector;
+            mRemoteService.setServiceLifecycleCallbacks(
+                new ServiceConnector.ServiceLifecycleCallbacks<>() {
+                    @Override
+                    public void onDisconnected(@NonNull IDependencyInstallerService service) {
+                        Slog.w(TAG,
+                                "DependencyInstallerService " + componentName + " is disconnected");
+                        destroy();
+                    }
+
+                    @Override
+                    public void onBinderDied() {
+                        Slog.w(TAG, "DependencyInstallerService " + componentName + " has died");
+                        destroy();
+                    }
+
+                    private void destroy() {
+                        synchronized (mRemoteServiceLock) {
+                            if (mRemoteService != null) {
+                                mRemoteService.unbind();
+                                mRemoteService = null;
+                            }
+                        }
+                    }
+
+                });
+            AndroidFuture<IDependencyInstallerService> unusedFuture = mRemoteService.connect();
+        }
+        return true;
+    }
+
+    /**
+     * Ensure we call one of the outcomes only once, on the right handler.
+     *
+     * Repeated calls will be no-op.
+     */
+    private static class CallOnceProxy implements OutcomeReceiver<Void, PackageManagerException> {
+        private final Handler mHandler;
+        private final OutcomeReceiver<Void, PackageManagerException> mCallback;
+        @GuardedBy("this")
+        private boolean mCalled = false;
+
+        CallOnceProxy(Handler handler, OutcomeReceiver<Void, PackageManagerException> callback) {
+            mHandler = handler;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onResult(Void result) {
+            synchronized (this) {
+                if (!mCalled) {
+                    mHandler.post(() -> {
+                        mCallback.onResult(null);
+                    });
+                    mCalled = true;
+                }
+            }
+        }
+
+        @Override
+        public void onError(@NonNull PackageManagerException error) {
+            synchronized (this) {
+                if (!mCalled) {
+                    mHandler.post(() -> {
+                        mCallback.onError(error);
+                    });
+                    mCalled = true;
+                }
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index eb70748..9b44f93 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -347,7 +347,7 @@
         synchronized (mVerificationPolicyPerUser) {
             mVerificationPolicyPerUser.put(USER_SYSTEM, DEFAULT_VERIFICATION_POLICY);
         }
-        mInstallDependencyHelper = new InstallDependencyHelper(
+        mInstallDependencyHelper = new InstallDependencyHelper(mContext,
                 mPm.mInjector.getSharedLibrariesImpl());
 
         LocalServices.getService(SystemServiceManager.class).startService(
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index e156b31..505b7e6 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3428,8 +3428,8 @@
 
     private void resolveLibraryDependenciesIfNeeded() {
         synchronized (mLock) {
-            // TODO(b/372862145): Callback should be called on a handler passed as parameter
             mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(mPackageLite,
+                    mPm.snapshotComputer(), userId, mHandler,
                     new OutcomeReceiver<>() {
 
                         @Override
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index fc54f68..17d7a14 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -1017,10 +1017,11 @@
                     boolean isSdkOrStatic = libraryType.equals(LIBRARY_TYPE_SDK)
                             || libraryType.equals(LIBRARY_TYPE_STATIC);
                     if (isSdkOrStatic && outMissingSharedLibraryInfos != null) {
-                        // TODO(b/372862145): Pass the CertDigest too
                         // If Dependency Installation is supported, try that instead of failing.
+                        final List<String> libCertDigests = Arrays.asList(requiredCertDigests[i]);
                         SharedLibraryInfo missingLibrary = new SharedLibraryInfo(
-                                libName, libVersion, SharedLibraryInfo.TYPE_SDK_PACKAGE
+                                libName, libVersion, SharedLibraryInfo.TYPE_SDK_PACKAGE,
+                                libCertDigests
                         );
                         outMissingSharedLibraryInfos.add(missingLibrary);
                     } else {
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
index 07478e3..9e75cf2 100644
--- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
@@ -37,8 +37,11 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 import java.util.NoSuchElementException;
 
@@ -60,6 +63,9 @@
          * used for another vibration.
          */
         void onSessionReleased(long sessionId);
+
+        /** Request the manager to trigger a vibration within this session. */
+        void vibrate(long sessionId, CallerInfo callerInfo, CombinedVibration vibration);
     }
 
     private final Object mLock = new Object();
@@ -71,7 +77,9 @@
     private final IVibrationSessionCallback mCallback;
     private final CallerInfo mCallerInfo;
     private final VibratorManagerHooks mManagerHooks;
+    private final DeviceAdapter mDeviceAdapter;
     private final Handler mHandler;
+    private final List<DebugInfo> mVibrations = new ArrayList<>();
 
     @GuardedBy("mLock")
     private Status mStatus = Status.RUNNING;
@@ -83,24 +91,28 @@
     private long mEndUptime;
     @GuardedBy("mLock")
     private long mEndTime; // for debugging
+    @GuardedBy("mLock")
+    private VibrationStepConductor mConductor;
 
     VendorVibrationSession(@NonNull CallerInfo callerInfo, @NonNull Handler handler,
-            @NonNull VibratorManagerHooks managerHooks, @NonNull int[] vibratorIds,
+            @NonNull VibratorManagerHooks managerHooks, @NonNull DeviceAdapter deviceAdapter,
             @NonNull IVibrationSessionCallback callback) {
         mCreateUptime = SystemClock.uptimeMillis();
         mCreateTime = System.currentTimeMillis();
-        mVibratorIds = vibratorIds;
+        mVibratorIds = deviceAdapter.getAvailableVibratorIds();
         mHandler = handler;
         mCallback = callback;
         mCallerInfo = callerInfo;
         mManagerHooks = managerHooks;
+        mDeviceAdapter = deviceAdapter;
         CancellationSignal.fromTransport(mCancellationSignal).setOnCancelListener(this);
     }
 
     @Override
     public void vibrate(CombinedVibration vibration, String reason) {
-        // TODO(b/345414356): implement vibration support
-        throw new UnsupportedOperationException("Vendor session vibrations not yet implemented");
+        CallerInfo vibrationCallerInfo = new CallerInfo(mCallerInfo.attrs, mCallerInfo.uid,
+                mCallerInfo.deviceId, mCallerInfo.opPkg, reason);
+        mManagerHooks.vibrate(mSessionId, vibrationCallerInfo, vibration);
     }
 
     @Override
@@ -146,7 +158,7 @@
     public DebugInfo getDebugInfo() {
         synchronized (mLock) {
             return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime,
-                    mEndUptime, mEndTime);
+                    mEndUptime, mEndTime, mVibrations);
         }
     }
 
@@ -200,12 +212,12 @@
 
     @Override
     public void notifyVibratorCallback(int vibratorId, long vibrationId) {
-        // TODO(b/345414356): implement vibration support
+        // Ignore it, the session vibration playback doesn't depend on HAL timings
     }
 
     @Override
     public void notifySyncedVibratorsCallback(long vibrationId) {
-        // TODO(b/345414356): implement vibration support
+        // Ignore it, the session vibration playback doesn't depend on HAL timings
     }
 
     @Override
@@ -214,8 +226,9 @@
             // If end was not requested then the HAL has cancelled the session.
             maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON);
             maybeSetStatusToRequestedLocked();
+            clearVibrationConductor();
         }
-        mManagerHooks.onSessionReleased(mSessionId);
+        mHandler.post(() -> mManagerHooks.onSessionReleased(mSessionId));
     }
 
     @Override
@@ -228,7 +241,8 @@
                     /* includeDate= */ true))
                     + ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
                     + ", callerInfo: " + mCallerInfo
-                    + ", vibratorIds: " + Arrays.toString(mVibratorIds);
+                    + ", vibratorIds: " + Arrays.toString(mVibratorIds)
+                    + ", vibrations: " + mVibrations;
         }
     }
 
@@ -254,6 +268,13 @@
         return mVibratorIds;
     }
 
+    @VisibleForTesting
+    public List<DebugInfo> getVibrations() {
+        synchronized (mLock) {
+            return new ArrayList<>(mVibrations);
+        }
+    }
+
     public ICancellationSignal getCancellationSignal() {
         return mCancellationSignal;
     }
@@ -278,7 +299,39 @@
         }
         if (isAlreadyEnded) {
             // Session already ended, make sure we end it in the HAL.
-            mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true);
+            mHandler.post(() -> mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true));
+        }
+    }
+
+    public void notifyVibrationAttempt(DebugInfo vibrationDebugInfo) {
+        mVibrations.add(vibrationDebugInfo);
+    }
+
+    @Nullable
+    public VibrationStepConductor clearVibrationConductor() {
+        synchronized (mLock) {
+            VibrationStepConductor conductor = mConductor;
+            if (conductor != null) {
+                mVibrations.add(conductor.getVibration().getDebugInfo());
+            }
+            mConductor = null;
+            return conductor;
+        }
+    }
+
+    public DeviceAdapter getDeviceAdapter() {
+        return mDeviceAdapter;
+    }
+
+    public boolean maybeSetVibrationConductor(VibrationStepConductor conductor) {
+        synchronized (mLock) {
+            if (mConductor != null) {
+                Slog.d(TAG, "Vibration session still dispatching previous vibration,"
+                        + " new vibration ignored");
+                return false;
+            }
+            mConductor = conductor;
+            return true;
         }
     }
 
@@ -296,7 +349,7 @@
             }
         }
         if (shouldTriggerSessionHook) {
-            mManagerHooks.endSession(mSessionId, shouldAbort);
+            mHandler.post(() ->  mManagerHooks.endSession(mSessionId, shouldAbort));
         }
     }
 
@@ -309,6 +362,11 @@
         mEndStatusRequest = status;
         mEndTime = System.currentTimeMillis();
         mEndUptime = SystemClock.uptimeMillis();
+        if (mConductor != null) {
+            // Vibration is being dispatched when session end was requested, cancel it.
+            mConductor.notifyCancelled(new Vibration.EndInfo(status),
+                    /* immediate= */ status != Status.FINISHED);
+        }
         if (isStarted()) {
             // Only trigger "finishing" callback if session started.
             // Run client callback in separate thread.
@@ -377,6 +435,7 @@
     static final class DebugInfoImpl implements VibrationSession.DebugInfo {
         private final Status mStatus;
         private final CallerInfo mCallerInfo;
+        private final List<DebugInfo> mVibrations;
 
         private final long mCreateUptime;
         private final long mCreateTime;
@@ -385,7 +444,7 @@
         private final long mDurationMs;
 
         DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime,
-                long startTime, long endUptime, long endTime) {
+                long startTime, long endUptime, long endTime, List<DebugInfo> vibrations) {
             mStatus = status;
             mCallerInfo = callerInfo;
             mCreateUptime = createUptime;
@@ -393,6 +452,7 @@
             mStartTime = startTime;
             mEndTime = endTime;
             mDurationMs = endUptime > 0 ? endUptime - createUptime : -1;
+            mVibrations = vibrations == null ? new ArrayList<>() : new ArrayList<>(vibrations);
         }
 
         @Override
@@ -418,6 +478,9 @@
 
         @Override
         public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
+            for (DebugInfo vibration : mVibrations) {
+                vibration.logMetrics(statsLogger);
+            }
         }
 
         @Override
@@ -448,6 +511,14 @@
             pw.println("endTime = " + (mEndTime == 0 ? null
                     : formatTime(mEndTime, /*includeDate=*/ true)));
             pw.println("callerInfo = " + mCallerInfo);
+
+            pw.println("vibrations:");
+            pw.increaseIndent();
+            for (DebugInfo vibration : mVibrations) {
+                vibration.dump(pw);
+            }
+            pw.decreaseIndent();
+
             pw.decreaseIndent();
         }
 
@@ -477,6 +548,12 @@
                     " | %s (uid=%d, deviceId=%d) | reason: %s",
                     mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.reason);
             pw.println(timingsStr + paramStr + audioUsageStr + callerStr);
+
+            pw.increaseIndent();
+            for (DebugInfo vibration : mVibrations) {
+                vibration.dumpCompact(pw);
+            }
+            pw.decreaseIndent();
         }
 
         @Override
@@ -487,7 +564,8 @@
                     /* includeDate= */ true))
                     + ", durationMs: " + mDurationMs
                     + ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
-                    + ", callerInfo: " + mCallerInfo;
+                    + ", callerInfo: " + mCallerInfo
+                    + ", vibrations: " + mVibrations;
         }
     }
 }
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 1030df6..cc163db 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -106,7 +106,7 @@
     private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
     private static final String VIBRATOR_CONTROL_SERVICE =
             "android.frameworks.vibrator.IVibratorControlService/default";
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
     private static final VibrationAttributes DEFAULT_ATTRIBUTES =
             new VibrationAttributes.Builder().build();
     private static final int ATTRIBUTES_ALL_BYPASS_FLAGS =
@@ -610,6 +610,11 @@
             logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_ERROR_TOKEN);
             return null;
         }
+        enforceUpdateAppOpsStatsPermission(uid);
+        if (!isEffectValid(effect)) {
+            logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_UNSUPPORTED);
+            return null;
+        }
         if (effect.hasVendorEffects()) {
             if (!Flags.vendorVibrationEffects()) {
                 Slog.e(TAG, "vibrate; vendor effects feature disabled");
@@ -622,11 +627,6 @@
                 return null;
             }
         }
-        enforceUpdateAppOpsStatsPermission(uid);
-        if (!isEffectValid(effect)) {
-            logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_UNSUPPORTED);
-            return null;
-        }
         // Create Vibration.Stats as close to the received request as possible, for tracking.
         SingleVibrationSession session = new SingleVibrationSession(token, callerInfo, effect);
         HalVibration vib = session.getVibration();
@@ -658,6 +658,7 @@
 
             // If not ignored so far then try to start this vibration.
             if (ignoreStatus == null) {
+                // TODO(b/378492007): Investigate if we can move this around AppOpsManager calls
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     if (mCurrentSession != null) {
@@ -703,6 +704,7 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Canceling vibration");
                 }
+                // TODO(b/378492007): Investigate if we can move this around AppOpsManager calls
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     // TODO(b/370948466): investigate why token not checked on external vibrations.
@@ -762,8 +764,18 @@
             vibratorIds = new int[0];
         }
         enforceUpdateAppOpsStatsPermission(uid);
+
+        // Create session with adapter that only uses the session vibrators.
+        SparseArray<VibratorController> sessionVibrators = new SparseArray<>(vibratorIds.length);
+        for (int vibratorId : vibratorIds) {
+            VibratorController controller = mVibrators.get(vibratorId);
+            if (controller != null) {
+                sessionVibrators.put(vibratorId, controller);
+            }
+        }
+        DeviceAdapter deviceAdapter = new DeviceAdapter(mVibrationSettings, sessionVibrators);
         VendorVibrationSession session = new VendorVibrationSession(callerInfo, mHandler,
-                mVendorVibrationSessionCallbacks, vibratorIds, callback);
+                mVendorVibrationSessionCallbacks, deviceAdapter, callback);
 
         if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
             // Force update of user settings before checking if this vibration effect should
@@ -787,12 +799,15 @@
                 ignoreStatus = Status.IGNORED_UNSUPPORTED;
             }
 
-            // Check if any vibrator ID was requested.
-            if (ignoreStatus == null && vibratorIds.length == 0) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Empty vibrator ids to start session, ignoring request");
+            // Check if vibrator IDs requested are available.
+            if (ignoreStatus == null) {
+                if (vibratorIds.length == 0
+                        || vibratorIds.length != deviceAdapter.getAvailableVibratorIds().length) {
+                    Slog.e(TAG, "Bad vibrator ids to start session, ignoring request."
+                            + " requested=" + Arrays.toString(vibratorIds)
+                            + " available=" + Arrays.toString(mVibratorIds));
+                    ignoreStatus = Status.IGNORED_UNSUPPORTED;
                 }
-                ignoreStatus = Status.IGNORED_UNSUPPORTED;
             }
 
             // Check if user settings or DnD is set to ignore this session.
@@ -810,6 +825,7 @@
             }
 
             if (ignoreStatus == null) {
+                // TODO(b/378492007): Investigate if we can move this around AppOpsManager calls
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     // If not ignored so far then stop ongoing sessions before starting this one.
@@ -839,22 +855,40 @@
     private Status startVendorSessionLocked(VendorVibrationSession session) {
         Trace.traceBegin(TRACE_TAG_VIBRATOR, "startSessionLocked");
         try {
+            long sessionId = session.getSessionId();
+            if (DEBUG) {
+                Slog.d(TAG, "Starting session " + sessionId + " in HAL");
+            }
             if (session.isEnded()) {
                 // Session already ended, possibly cancelled by app cancellation signal.
                 return session.getStatus();
             }
-            if (!session.linkToDeath()) {
-                return Status.IGNORED_ERROR_TOKEN;
+            int mode = startAppOpModeLocked(session.getCallerInfo());
+            switch (mode) {
+                case AppOpsManager.MODE_ALLOWED:
+                    Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0);
+                    // Make sure mCurrentVibration is set while triggering the HAL.
+                    mCurrentSession = session;
+                    if (!session.linkToDeath()) {
+                        mCurrentSession = null;
+                        return Status.IGNORED_ERROR_TOKEN;
+                    }
+                    if (!mNativeWrapper.startSession(sessionId, session.getVibratorIds())) {
+                        Slog.e(TAG, "Error starting session " + sessionId + " on vibrators "
+                                + Arrays.toString(session.getVibratorIds()));
+                        session.unlinkToDeath();
+                        mCurrentSession = null;
+                        return Status.IGNORED_UNSUPPORTED;
+                    }
+                    session.notifyStart();
+                    return null;
+                case AppOpsManager.MODE_ERRORED:
+                    Slog.w(TAG, "Start AppOpsManager operation errored for uid "
+                            + session.getCallerInfo().uid);
+                    return Status.IGNORED_ERROR_APP_OPS;
+                default:
+                    return Status.IGNORED_APP_OPS;
             }
-            if (!mNativeWrapper.startSession(session.getSessionId(), session.getVibratorIds())) {
-                Slog.e(TAG, "Error starting session " + session.getSessionId()
-                        + " on vibrators " + Arrays.toString(session.getVibratorIds()));
-                session.unlinkToDeath();
-                return Status.IGNORED_UNSUPPORTED;
-            }
-            session.notifyStart();
-            mCurrentSession = session;
-            return null;
         } finally {
             Trace.traceEnd(TRACE_TAG_VIBRATOR);
         }
@@ -1045,6 +1079,9 @@
     @GuardedBy("mLock")
     @Nullable
     private Status startVibrationOnThreadLocked(SingleVibrationSession session) {
+        if (DEBUG) {
+            Slog.d(TAG, "Starting vibration " + session.getVibration().id +  " on thread");
+        }
         VibrationStepConductor conductor = createVibrationStepConductor(session.getVibration());
         session.setVibrationConductor(conductor);
         int mode = startAppOpModeLocked(session.getCallerInfo());
@@ -1080,12 +1117,18 @@
             mNextSession = null;
             Status errorStatus = startVibrationOnThreadLocked(session);
             if (errorStatus != null) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Error starting next vibration " + session.getVibration().id);
+                }
                 endSessionLocked(session, errorStatus);
             }
         } else if (mNextSession instanceof VendorVibrationSession session) {
             mNextSession = null;
             Status errorStatus = startVendorSessionLocked(session);
             if (errorStatus != null) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Error starting next session " + session.getSessionId());
+                }
                 endSessionLocked(session, errorStatus);
             }
         } // External vibrations cannot be started asynchronously.
@@ -1103,6 +1146,16 @@
     }
 
     private VibrationStepConductor createVibrationStepConductor(HalVibration vib) {
+        return createVibrationStepConductor(vib, mDeviceAdapter, /* isInSession= */ false);
+    }
+
+    private VibrationStepConductor createSessionVibrationStepConductor(HalVibration vib,
+            DeviceAdapter deviceAdapter) {
+        return createVibrationStepConductor(vib, deviceAdapter, /* isInSession= */ true);
+    }
+
+    private VibrationStepConductor createVibrationStepConductor(HalVibration vib,
+            DeviceAdapter deviceAdapter, boolean isInSession) {
         CompletableFuture<Void> requestVibrationParamsFuture = null;
 
         if (Flags.adaptiveHapticsEnabled()
@@ -1114,8 +1167,8 @@
                             mVibrationSettings.getRequestVibrationParamsTimeoutMs());
         }
 
-        return new VibrationStepConductor(vib, /* isInSession= */ false, mVibrationSettings,
-                mDeviceAdapter, mVibrationScaler, mFrameworkStatsLogger,
+        return new VibrationStepConductor(vib, isInSession, mVibrationSettings,
+                deviceAdapter, mVibrationScaler, mFrameworkStatsLogger,
                 requestVibrationParamsFuture, mVibrationThreadCallbacks);
     }
 
@@ -1136,18 +1189,15 @@
 
     private void logAndRecordVibrationAttempt(@Nullable CombinedVibration effect,
             CallerInfo callerInfo, Status status) {
-        logAndRecordVibration(
-                new Vibration.DebugInfoImpl(status, callerInfo,
-                        VibrationStats.StatsInfo.findVibrationType(effect), new VibrationStats(),
-                        effect, /* originalEffect= */ null, VibrationScaler.SCALE_NONE,
-                        VibrationScaler.ADAPTIVE_SCALE_NONE));
+        logAndRecordVibration(createVibrationAttemptDebugInfo(effect, callerInfo, status));
     }
 
     private void logAndRecordSessionAttempt(CallerInfo callerInfo, Status status) {
         logAndRecordVibration(
                 new VendorVibrationSession.DebugInfoImpl(status, callerInfo,
                         SystemClock.uptimeMillis(), System.currentTimeMillis(),
-                        /* startTime= */ 0, /* endUptime= */ 0, /* endTime= */ 0));
+                        /* startTime= */ 0, /* endUptime= */ 0, /* endTime= */ 0,
+                        /* vibrations= */ null));
     }
 
     private void logAndRecordVibration(DebugInfo info) {
@@ -1156,6 +1206,14 @@
         mVibratorManagerRecords.record(info);
     }
 
+    private DebugInfo createVibrationAttemptDebugInfo(@Nullable CombinedVibration effect,
+            CallerInfo callerInfo, Status status) {
+        return new Vibration.DebugInfoImpl(status, callerInfo,
+                VibrationStats.StatsInfo.findVibrationType(effect), new VibrationStats(),
+                effect, /* originalEffect= */ null, VibrationScaler.SCALE_NONE,
+                VibrationScaler.ADAPTIVE_SCALE_NONE);
+    }
+
     private void logVibrationStatus(int uid, VibrationAttributes attrs, Status status) {
         switch (status) {
             case IGNORED_BACKGROUND:
@@ -1766,25 +1824,35 @@
             Trace.traceBegin(TRACE_TAG_VIBRATOR, "onVibrationThreadReleased");
             try {
                 synchronized (mLock) {
-                    if (!(mCurrentSession instanceof SingleVibrationSession session)) {
-                        if (Build.IS_DEBUGGABLE) {
-                            Slog.wtf(TAG, "VibrationSession invalid on vibration thread release."
-                                    + " currentSession=" + mCurrentSession);
+                    if (mCurrentSession instanceof SingleVibrationSession session) {
+                        if (Build.IS_DEBUGGABLE && (session.getVibration().id != vibrationId)) {
+                            Slog.wtf(TAG, TextUtils.formatSimple(
+                                    "VibrationId mismatch on vibration thread release."
+                                            + " expected=%d, released=%d",
+                                    session.getVibration().id, vibrationId));
                         }
-                        // Only single vibration sessions are ended by thread being released. Abort.
-                        return;
+                        finishAppOpModeLocked(mCurrentSession.getCallerInfo());
+                        clearCurrentSessionLocked();
+                        Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+                        // Start next vibration if it's waiting for the thread.
+                        maybeStartNextSessionLocked();
+                    } else if (mCurrentSession instanceof VendorVibrationSession session) {
+                        VibrationStepConductor conductor = session.clearVibrationConductor();
+                        if (Build.IS_DEBUGGABLE) {
+                            if (conductor == null) {
+                                Slog.wtf(TAG, "Vendor session without ongoing vibration on"
+                                        + " thread release. currentSession=" + mCurrentSession);
+                            } else if (conductor.getVibration().id != vibrationId) {
+                                Slog.wtf(TAG, TextUtils.formatSimple(
+                                        "VibrationId mismatch on vibration thread release."
+                                                + " expected=%d, released=%d",
+                                        conductor.getVibration().id, vibrationId));
+                            }
+                        }
+                    } else if (Build.IS_DEBUGGABLE) {
+                        Slog.wtf(TAG, "VibrationSession invalid on vibration thread release."
+                                + " currentSession=" + mCurrentSession);
                     }
-                    if (Build.IS_DEBUGGABLE && (session.getVibration().id != vibrationId)) {
-                        Slog.wtf(TAG, TextUtils.formatSimple(
-                                "VibrationId mismatch on vibration thread release."
-                                        + " expected=%d, released=%d",
-                                session.getVibration().id, vibrationId));
-                    }
-                    finishAppOpModeLocked(mCurrentSession.getCallerInfo());
-                    clearCurrentSessionLocked();
-                    Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
-                    // Start next vibration if it's waiting for the thread.
-                    maybeStartNextSessionLocked();
                 }
             } finally {
                 Trace.traceEnd(TRACE_TAG_VIBRATOR);
@@ -1839,6 +1907,86 @@
             implements VendorVibrationSession.VibratorManagerHooks {
 
         @Override
+        public void vibrate(long sessionId, CallerInfo callerInfo, CombinedVibration effect) {
+            if (DEBUG) {
+                Slog.d(TAG, "Vibration session " + sessionId + " vibration requested");
+            }
+            Trace.traceBegin(TRACE_TAG_VIBRATOR, "sessionVibrate");
+            try {
+                synchronized (mLock) {
+                    if (!(mCurrentSession instanceof VendorVibrationSession session)) {
+                        if (Build.IS_DEBUGGABLE) {
+                            Slog.wtf(TAG, "VibrationSession invalid on session vibrate."
+                                    + " currentSession=" + mCurrentSession);
+                        }
+                        // Only vendor vibration sessions can handle this call. Abort.
+                        return;
+                    }
+                    if (session.getSessionId() != sessionId) {
+                        if (Build.IS_DEBUGGABLE) {
+                            Slog.wtf(TAG, TextUtils.formatSimple(
+                                    "SessionId mismatch on vendor vibration session vibrate."
+                                            + " expected=%d, released=%d",
+                                    session.getSessionId(), sessionId));
+                        }
+                        // Only the ongoing vendor vibration sessions can handle this call. Abort.
+                        return;
+                    }
+                    if (session.wasEndRequested()) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "session vibrate; session is ending, vibration ignored");
+                        }
+                        session.notifyVibrationAttempt(createVibrationAttemptDebugInfo(effect,
+                                callerInfo, Status.IGNORED_ERROR_SCHEDULING));
+                        return;
+                    }
+                    if (!isEffectValid(effect)) {
+                        session.notifyVibrationAttempt(createVibrationAttemptDebugInfo(effect,
+                                callerInfo, Status.IGNORED_UNSUPPORTED));
+                        return;
+                    }
+                    if (effect.getDuration() == Long.MAX_VALUE) {
+                        // Repeating effects cannot be played by the service in a session.
+                        session.notifyVibrationAttempt(createVibrationAttemptDebugInfo(effect,
+                                callerInfo, Status.IGNORED_UNSUPPORTED));
+                        return;
+                    }
+                    // Create Vibration.Stats as close to the request as possible, for tracking.
+                    HalVibration vib = new HalVibration(callerInfo, effect);
+                    vib.fillFallbacks(mVibrationSettings::getFallbackEffect);
+
+                    if (callerInfo.attrs.isFlagSet(
+                            VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
+                        // Force update of user settings before checking if this vibration effect
+                        // should be ignored or scaled.
+                        mVibrationSettings.update();
+                    }
+
+                    if (DEBUG) {
+                        Slog.d(TAG, "Starting vibrate for vibration " + vib.id
+                                + " in session " + sessionId);
+                    }
+
+                    VibrationStepConductor conductor =
+                            createSessionVibrationStepConductor(vib, session.getDeviceAdapter());
+                    if (session.maybeSetVibrationConductor(conductor)) {
+                        if (!mVibrationThread.runVibrationOnVibrationThread(conductor)) {
+                            // Shouldn't happen. The method call already logs.
+                            vib.end(new Vibration.EndInfo(Status.IGNORED_ERROR_SCHEDULING));
+                            session.clearVibrationConductor(); // Rejected by thread, clear it.
+                        }
+                    } else {
+                        // Cannot set vibration in session, log failed attempt.
+                        session.notifyVibrationAttempt(createVibrationAttemptDebugInfo(effect,
+                                callerInfo, Status.IGNORED_ERROR_SCHEDULING));
+                    }
+                }
+            } finally {
+                Trace.traceEnd(TRACE_TAG_VIBRATOR);
+            }
+        }
+
+        @Override
         public void endSession(long sessionId, boolean shouldAbort) {
             if (DEBUG) {
                 Slog.d(TAG, "Vibration session " + sessionId
@@ -1874,6 +2022,12 @@
                                         + " expected=%d, released=%d",
                                 session.getSessionId(), sessionId));
                     }
+                    // Make sure all controllers in session are reset after session ended.
+                    // This will update the vibrator state to isVibrating = false for listeners.
+                    for (int vibratorId : session.getVibratorIds()) {
+                        mVibrators.get(vibratorId).off();
+                    }
+                    finishAppOpModeLocked(mCurrentSession.getCallerInfo());
                     clearCurrentSessionLocked();
                     // Start next vibration if it's waiting for the HAL session to be over.
                     maybeStartNextSessionLocked();
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
index 1be5cef..acd34e3 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -60,6 +61,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.List;
 
 @AppModeFull
 @AppModeNonSdkSandbox
@@ -124,7 +126,8 @@
     @Test
     public void changeNonExportedComponent_sendPackageChangedBroadcastToSystem_withPermission()
             throws Exception {
-        changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */);
+        changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
+                new String[0] /* sharedPackages */);
 
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
         verify(mMockActivityManagerInternal).broadcastIntentWithCallback(
@@ -140,7 +143,8 @@
     @Test
     public void changeNonExportedComponent_sendPackageChangedBroadcastToApplicationItself()
             throws Exception {
-        changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */);
+        changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
+                new String[0] /* sharedPackages */);
 
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
         verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null),
@@ -150,9 +154,45 @@
         assertThat(intent.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
     }
 
+    @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
+    @Test
+    public void changeNonExportedComponent_sendPackageChangedBroadcastToSharedUserIdApplications()
+            throws Exception {
+        changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
+                new String[]{"shared.package"} /* sharedPackages */);
+
+        ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<String[]> captorRequiredPermissions = ArgumentCaptor.forClass(
+                String[].class);
+        verify(mMockActivityManagerInternal, times(3)).broadcastIntentWithCallback(
+                captorIntent.capture(), eq(null), captorRequiredPermissions.capture(), anyInt(),
+                eq(null), eq(null), eq(null));
+        List<Intent> intents = captorIntent.getAllValues();
+        List<String[]> requiredPermissions = captorRequiredPermissions.getAllValues();
+        assertNotNull(intents);
+        assertThat(intents.size()).isEqualTo(3);
+
+        final Intent intent1 = intents.get(0);
+        final String[] requiredPermission1 = requiredPermissions.get(0);
+        assertThat(intent1.getPackage()).isEqualTo("android");
+        assertThat(requiredPermission1).isEqualTo(
+                new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
+
+        final Intent intent2 = intents.get(1);
+        final String[] requiredPermission2 = requiredPermissions.get(1);
+        assertThat(intent2.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
+        assertThat(requiredPermission2).isNull();
+
+        final Intent intent3 = intents.get(2);
+        final String[] requiredPermission3 = requiredPermissions.get(2);
+        assertThat(intent3.getPackage()).isEqualTo("shared.package");
+        assertThat(requiredPermission3).isNull();
+    }
+
     @Test
     public void changeExportedComponent_sendPackageChangedBroadcastToAll() throws Exception {
-        changeComponentAndSendPackageChangedBroadcast(true /* changeExportedComponent */);
+        changeComponentAndSendPackageChangedBroadcast(true /* changeExportedComponent */,
+                new String[0] /* sharedPackages */);
 
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
         verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null),
@@ -162,11 +202,14 @@
         assertNull(intent.getPackage());
     }
 
-    private void changeComponentAndSendPackageChangedBroadcast(boolean changeExportedComponent) {
+    private void changeComponentAndSendPackageChangedBroadcast(boolean changeExportedComponent,
+            String[] sharedPackages) {
         when(mMockSnapshot.getPackageStateInternal(eq(PACKAGE_CHANGED_TEST_PACKAGE_NAME),
                 anyInt())).thenReturn(mMockPackageStateInternal);
         when(mMockSnapshot.isInstantAppInternal(any(), anyInt(), anyInt())).thenReturn(false);
         when(mMockSnapshot.getVisibilityAllowLists(any(), any())).thenReturn(null);
+        when(mMockSnapshot.getSharedUserPackagesForPackage(eq(PACKAGE_CHANGED_TEST_PACKAGE_NAME),
+                anyInt())).thenReturn(sharedPackages);
         when(mMockPackageStateInternal.getPkg()).thenReturn(mMockAndroidPackageInternal);
 
         when(mMockParsedActivity.getClassName()).thenReturn(
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/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index f82a860..a9569b4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -1427,6 +1427,31 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_DoOne_Service_NotPerceptible_AboveClient() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+        ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+                MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+        ProcessRecord service = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+                MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+        bindService(app, client, null, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
+        bindService(service, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
+        mProcessStateController.setRunningRemoteAnimation(client, true);
+        mProcessStateController.updateHasAboveClientLocked(app.mServices);
+        setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
+        updateOomAdj(client, app, service);
+
+        final int expectedAdj;
+        if (Flags.addModifyRawOomAdjServiceLevel()) {
+            expectedAdj = SERVICE_ADJ;
+        } else {
+            expectedAdj = CACHED_APP_MIN_ADJ;
+        }
+        assertEquals(expectedAdj, app.mState.getSetAdj());
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_DoOne_Service_NotVisible() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
@@ -2906,7 +2931,7 @@
         // Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and
         // verify that its OOM adjustment level is unaffected.
         bindService(service, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
-        app.mServices.updateHasAboveClientLocked();
+        mProcessStateController.updateHasAboveClientLocked(app.mServices);
         assertTrue(app.mServices.hasAboveClient());
 
         updateOomAdj(app);
@@ -2928,7 +2953,7 @@
         // Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and
         // verify that its OOM adjustment level is unaffected.
         bindService(app, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
-        app.mServices.updateHasAboveClientLocked();
+        mProcessStateController.updateHasAboveClientLocked(app.mServices);
         assertFalse(app.mServices.hasAboveClient());
 
         updateOomAdj(app);
@@ -2983,7 +3008,7 @@
 
         // Since sr.app is null, this service cannot be in the same process as the
         // client so we expect the BIND_ABOVE_CLIENT adjustment to take effect.
-        app.mServices.updateHasAboveClientLocked();
+        mProcessStateController.updateHasAboveClientLocked(app.mServices);
         updateOomAdj(app);
         assertTrue(app.mServices.hasAboveClient());
         assertNotEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
@@ -3306,7 +3331,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/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
index f6c644e..20ac078 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.parsing.ApkLite;
 import android.content.pm.parsing.ApkLiteParseUtils;
@@ -34,6 +35,8 @@
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.OutcomeReceiver;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -71,13 +74,17 @@
     private static final String PUSH_FILE_DIR = "/data/local/tmp/tests/smockingservicestest/pm/";
     private static final String TEST_APP_USING_SDK1_AND_SDK2 = "HelloWorldUsingSdk1And2.apk";
 
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
     @Mock private SharedLibrariesImpl mSharedLibraries;
+    @Mock private Context mContext;
+    @Mock private Computer mComputer;
     private InstallDependencyHelper mInstallDependencyHelper;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mInstallDependencyHelper = new InstallDependencyHelper(mSharedLibraries);
+        mInstallDependencyHelper = new InstallDependencyHelper(mContext, mSharedLibraries);
     }
 
     @Test
@@ -88,7 +95,8 @@
 
         PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
         CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
-        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
+                0, mHandler, callback);
         callback.assertFailure();
 
         assertThat(callback.error).hasMessageThat().contains("xyz");
@@ -104,11 +112,12 @@
                 .thenReturn(missingDependency);
 
         CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
-        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
+                0, mHandler, callback);
         callback.assertFailure();
 
         assertThat(callback.error).hasMessageThat().contains(
-                "Failed to bind to Dependency Installer");
+                "Dependency Installer Service not found");
     }
 
 
@@ -121,7 +130,8 @@
                 .thenReturn(missingDependency);
 
         CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ true);
-        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
+                0, mHandler, callback);
         callback.assertSuccess();
     }
 
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
index 89d2d28..affcfc1 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java
@@ -194,12 +194,14 @@
 
     @Test
     public void testExemptFromStoppingNullProjection() throws Exception {
-        assertThat(mStopController.isExemptFromStopping(null)).isTrue();
+        assertThat(mStopController.isExemptFromStopping(null,
+                MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
     }
 
     @Test
     public void testExemptFromStoppingInvalidProjection() throws Exception {
-        assertThat(mStopController.isExemptFromStopping(createMediaProjection(null))).isTrue();
+        assertThat(mStopController.isExemptFromStopping(createMediaProjection(null),
+                MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
     }
 
     @Test
@@ -213,7 +215,8 @@
             Settings.Global.putInt(mContext.getContentResolver(),
                     DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 1);
 
-            assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+            assertThat(mStopController.isExemptFromStopping(mediaProjection,
+                    MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
         } finally {
             Settings.Global.putInt(mContext.getContentResolver(),
                     DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, value);
@@ -230,7 +233,8 @@
                         eq(mediaProjection.uid), eq(mediaProjection.packageName),
                         nullable(String.class),
                         nullable(String.class));
-        assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+        assertThat(mStopController.isExemptFromStopping(mediaProjection,
+                MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
     }
 
     @Test
@@ -244,7 +248,8 @@
                         doReturn(PackageManager.PERMISSION_DENIED).when(
                                 mPackageManager).checkPermission(
                                 RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
-                        assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+                        assertThat(mStopController.isExemptFromStopping(mediaProjection,
+                                MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
                     } catch (Exception e) {
                         throw new RuntimeException(e);
                     }
@@ -261,7 +266,8 @@
                 packages.valueAt(0));
         doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
                 RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
-        assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+        assertThat(mStopController.isExemptFromStopping(mediaProjection,
+                MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
     }
 
     @Test
@@ -270,7 +276,8 @@
                 PACKAGE_NAME);
         doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
                 RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
-        assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+        assertThat(mStopController.isExemptFromStopping(mediaProjection,
+                MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
     }
 
     @Test
@@ -278,7 +285,8 @@
         MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
         doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
                 RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
-        assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+        assertThat(mStopController.isExemptFromStopping(mediaProjection,
+                MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
     }
 
     @Test
@@ -287,7 +295,8 @@
         mediaProjection.notifyVirtualDisplayCreated(1);
         doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
                 RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
-        assertThat(mStopController.isExemptFromStopping(mediaProjection)).isFalse();
+        assertThat(mStopController.isExemptFromStopping(mediaProjection,
+                MediaProjectionStopController.STOP_REASON_UNKNOWN)).isFalse();
     }
 
     @Test
@@ -368,6 +377,36 @@
         verify(mStopConsumer).accept(MediaProjectionStopController.STOP_REASON_CALL_END);
     }
 
+    @Test
+    @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+    public void testExemptFromStopping_callEnd_callBeforeMediaProjection() throws Exception {
+        when(mTelecomManager.isInCall()).thenReturn(true);
+        mStopController.callStateChanged();
+
+        MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+        mediaProjection.notifyVirtualDisplayCreated(1);
+        doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+                RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+
+        assertThat(mStopController.isExemptFromStopping(mediaProjection,
+                MediaProjectionStopController.STOP_REASON_CALL_END)).isFalse();
+    }
+
+    @Test
+    @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+    public void testExemptFromStopping_callEnd_callAfterMediaProjection() throws Exception {
+        MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+        mediaProjection.notifyVirtualDisplayCreated(1);
+        doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+                RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+
+        when(mTelecomManager.isInCall()).thenReturn(true);
+        mStopController.callStateChanged();
+
+        assertThat(mStopController.isExemptFromStopping(mediaProjection,
+                MediaProjectionStopController.STOP_REASON_CALL_END)).isTrue();
+    }
+
     private MediaProjectionManagerService.MediaProjection createMediaProjection()
             throws NameNotFoundException {
         return createMediaProjection(PACKAGE_NAME);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 3c2f961..eb44daa 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -747,6 +747,23 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+    public void vibrate_singleVibratorComposedAndNoCapability_triggersHalAndReturnsUnsupported() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+                .compose();
+        HalVibration vibration = startThreadAndDispatcher(effect);
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
+        verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
+        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
     public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() {
         VibrationEffect effect = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
@@ -887,7 +904,7 @@
         fakeVibrator.setMaxEnvelopeEffectSize(10);
         fakeVibrator.setMinEnvelopeEffectControlPointDurationMillis(20);
 
-        VibrationEffect effect = VibrationEffect.startWaveformEnvelope()
+        VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
                 .addControlPoint(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
                 .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30)
                 .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20)
@@ -922,12 +939,14 @@
         fakeVibrator.setMaxEnvelopeEffectSize(10);
         fakeVibrator.setMinEnvelopeEffectControlPointDurationMillis(20);
 
-        VibrationEffect effect = VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 30)
+        VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
+                .setInitialFrequencyHz(/*initialFrequencyHz=*/ 30)
                 .addControlPoint(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
                 .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30)
                 .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20)
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
                 .build();
+
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
 
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 5f76d68..ec83e99 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -2865,7 +2865,7 @@
         mTestLooper.dispatchAll();
 
         assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
-        verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 3}));
+        verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
         verify(callback, never()).onStarted(any(IVibrationSession.class));
         verify(callback, never()).onFinishing();
         verify(callback)
@@ -2889,6 +2889,7 @@
         verify(callback).onStarted(captor.capture());
 
         captor.getValue().finishSession();
+        mTestLooper.dispatchAll();
 
         // Session not ended until HAL callback.
         assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
@@ -3139,6 +3140,224 @@
     }
 
     @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void vibrateInSession_afterCancel_vibrationIgnored() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1, 2);
+        FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+        fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+        int sessionFinishDelayMs = 200;
+        IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+        mTestLooper.dispatchAll();
+
+        verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+        ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+        verify(callback).onStarted(captor.capture());
+
+        IVibrationSession startedSession = captor.getValue();
+        startedSession.cancelSession();
+        startedSession.vibrate(
+                CombinedVibration.createParallel(VibrationEffect.createOneShot(10, 255)),
+                "reason");
+
+        // VibrationThread will never start this vibration.
+        assertFalse(waitUntil(s -> !fakeVibrator1.getAmplitudes().isEmpty(), service,
+                TEST_TIMEOUT_MILLIS));
+
+        // Dispatch HAL callbacks.
+        mTestLooper.moveTimeForward(sessionFinishDelayMs);
+        mTestLooper.dispatchAll();
+
+        assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER);
+        verify(callback).onFinishing();
+        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void vibrateInSession_afterFinish_vibrationIgnored() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1, 2);
+        FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+        fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+        int sessionFinishDelayMs = 200;
+        IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+        mTestLooper.dispatchAll();
+
+        verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+        ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+        verify(callback).onStarted(captor.capture());
+
+        IVibrationSession startedSession = captor.getValue();
+        startedSession.finishSession();
+        mTestLooper.dispatchAll();
+
+        startedSession.vibrate(
+                CombinedVibration.createParallel(VibrationEffect.createOneShot(10, 255)),
+                "reason");
+
+        // Session not ended until HAL callback.
+        assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+
+        // VibrationThread will never start this vibration.
+        assertFalse(waitUntil(s -> !fakeVibrator1.getAmplitudes().isEmpty(), service,
+                TEST_TIMEOUT_MILLIS));
+
+        // Dispatch HAL callbacks.
+        mTestLooper.moveTimeForward(sessionFinishDelayMs);
+        mTestLooper.dispatchAll();
+
+        assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+        verify(callback).onFinishing();
+        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void vibrateInSession_repeatingVibration_vibrationIgnored() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1, 2);
+        FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+        fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+        int sessionFinishDelayMs = 200;
+        IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+        mTestLooper.dispatchAll();
+
+        verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+        ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+        verify(callback).onStarted(captor.capture());
+
+        IVibrationSession startedSession = captor.getValue();
+        startedSession.vibrate(
+                CombinedVibration.createParallel(
+                        VibrationEffect.createWaveform(new long[]{ 10, 10, 10, 10}, 0)),
+                "reason");
+
+        // VibrationThread will never start this vibration.
+        assertFalse(waitUntil(s -> !fakeVibrator1.getAmplitudes().isEmpty(), service,
+                TEST_TIMEOUT_MILLIS));
+
+        startedSession.finishSession();
+        mTestLooper.dispatchAll();
+
+        // Dispatch HAL callbacks.
+        mTestLooper.moveTimeForward(sessionFinishDelayMs);
+        mTestLooper.dispatchAll();
+
+        assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+        assertThat(service.isVibrating(1)).isFalse();
+        assertThat(service.isVibrating(2)).isFalse();
+        verify(callback).onFinishing();
+        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void vibrateInSession_singleVibration_playsAllVibrateCommands() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1, 2);
+        FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+        fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        FakeVibratorControllerProvider fakeVibrator2 = mVibratorProviders.get(1);
+        fakeVibrator2.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+        int sessionFinishDelayMs = 200;
+        IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+        mTestLooper.dispatchAll();
+
+        verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+        ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+        verify(callback).onStarted(captor.capture());
+
+        IVibrationSession startedSession = captor.getValue();
+        startedSession.vibrate(
+                CombinedVibration.createParallel(
+                        VibrationEffect.createWaveform(new long[]{ 10, 10, 10, 10}, -1)),
+                "reason");
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        // Vibrators will receive 2 requests for the waveform playback
+        assertTrue(waitUntil(s -> fakeVibrator1.getAmplitudes().size() == 2, service,
+                TEST_TIMEOUT_MILLIS));
+        assertTrue(waitUntil(s -> fakeVibrator2.getAmplitudes().size() == 2, service,
+                TEST_TIMEOUT_MILLIS));
+
+        startedSession.finishSession();
+        mTestLooper.dispatchAll();
+
+        // Dispatch HAL callbacks.
+        mTestLooper.moveTimeForward(sessionFinishDelayMs);
+        mTestLooper.dispatchAll();
+
+        assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+        assertThat(service.isVibrating(1)).isFalse();
+        assertThat(service.isVibrating(2)).isFalse();
+        verify(callback).onFinishing();
+        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void vibrateInSession_multipleVibrations_playsAllVibrations() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1, 2);
+        FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+        fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+        int sessionFinishDelayMs = 200;
+        IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+        mTestLooper.dispatchAll();
+
+        verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+        ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+        verify(callback).onStarted(captor.capture());
+
+        IVibrationSession startedSession = captor.getValue();
+        startedSession.vibrate(
+                CombinedVibration.createParallel(VibrationEffect.createOneShot(10, 255)),
+                "reason");
+
+        // VibrationThread will start this vibration async, so wait until vibration is completed.
+        assertTrue(waitUntil(s -> fakeVibrator1.getAmplitudes().size() == 1, service,
+                TEST_TIMEOUT_MILLIS));
+        assertTrue(waitUntil(s -> !session.getVibrations().isEmpty(), service,
+                TEST_TIMEOUT_MILLIS));
+
+        startedSession.vibrate(
+                CombinedVibration.createParallel(VibrationEffect.createOneShot(20, 255)),
+                "reason");
+
+        assertTrue(waitUntil(s -> fakeVibrator1.getAmplitudes().size() == 2, service,
+                TEST_TIMEOUT_MILLIS));
+
+        startedSession.finishSession();
+        mTestLooper.dispatchAll();
+
+        // Dispatch HAL callbacks.
+        mTestLooper.moveTimeForward(sessionFinishDelayMs);
+        mTestLooper.dispatchAll();
+
+        assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+        assertThat(service.isVibrating(1)).isFalse();
+        assertThat(service.isVibrating(2)).isFalse();
+        verify(callback).onFinishing();
+        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+    }
+
+    @Test
     public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception {
         mockVibrators(1);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index f68ae2c..74a907f 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -309,7 +309,7 @@
      */
     @TestApi
     @NonNull
-    @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api")
+    @SuppressLint("UnflaggedApi") // Exempt: Test API for already shipped feature
     public BroadcastReceiver getBroadcastReceiver() {
         return mBroadcastReceiver;
     }