Merge "Prevent system restart while updating package during boot" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 29349cb..9a417e9 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -19,11 +19,14 @@
     // Add java_aconfig_libraries to here to add them to the core framework
     srcs: [
         ":android.os.flags-aconfig-java{.generated_srcjars}",
+        ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
         ":android.security.flags-aconfig-java{.generated_srcjars}",
         ":camera_platform_flags_core_java_lib{.generated_srcjars}",
         ":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
         ":com.android.text.flags-aconfig-java{.generated_srcjars}",
+        ":telecom_flags_core_java_lib{.generated_srcjars}",
         ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
+        ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
     ],
     // Add aconfig-annotations-lib as a dependency for the optimization
     libs: ["aconfig-annotations-lib"],
@@ -44,6 +47,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Telecom
+java_aconfig_library {
+    name: "telecom_flags_core_java_lib",
+    aconfig_declarations: "telecom_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Window
 aconfig_declarations {
     name: "com.android.window.flags.window-aconfig",
@@ -70,6 +80,11 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+cc_aconfig_library {
+    name: "aconfig_text_flags_c_lib",
+    aconfig_declarations: "com.android.text.flags-aconfig",
+}
+
 // Security
 aconfig_declarations {
     name: "android.security.flags-aconfig",
@@ -116,3 +131,29 @@
     package: "android.companion.virtual.flags",
     srcs: ["core/java/android/companion/virtual/*.aconfig"],
 }
+
+// InputMethod
+aconfig_declarations {
+    name: "android.view.inputmethod.flags-aconfig",
+    package: "android.view.inputmethod",
+    srcs: ["core/java/android/view/inputmethod/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.view.inputmethod.flags-aconfig-java",
+    aconfig_declarations: "android.view.inputmethod.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Vibrator
+aconfig_declarations {
+    name: "android.os.vibrator.flags-aconfig",
+    package: "android.os.vibrator",
+    srcs: ["core/java/android/os/vibrator/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.os.vibrator.flags-aconfig-java",
+    aconfig_declarations: "android.os.vibrator.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 17076bc..66c1efc 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -419,6 +419,14 @@
      */
     public static final int REASON_SYSTEM_EXEMPT_APP_OP = 327;
 
+    /**
+     * Granted by {@link com.android.server.pm.PackageArchiverService} to the installer responsible
+     * for unarchiving an app.
+     *
+     * @hide
+     */
+    public static final int REASON_PACKAGE_UNARCHIVE = 328;
+
     /** @hide The app requests out-out. */
     public static final int REASON_OPT_OUT_REQUESTED = 1000;
 
@@ -502,6 +510,7 @@
             REASON_ACTIVE_DEVICE_ADMIN,
             REASON_MEDIA_NOTIFICATION_TRANSFER,
             REASON_PACKAGE_INSTALLER,
+            REASON_PACKAGE_UNARCHIVE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ReasonCode {}
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 1be07fd..b8596d5 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -538,7 +538,7 @@
 
     /**
      * Package names the system has white-listed to opt out of power save restrictions,
-     * except for device idle mode.
+     * except for device idle modes (light and full doze).
      */
     private final ArrayMap<String, Integer> mPowerSaveWhitelistAppsExceptIdle = new ArrayMap<>();
 
diff --git a/core/api/current.txt b/core/api/current.txt
index 4d93e15..df41b1f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -42273,7 +42273,7 @@
     field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10
     field public static final int PROPERTY_IS_ADHOC_CONFERENCE = 8192; // 0x2000
     field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40
-    field public static final int PROPERTY_IS_TRANSACTIONAL = 32768; // 0x8000
+    field @FlaggedApi(Flags.FLAG_VOIP_APP_ACTIONS_SUPPORT) public static final int PROPERTY_IS_TRANSACTIONAL = 32768; // 0x8000
     field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 2048; // 0x800
     field public static final int PROPERTY_RTT = 1024; // 0x400
     field public static final int PROPERTY_SELF_MANAGED = 256; // 0x100
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index adf1da9..0c61981 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3213,6 +3213,7 @@
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
     method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
     method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
+    method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
   }
@@ -3546,6 +3547,7 @@
     field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
     field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
     field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
+    field public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
     field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
     field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";
     field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
@@ -3795,6 +3797,13 @@
 
   public class PackageArchiver {
     method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+    field public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
+    field public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
+  }
+
+  public class PackageInfo implements android.os.Parcelable {
+    field public boolean isArchived;
   }
 
   public class PackageInstaller {
@@ -4016,6 +4025,7 @@
     field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
     field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
     field public static final int MATCH_ANY_USER = 4194304; // 0x400000
+    field public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L
     field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000
     field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
     field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000
@@ -7281,8 +7291,11 @@
 
 package android.media.audiopolicy {
 
-  public class AudioMix {
+  public class AudioMix implements android.os.Parcelable {
+    method public int describeContents();
     method public int getMixState();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.audiopolicy.AudioMix> CREATOR;
     field public static final int MIX_STATE_DISABLED = -1; // 0xffffffff
     field public static final int MIX_STATE_IDLE = 0; // 0x0
     field public static final int MIX_STATE_MIXING = 1; // 0x1
@@ -7291,15 +7304,18 @@
   }
 
   public static class AudioMix.Builder {
-    ctor public AudioMix.Builder(android.media.audiopolicy.AudioMixingRule) throws java.lang.IllegalArgumentException;
+    ctor public AudioMix.Builder(@NonNull android.media.audiopolicy.AudioMixingRule) throws java.lang.IllegalArgumentException;
     method public android.media.audiopolicy.AudioMix build() throws java.lang.IllegalArgumentException;
     method public android.media.audiopolicy.AudioMix.Builder setDevice(@NonNull android.media.AudioDeviceInfo) throws java.lang.IllegalArgumentException;
-    method public android.media.audiopolicy.AudioMix.Builder setFormat(android.media.AudioFormat) throws java.lang.IllegalArgumentException;
+    method public android.media.audiopolicy.AudioMix.Builder setFormat(@NonNull android.media.AudioFormat) throws java.lang.IllegalArgumentException;
     method public android.media.audiopolicy.AudioMix.Builder setRouteFlags(int) throws java.lang.IllegalArgumentException;
   }
 
-  public class AudioMixingRule {
+  public class AudioMixingRule implements android.os.Parcelable {
+    method public int describeContents();
     method public int getTargetMixRole();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.audiopolicy.AudioMixingRule> CREATOR;
     field public static final int MIX_ROLE_INJECTOR = 1; // 0x1
     field public static final int MIX_ROLE_PLAYERS = 0; // 0x0
     field public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 2; // 0x2
@@ -7336,6 +7352,7 @@
     method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
     method public boolean setUserIdDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
     method public String toLogFriendlyString();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules(@NonNull java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>);
     field public static final int FOCUS_POLICY_DUCKING_DEFAULT = 0; // 0x0
     field public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; // 0x0
     field public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1; // 0x1
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 56fef1a..dab4110 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1486,13 +1486,13 @@
             AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
 
     /**
-     * Allows the assistant app to get the negative trigger data from the PCC sandbox to improve the
+     * Allows the assistant app to get the training data from the PCC sandbox to improve the
      * hotword training model.
      *
      * @hide
      */
-    public static final int OP_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO =
-            AppProtoEnums.APP_OP_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO;
+    public static final int OP_RECEIVE_SANDBOX_TRAINING_DATA =
+            AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRAINING_DATA;
 
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1640,7 +1640,7 @@
             OPSTR_CAMERA_SANDBOXED,
             OPSTR_RECORD_AUDIO_SANDBOXED,
             OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
-            OPSTR_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO
+            OPSTR_RECEIVE_SANDBOX_TRAINING_DATA
     })
     public @interface AppOpString {}
 
@@ -2261,13 +2261,13 @@
             "android:receive_sandbox_trigger_audio";
 
     /**
-     * Allows the assistant app to get the negative trigger data from the PCC sandbox to improve
+     * Allows the assistant app to get the training data from the PCC sandbox to improve
      * the hotword training model.
      *
      * @hide
      */
-    public static final String OPSTR_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO =
-            "android:receive_sandbox_negative_data_audio";
+    public static final String OPSTR_RECEIVE_SANDBOX_TRAINING_DATA =
+            "android:receive_sandbox_training_data";
 
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
@@ -2811,9 +2811,9 @@
                 OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
                 "RECEIVE_SANDBOX_TRIGGER_AUDIO")
                 .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
-        new AppOpInfo.Builder(OP_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO,
-                OPSTR_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO,
-                "RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO").build()
+        new AppOpInfo.Builder(OP_RECEIVE_SANDBOX_TRAINING_DATA,
+                OPSTR_RECEIVE_SANDBOX_TRAINING_DATA,
+                "RECEIVE_SANDBOX_TRAINING_DATA").build()
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index d66fca8..ed0f872 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -2495,7 +2495,8 @@
                     + ", hints=" + Arrays.toString(node.getAutofillHints())
                     + ", value=" + node.getAutofillValue()
                     + ", sanitized=" + node.isSanitized()
-                    + ", important=" + node.getImportantForAutofill());
+                    + ", important=" + node.getImportantForAutofill()
+                    + ", visibility=" + node.getVisibility());
         }
 
         final int NCHILDREN = node.getChildCount();
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 4801d15..be699f4 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -70,6 +70,12 @@
     void close();
 
     /**
+     * Specifies a policy for this virtual device.
+     */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+    void setDevicePolicy(int policyType, int devicePolicy);
+
+    /**
      * Notifies that an audio session being started.
      */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index d13bfd4..2e5c0f7 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -238,6 +238,15 @@
         }
     }
 
+    void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
+            @VirtualDeviceParams.DevicePolicy int devicePolicy) {
+        try {
+            mVirtualDevice.setDevicePolicy(policyType, devicePolicy);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     @NonNull
     VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
         try {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index af41370..923e689 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -19,6 +19,7 @@
 import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -59,6 +60,8 @@
 import android.util.Log;
 import android.view.Surface;
 
+import com.android.internal.util.AnnotationValidations;
+
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -513,6 +516,28 @@
         }
 
         /**
+         * Specifies a policy for this virtual device.
+         *
+         * <p>Policies define the system behavior that may be specific for this virtual device. The
+         * given policy must be able to be changed dynamically during the lifetime of the device.
+         *
+         * @param policyType the type of policy, i.e. which behavior to specify a policy for.
+         * @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
+         *
+         * @see VirtualDeviceParams#POLICY_TYPE_RECENTS
+         */
+        @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
+                @VirtualDeviceParams.DevicePolicy int devicePolicy) {
+            AnnotationValidations.validate(
+                    VirtualDeviceParams.DynamicPolicyType.class, null, policyType);
+            AnnotationValidations.validate(
+                    VirtualDeviceParams.DevicePolicy.class, null, devicePolicy);
+            mVirtualDeviceInternal.setDevicePolicy(policyType, devicePolicy);
+        }
+
+        /**
          * Creates a virtual dpad.
          *
          * @param config the configurations of the virtual dpad.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index b6d8375..037e814a 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -150,6 +150,17 @@
     public @interface PolicyType {}
 
     /**
+     * Policy types that can be dynamically changed during the virtual device's lifetime.
+     *
+     * @see VirtualDeviceManager.VirtualDevice#setDevicePolicy
+     * @hide
+     */
+    @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_RECENTS})
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+    public @interface DynamicPolicyType {}
+
+    /**
      * Tells the sensor framework how to handle sensor requests from contexts associated with this
      * virtual device, namely the sensors returned by
      * {@link android.hardware.SensorManager#getSensorList}:
@@ -375,6 +386,14 @@
     }
 
     /**
+     * Returns all device policies.
+     * @hide
+     */
+    public @NonNull SparseIntArray getDevicePolicies() {
+        return mDevicePolicies;
+    }
+
+    /**
      * Returns the configurations for all sensors that should be created for this device.
      *
      * @see Builder#addVirtualSensorConfig
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index 39b99c6..057b856 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -6,3 +6,10 @@
   description: "More logs to test flags with"
   bug: "291725823"
 }
+
+flag {
+  name: "dynamic_policy"
+  namespace: "virtual_devices"
+  description: "Enable dynamic policy API"
+  bug: "298401780"
+}
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 8f35ca2..15678a7 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -226,6 +226,11 @@
     }
 
     /** @hide */
+    public AttributionSource withDefaultToken() {
+        return withToken(sDefaultToken);
+    }
+
+    /** @hide */
     public AttributionSource withPid(int pid) {
         return new AttributionSource(getUid(), pid, getPackageName(), getAttributionTag(),
                 getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());
@@ -552,16 +557,28 @@
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         AttributionSource that = (AttributionSource) o;
-        return mAttributionSourceState.uid == that.mAttributionSourceState.uid
+        return equalsExceptToken(that) && Objects.equals(
+                mAttributionSourceState.token, that.mAttributionSourceState.token);
+    }
+
+    /**
+     * We store trusted attribution sources without their token (the token is the key to the map)
+     * to avoid having a strong reference to the token. This means, when checking the equality of a
+     * supplied AttributionSource in PermissionManagerService.isTrustedAttributionSource, we want to
+     * compare everything except the token.
+     *
+     * @hide
+     */
+    public boolean equalsExceptToken(@Nullable AttributionSource o) {
+        if (o == null) return false;
+        return mAttributionSourceState.uid == o.mAttributionSourceState.uid
                 && Objects.equals(mAttributionSourceState.packageName,
-                        that.mAttributionSourceState.packageName)
+                o.mAttributionSourceState.packageName)
                 && Objects.equals(mAttributionSourceState.attributionTag,
-                        that.mAttributionSourceState.attributionTag)
-                && Objects.equals(mAttributionSourceState.token,
-                        that.mAttributionSourceState.token)
+                o.mAttributionSourceState.attributionTag)
                 && Arrays.equals(mAttributionSourceState.renouncedPermissions,
-                        that.mAttributionSourceState.renouncedPermissions)
-                && Objects.equals(getNext(), that.getNext());
+                o.mAttributionSourceState.renouncedPermissions)
+                && Objects.equals(getNext(), o.getNext());
     }
 
     @Override
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 31f6418..fe7d1e6 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5270,6 +5270,16 @@
     public static final String ACTION_SHOW_FOREGROUND_SERVICE_MANAGER =
             "android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER";
 
+    /**
+     * Broadcast Action: Sent to the responsible installer of an archived package when unarchival
+     * is requested.
+     *
+     * @see android.content.pm.PackageArchiver
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Standard intent categories (see addCategory()).
diff --git a/core/java/android/content/pm/IPackageArchiverService.aidl b/core/java/android/content/pm/IPackageArchiverService.aidl
index fc471c4..dc6491d 100644
--- a/core/java/android/content/pm/IPackageArchiverService.aidl
+++ b/core/java/android/content/pm/IPackageArchiverService.aidl
@@ -23,4 +23,7 @@
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})")
     void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
+    void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle);
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/PackageArchiver.java b/core/java/android/content/pm/PackageArchiver.java
index d739d50..b065231 100644
--- a/core/java/android/content/pm/PackageArchiver.java
+++ b/core/java/android/content/pm/PackageArchiver.java
@@ -42,6 +42,26 @@
 @SystemApi
 public class PackageArchiver {
 
+    /**
+     * Extra field for the package name of a package that is requested to be unarchived. Sent as
+     * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_UNARCHIVE_PACKAGE_NAME =
+            "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
+
+    /**
+     * If true, the requestor of the unarchival has specified that the app should be unarchived
+     * for {@link android.os.UserHandle#ALL}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_UNARCHIVE_ALL_USERS =
+            "android.content.pm.extra.UNARCHIVE_ALL_USERS";
+
     private final Context mContext;
     private final IPackageArchiverService mService;
 
@@ -58,7 +78,7 @@
      *
      * @param statusReceiver Callback used to notify when the operation is completed.
      * @throws NameNotFoundException If {@code packageName} isn't found or not available to the
-     *                               caller.
+     *                               caller or isn't archived.
      * @hide
      */
     @RequiresPermission(anyOf = {
@@ -76,4 +96,34 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Requests to unarchive a currently archived package.
+     *
+     * <p> Sends a request to unarchive an app to the responsible installer. The installer is
+     * determined by {@link InstallSourceInfo#getUpdateOwnerPackageName()}, or
+     * {@link InstallSourceInfo#getInstallingPackageName()} if the former value is null.
+     *
+     * <p> The installation will happen asynchronously and can be observed through
+     * {@link android.content.Intent#ACTION_PACKAGE_ADDED}.
+     *
+     * @throws NameNotFoundException If {@code packageName} isn't found or not visible to the
+     *                               caller or if the package has no installer on the device
+     *                               anymore to unarchive it.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.INSTALL_PACKAGES,
+            Manifest.permission.REQUEST_INSTALL_PACKAGES})
+    @SystemApi
+    public void requestUnarchive(@NonNull String packageName)
+            throws NameNotFoundException {
+        try {
+            mService.requestUnarchive(packageName, mContext.getPackageName(), mContext.getUser());
+        } catch (ParcelableException e) {
+            e.maybeRethrow(NameNotFoundException.class);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 63c11b7..cdb8b46 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
@@ -488,6 +489,17 @@
      */
     public boolean isActiveApex;
 
+    /**
+     * Whether the package is currently in an archived state.
+     *
+     * <p>Packages can be archived through {@link PackageArchiver} and do not have any APKs stored
+     * on the device, but do keep the data directory.
+     * @hide
+     */
+    // TODO(b/278553670) Unhide and update @links before launch.
+    @SystemApi
+    public boolean isArchived;
+
     public PackageInfo() {
     }
 
@@ -575,6 +587,7 @@
         }
         dest.writeBoolean(isApex);
         dest.writeBoolean(isActiveApex);
+        dest.writeBoolean(isArchived);
         dest.restoreAllowSquashing(prevAllowSquashing);
     }
 
@@ -640,5 +653,6 @@
         }
         isApex = source.readBoolean();
         isActiveApex = source.readBoolean();
+        isArchived = source.readBoolean();
     }
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c384389..9a53a2a6 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -787,6 +787,7 @@
             MATCH_DEBUG_TRIAGED_MISSING,
             MATCH_INSTANT,
             MATCH_APEX,
+            MATCH_ARCHIVED_PACKAGES,
             GET_DISABLED_COMPONENTS,
             GET_DISABLED_UNTIL_USED_COMPONENTS,
             GET_UNINSTALLED_PACKAGES,
@@ -811,6 +812,7 @@
             GET_UNINSTALLED_PACKAGES,
             MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
             MATCH_APEX,
+            MATCH_ARCHIVED_PACKAGES,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ApplicationInfoFlagsBits {}
@@ -1235,6 +1237,21 @@
     public static final long GET_ATTRIBUTIONS_LONG = 0x80000000L;
 
     /**
+     * Flag parameter to also retrieve some information about archived packages.
+     * Packages can be archived through {@link PackageArchiver} and do not have any APKs stored on
+     * the device, but do keep the data directory.
+     * <p> Note: Archived apps are a subset of apps returned by {@link #MATCH_UNINSTALLED_PACKAGES}.
+     * <p> Note: this flag may cause less information about currently installed
+     * applications to be returned.
+     * <p> Note: use of this flag requires the android.permission.QUERY_ALL_PACKAGES
+     * permission to see uninstalled packages.
+     * @hide
+     */
+    // TODO(b/278553670) Unhide and update @links before launch.
+    @SystemApi
+    public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32;
+
+    /**
      * @hide
      */
     public static final long FILTER_OUT_QUARANTINED_COMPONENTS = 0x100000000L;
diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java
index 7a153ef..c5f5614 100644
--- a/core/java/android/os/AggregateBatteryConsumer.java
+++ b/core/java/android/os/AggregateBatteryConsumer.java
@@ -116,8 +116,9 @@
      * Builder for DeviceBatteryConsumer.
      */
     public static final class Builder extends BaseBuilder<AggregateBatteryConsumer.Builder> {
-        public Builder(BatteryConsumer.BatteryConsumerData data, int scope) {
-            super(data, CONSUMER_TYPE_AGGREGATE);
+        public Builder(BatteryConsumer.BatteryConsumerData data, int scope,
+                double minConsumedPowerThreshold) {
+            super(data, CONSUMER_TYPE_AGGREGATE, minConsumedPowerThreshold);
             data.putInt(COLUMN_INDEX_SCOPE, scope);
         }
 
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 0ba8d51..ca84b35 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -795,11 +795,12 @@
         protected final BatteryConsumer.BatteryConsumerData mData;
         protected final PowerComponents.Builder mPowerComponentsBuilder;
 
-        public BaseBuilder(BatteryConsumer.BatteryConsumerData data, int consumerType) {
+        public BaseBuilder(BatteryConsumer.BatteryConsumerData data, int consumerType,
+                double minConsumedPowerThreshold) {
             mData = data;
             data.putLong(COLUMN_INDEX_BATTERY_CONSUMER_TYPE, consumerType);
 
-            mPowerComponentsBuilder = new PowerComponents.Builder(data);
+            mPowerComponentsBuilder = new PowerComponents.Builder(data, minConsumedPowerThreshold);
         }
 
         @Nullable
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 7586bf7..a5f8844 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -707,7 +707,7 @@
                         XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA, false);
 
                 builder = new Builder(customComponentNames.toArray(new String[0]), true,
-                        includesProcStateData);
+                        includesProcStateData, 0);
 
                 builder.setStatsStartTimestamp(
                         parser.getAttributeLong(null, XML_ATTR_START_TIMESTAMP));
@@ -782,6 +782,7 @@
         private final String[] mCustomPowerComponentNames;
         private final boolean mIncludePowerModels;
         private final boolean mIncludesProcessStateData;
+        private final double mMinConsumedPowerThreshold;
         private final BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout;
         private long mStatsStartTimestampMs;
         private long mStatsEndTimestampMs;
@@ -802,11 +803,11 @@
         private BatteryStatsHistory mBatteryStatsHistory;
 
         public Builder(@NonNull String[] customPowerComponentNames) {
-            this(customPowerComponentNames, false, false);
+            this(customPowerComponentNames, false, false, 0);
         }
 
         public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels,
-                boolean includeProcessStateData) {
+                boolean includeProcessStateData, double minConsumedPowerThreshold) {
             mBatteryConsumersCursorWindow =
                     new CursorWindow(null, BATTERY_CONSUMER_CURSOR_WINDOW_SIZE);
             mBatteryConsumerDataLayout =
@@ -817,12 +818,14 @@
             mCustomPowerComponentNames = customPowerComponentNames;
             mIncludePowerModels = includePowerModels;
             mIncludesProcessStateData = includeProcessStateData;
+            mMinConsumedPowerThreshold = minConsumedPowerThreshold;
             for (int scope = 0; scope < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; scope++) {
                 final BatteryConsumer.BatteryConsumerData data =
                         BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
                                 mBatteryConsumerDataLayout);
                 mAggregateBatteryConsumersBuilders[scope] =
-                        new AggregateBatteryConsumer.Builder(data, scope);
+                        new AggregateBatteryConsumer.Builder(
+                                data, scope, mMinConsumedPowerThreshold);
             }
         }
 
@@ -961,7 +964,8 @@
                 final BatteryConsumer.BatteryConsumerData data =
                         BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
                                 mBatteryConsumerDataLayout);
-                builder = new UidBatteryConsumer.Builder(data, batteryStatsUid);
+                builder = new UidBatteryConsumer.Builder(data, batteryStatsUid,
+                        mMinConsumedPowerThreshold);
                 mUidBatteryConsumerBuilders.put(uid, builder);
             }
             return builder;
@@ -979,7 +983,7 @@
                 final BatteryConsumer.BatteryConsumerData data =
                         BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
                                 mBatteryConsumerDataLayout);
-                builder = new UidBatteryConsumer.Builder(data, uid);
+                builder = new UidBatteryConsumer.Builder(data, uid, mMinConsumedPowerThreshold);
                 mUidBatteryConsumerBuilders.put(uid, builder);
             }
             return builder;
@@ -996,7 +1000,7 @@
                 final BatteryConsumer.BatteryConsumerData data =
                         BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
                                 mBatteryConsumerDataLayout);
-                builder = new UserBatteryConsumer.Builder(data, userId);
+                builder = new UserBatteryConsumer.Builder(data, userId, mMinConsumedPowerThreshold);
                 mUserBatteryConsumerBuilders.put(userId, builder);
             }
             return builder;
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index b3f4d98..49d7e8b 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -80,6 +80,7 @@
     private final long mMaxStatsAgeMs;
     private final long mFromTimestamp;
     private final long mToTimestamp;
+    private final double mMinConsumedPowerThreshold;
     private final @BatteryConsumer.PowerComponent int[] mPowerComponents;
 
     private BatteryUsageStatsQuery(@NonNull Builder builder) {
@@ -87,6 +88,7 @@
         mUserIds = builder.mUserIds != null ? builder.mUserIds.toArray()
                 : new int[]{UserHandle.USER_ALL};
         mMaxStatsAgeMs = builder.mMaxStatsAgeMs;
+        mMinConsumedPowerThreshold = builder.mMinConsumedPowerThreshold;
         mFromTimestamp = builder.mFromTimestamp;
         mToTimestamp = builder.mToTimestamp;
         mPowerComponents = builder.mPowerComponents;
@@ -137,6 +139,14 @@
     }
 
     /**
+     * Returns the minimal power component consumed power threshold. The small power consuming
+     * components will be reported as zero.
+     */
+    public double getMinConsumedPowerThreshold() {
+        return mMinConsumedPowerThreshold;
+    }
+
+    /**
      * Returns the exclusive lower bound of the stored snapshot timestamps that should be included
      * in the aggregation.  Ignored if {@link #getToTimestamp()} is zero.
      */
@@ -158,6 +168,7 @@
         mUserIds = new int[in.readInt()];
         in.readIntArray(mUserIds);
         mMaxStatsAgeMs = in.readLong();
+        mMinConsumedPowerThreshold = in.readDouble();
         mFromTimestamp = in.readLong();
         mToTimestamp = in.readLong();
         mPowerComponents = in.createIntArray();
@@ -169,6 +180,7 @@
         dest.writeInt(mUserIds.length);
         dest.writeIntArray(mUserIds);
         dest.writeLong(mMaxStatsAgeMs);
+        dest.writeDouble(mMinConsumedPowerThreshold);
         dest.writeLong(mFromTimestamp);
         dest.writeLong(mToTimestamp);
         dest.writeIntArray(mPowerComponents);
@@ -202,6 +214,7 @@
         private long mMaxStatsAgeMs = DEFAULT_MAX_STATS_AGE_MS;
         private long mFromTimestamp;
         private long mToTimestamp;
+        private double mMinConsumedPowerThreshold = 0;
         private @BatteryConsumer.PowerComponent int[] mPowerComponents;
 
         /**
@@ -301,5 +314,14 @@
             mMaxStatsAgeMs = maxStatsAgeMs;
             return this;
         }
+
+        /**
+         * Set the minimal power component consumed power threshold. The small power consuming
+         * components will be reported as zero.
+         */
+        public Builder setMinConsumedPowerThreshold(double minConsumedPowerThreshold) {
+            mMinConsumedPowerThreshold = minConsumedPowerThreshold;
+            return this;
+        }
     }
 }
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 6275352..f30dd20 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -36,4 +36,10 @@
     void vibrate(int uid, int displayId, String opPkg, in CombinedVibration vibration,
             in VibrationAttributes attributes, String reason, IBinder token);
     void cancelVibrate(int usageFilter, IBinder token);
+
+    // Async oneway APIs.
+    // There is no order guarantee with respect to the two-way APIs above like
+    // vibrate/isVibrating/cancel.
+    oneway void performHapticFeedback(int uid, int displayId, String opPkg, int constant,
+            boolean always, String reason, IBinder token);
 }
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 5dffa0a..9e5f539 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -461,9 +461,11 @@
         private static final byte POWER_MODEL_UNINITIALIZED = -1;
 
         private final BatteryConsumer.BatteryConsumerData mData;
+        private final double mMinConsumedPowerThreshold;
 
-        Builder(BatteryConsumer.BatteryConsumerData data) {
+        Builder(BatteryConsumer.BatteryConsumerData data, double minConsumedPowerThreshold) {
             mData = data;
+            mMinConsumedPowerThreshold = minConsumedPowerThreshold;
             for (BatteryConsumer.Key[] keys : mData.layout.keys) {
                 for (BatteryConsumer.Key key : keys) {
                     if (key.mPowerModelColumnIndex != -1) {
@@ -476,6 +478,9 @@
         @NonNull
         public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower,
                 int powerModel) {
+            if (Math.abs(componentPower) < mMinConsumedPowerThreshold) {
+                componentPower = 0;
+            }
             mData.putDouble(key.mPowerColumnIndex, componentPower);
             if (key.mPowerModelColumnIndex != -1) {
                 mData.putInt(key.mPowerModelColumnIndex, powerModel);
@@ -491,6 +496,9 @@
          */
         @NonNull
         public Builder setConsumedPowerForCustomComponent(int componentId, double componentPower) {
+            if (Math.abs(componentPower) < mMinConsumedPowerThreshold) {
+                componentPower = 0;
+            }
             final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
             if (index < 0 || index >= mData.layout.customPowerComponentCount) {
                 throw new IllegalArgumentException(
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 1cd0f3b..04c257b 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -206,6 +206,15 @@
     }
 
     @Override
+    public void performHapticFeedback(int constant, boolean always, String reason) {
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager.");
+            return;
+        }
+        mVibratorManager.performHapticFeedback(constant, always, reason);
+    }
+
+    @Override
     public void cancel() {
         if (mVibratorManager == null) {
             Log.w(TAG, "Failed to cancel vibrate; no vibrator manager.");
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index 284b246..ee90834 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -145,6 +145,21 @@
     }
 
     @Override
+    public void performHapticFeedback(int constant, boolean always, String reason) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager service.");
+            return;
+        }
+        try {
+            mService.performHapticFeedback(
+                    Process.myUid(), mContext.getAssociatedDisplayId(), mPackageName, constant,
+                    always, reason, mToken);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to perform haptic feedback.", e);
+        }
+    }
+
+    @Override
     public void cancel() {
         cancelVibration(VibrationAttributes.USAGE_FILTER_MATCH_ALL);
     }
@@ -228,6 +243,11 @@
         }
 
         @Override
+        public void performHapticFeedback(int effectId, boolean always, String reason) {
+            SystemVibratorManager.this.performHapticFeedback(effectId, always, reason);
+        }
+
+        @Override
         public void cancel() {
             SystemVibratorManager.this.cancel();
         }
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 103452d..03a1b6f 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -207,17 +207,18 @@
         private String mPackageWithHighestDrain = PACKAGE_NAME_UNINITIALIZED;
         private boolean mExcludeFromBatteryUsageStats;
 
-        public Builder(BatteryConsumerData data, @NonNull BatteryStats.Uid batteryStatsUid) {
-            this(data, batteryStatsUid, batteryStatsUid.getUid());
+        public Builder(BatteryConsumerData data, @NonNull BatteryStats.Uid batteryStatsUid,
+                double minConsumedPowerThreshold) {
+            this(data, batteryStatsUid, batteryStatsUid.getUid(), minConsumedPowerThreshold);
         }
 
-        public Builder(BatteryConsumerData data, int uid) {
-            this(data, null, uid);
+        public Builder(BatteryConsumerData data, int uid, double minConsumedPowerThreshold) {
+            this(data, null, uid, minConsumedPowerThreshold);
         }
 
         private Builder(BatteryConsumerData data, @Nullable BatteryStats.Uid batteryStatsUid,
-                int uid) {
-            super(data, CONSUMER_TYPE_UID);
+                int uid, double minConsumedPowerThreshold) {
+            super(data, CONSUMER_TYPE_UID, minConsumedPowerThreshold);
             mBatteryStatsUid = batteryStatsUid;
             mUid = uid;
             mIsVirtualUid = mUid == Process.SDK_SANDBOX_VIRTUAL_UID;
diff --git a/core/java/android/os/UserBatteryConsumer.java b/core/java/android/os/UserBatteryConsumer.java
index 6b4a5cf..a2ff078 100644
--- a/core/java/android/os/UserBatteryConsumer.java
+++ b/core/java/android/os/UserBatteryConsumer.java
@@ -107,8 +107,8 @@
     public static final class Builder extends BaseBuilder<Builder> {
         private List<UidBatteryConsumer.Builder> mUidBatteryConsumers;
 
-        Builder(BatteryConsumerData data, int userId) {
-            super(data, CONSUMER_TYPE_USER);
+        Builder(BatteryConsumerData data, int userId, double minConsumedPowerThreshold) {
+            super(data, CONSUMER_TYPE_USER, minConsumedPowerThreshold);
             data.putLong(COLUMN_INDEX_USER_ID, userId);
         }
 
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index aafa501..99c9925 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -510,6 +510,28 @@
             String reason, @NonNull VibrationAttributes attributes);
 
     /**
+     * Performs a haptic feedback.
+     *
+     * <p>A haptic feedback is a short vibration feedback. The type of feedback is identified via
+     * the {@code constant}, which should be one of the effect constants provided in
+     * {@link HapticFeedbackConstants}. The haptic feedback provided for a given effect ID is
+     * consistent across all usages on the same device.
+     *
+     * @param constant the ID for the haptic feedback. This should be one of the constants defined
+     *          in {@link HapticFeedbackConstants}.
+     * @param always {@code true} if the haptic feedback should be played regardless of the user
+     *          vibration intensity settings applicable to the corresponding vibration.
+     *          {@code false} if the vibration for the haptic feedback should respect the applicable
+     *          vibration intensity settings.
+     * @param reason the reason for this haptic feedback.
+     *
+     * @hide
+     */
+    public void performHapticFeedback(int constant, boolean always, String reason) {
+        Log.w(TAG, "performHapticFeedback is not supported");
+    }
+
+    /**
      * Query whether the vibrator natively supports the given effects.
      *
      * <p>If an effect is not supported, the system may still automatically fall back to playing
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index f506ef8..e0b6a9f 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -35,7 +35,8 @@
 public abstract class VibratorManager {
     private static final String TAG = "VibratorManager";
 
-    private final String mPackageName;
+    /** @hide */
+    protected final String mPackageName;
 
     /**
      * @hide to prevent subclassing from outside of the framework
@@ -137,6 +138,21 @@
             String reason, @Nullable VibrationAttributes attributes);
 
     /**
+     * Performs a haptic feedback.
+     *
+     * @param constant the ID of the requested haptic feedback. Should be one of the constants
+     *          defined in {@link HapticFeedbackConstants}.
+     * @param always {@code true} if the haptic feedback should be played regardless of the user
+     *          vibration intensity settings applicable to the corresponding vibration.
+     *          {@code false} otherwise.
+     * @param reason the reason for this haptic feedback.
+     * @hide
+     */
+    public void performHapticFeedback(int constant, boolean always, String reason) {
+        Log.w(TAG, "performHapticFeedback is not supported");
+    }
+
+    /**
      * Turn all the vibrators off.
      */
     @RequiresPermission(android.Manifest.permission.VIBRATE)
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
new file mode 100644
index 0000000..361e244
--- /dev/null
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.os.vibrator"
+
+flag {
+    namespace: "vibrator"
+    name: "use_vibrator_haptic_feedback"
+    description: "Enables performHapticFeedback to directly use the vibrator service instead of going through the window session"
+    bug: "295459081"
+}
\ No newline at end of file
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index c7e5453..637770c 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1307,9 +1307,9 @@
                     visibleFrame.intersect(mInsetsState.getDisplayFrame());
                     WindowInsets windowInsets = mInsetsState.calculateInsets(visibleFrame,
                             null /* ignoringVisibilityState */, config.isScreenRound(),
-                            false /* alwaysConsumeSystemBars */, mLayout.softInputMode,
-                            mLayout.flags, SYSTEM_UI_FLAG_VISIBLE, mLayout.type,
-                            config.windowConfiguration.getWindowingMode(), null /* idSideMap */);
+                            mLayout.softInputMode, mLayout.flags, SYSTEM_UI_FLAG_VISIBLE,
+                            mLayout.type, config.windowConfiguration.getActivityType(),
+                            null /* idSideMap */);
 
                     if (!fixedSize) {
                         final Rect padding = mIWallpaperEngine.mDisplayPadding;
diff --git a/core/java/android/text/flags/phrase_strict_fallback.aconfig b/core/java/android/text/flags/phrase_strict_fallback.aconfig
new file mode 100644
index 0000000..c67a21b
--- /dev/null
+++ b/core/java/android/text/flags/phrase_strict_fallback.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.text.flags"
+
+flag {
+  name: "phrase_strict_fallback"
+  namespace: "text"
+  description: "Feature flag for automatic fallback from phrase based line break to strict line break."
+  bug: "281970875"
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 85f5395..5fe2aa1 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -156,6 +156,13 @@
     public static final String SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS =
             "settings_biometrics2_fingerprint";
 
+    /**
+     * Flag to enable/disable remote auth enrollment and settings
+     * @hide
+     */
+    public static final String SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS =
+            "settings_remoteauth_enrollment";
+
     /** Flag to enable/disable entire page in Accessibility -> Hearing aids
      *  @hide
      */
@@ -248,6 +255,8 @@
         DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "true");
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
         DEFAULT_FLAGS.put("settings_press_hold_nav_handle_to_search", "false");
+        // TODO: b/298454866 Replace with Trunk Stable Feature Flag
+        DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index fabfed3..1ec7c41 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -16,7 +16,7 @@
 
 package android.view;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.view.EventLogTags.IMF_IME_ANIM_CANCEL;
 import static android.view.EventLogTags.IMF_IME_ANIM_FINISH;
 import static android.view.EventLogTags.IMF_IME_ANIM_START;
@@ -39,6 +39,7 @@
 import static android.view.InsetsState.ISIDE_RIGHT;
 import static android.view.InsetsState.ISIDE_TOP;
 import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
 import static android.view.inputmethod.ImeTracker.TOKEN_NONE;
@@ -62,7 +63,6 @@
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation.Bounds;
-import android.view.WindowManager.LayoutParams;
 import android.view.animation.Interpolator;
 import android.view.inputmethod.ImeTracker;
 
@@ -396,10 +396,9 @@
     private Insets getInsetsFromState(InsetsState state, Rect frame,
             @Nullable @InternalInsetsSide SparseIntArray idSideMap) {
         return state.calculateInsets(frame, null /* ignoringVisibilityState */,
-                false /* isScreenRound */, false /* alwaysConsumeSystemBars */,
-                LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/,
+                false /* isScreenRound */, SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode */,
                 0 /* legacyWindowFlags */, 0 /* legacySystemUiFlags */, TYPE_APPLICATION,
-                WINDOWING_MODE_UNDEFINED, idSideMap).getInsets(mTypes);
+                ACTIVITY_TYPE_UNDEFINED, idSideMap).getInsets(mTypes);
     }
 
     /** Computes the insets relative to the given frame. */
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8ec7d67..fb24211 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -652,7 +652,7 @@
     private int mLastLegacySoftInputMode;
     private int mLastLegacyWindowFlags;
     private int mLastLegacySystemUiFlags;
-    private int mLastWindowingMode;
+    private int mLastActivityType;
     private boolean mStartingAnimation;
     private int mCaptionInsetsHeight = 0;
     private int mImeCaptionBarInsetsHeight = 0;
@@ -800,10 +800,10 @@
                 }
             }
 
-            WindowInsets insets = state.calculateInsets(mFrame, mState /* ignoringVisibilityState*/,
-                    mLastInsets.isRound(), false /* alwaysConsumeSystemBars */,
+            WindowInsets insets = state.calculateInsets(mFrame,
+                    mState /* ignoringVisibilityState */, mLastInsets.isRound(),
                     mLastLegacySoftInputMode, mLastLegacyWindowFlags, mLastLegacySystemUiFlags,
-                    mWindowType, mLastWindowingMode, null /* idSideMap */);
+                    mWindowType, mLastActivityType, null /* idSideMap */);
             mHost.dispatchWindowInsetsAnimationProgress(insets,
                     Collections.unmodifiableList(runningAnimations));
             if (DEBUG) {
@@ -939,30 +939,29 @@
     }
 
     /**
-     * @see InsetsState#calculateInsets(Rect, InsetsState, boolean, boolean, int, int, int, int,
-     *      int, android.util.SparseIntArray)
+     * @see InsetsState#calculateInsets(Rect, InsetsState, boolean, int, int, int, int, int,
+     *      android.util.SparseIntArray)
      */
     @VisibleForTesting
-    public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeSystemBars,
-            int windowType, int windowingMode, int legacySoftInputMode, int legacyWindowFlags,
-            int legacySystemUiFlags) {
+    public WindowInsets calculateInsets(boolean isScreenRound, int windowType, int activityType,
+            int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags) {
         mWindowType = windowType;
-        mLastWindowingMode = windowingMode;
+        mLastActivityType = activityType;
         mLastLegacySoftInputMode = legacySoftInputMode;
         mLastLegacyWindowFlags = legacyWindowFlags;
         mLastLegacySystemUiFlags = legacySystemUiFlags;
-        mLastInsets = mState.calculateInsets(mFrame, null /* ignoringVisibilityState*/,
-                isScreenRound, alwaysConsumeSystemBars, legacySoftInputMode, legacyWindowFlags,
-                legacySystemUiFlags, windowType, windowingMode, null /* idSideMap */);
+        mLastInsets = mState.calculateInsets(mFrame, null /* ignoringVisibilityState */,
+                isScreenRound, legacySoftInputMode, legacyWindowFlags,
+                legacySystemUiFlags, windowType, activityType, null /* idSideMap */);
         return mLastInsets;
     }
 
     /**
      * @see InsetsState#calculateVisibleInsets(Rect, int, int, int, int)
      */
-    public Insets calculateVisibleInsets(int windowType, int windowingMode,
+    public Insets calculateVisibleInsets(int windowType, int activityType,
             @SoftInputModeFlags int softInputMode, int windowFlags) {
-        return mState.calculateVisibleInsets(mFrame, windowType, windowingMode, softInputMode,
+        return mState.calculateVisibleInsets(mFrame, windowType, activityType, softInputMode,
                 windowFlags);
     }
 
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index af24140..59e0932 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 import static android.view.InsetsStateProto.DISPLAY_CUTOUT;
@@ -39,7 +40,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.WindowConfiguration;
+import android.app.WindowConfiguration.ActivityType;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Parcel;
@@ -136,9 +137,8 @@
      * @return The calculated insets.
      */
     public WindowInsets calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState,
-            boolean isScreenRound, boolean alwaysConsumeSystemBars,
-            int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags,
-            int windowType, @WindowConfiguration.WindowingMode int windowingMode,
+            boolean isScreenRound, int legacySoftInputMode, int legacyWindowFlags,
+            int legacySystemUiFlags, int windowType, @ActivityType int activityType,
             @Nullable @InternalInsetsSide SparseIntArray idSideMap) {
         Insets[] typeInsetsMap = new Insets[Type.SIZE];
         Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
@@ -185,9 +185,8 @@
         if ((legacyWindowFlags & FLAG_FULLSCREEN) != 0) {
             compatInsetsTypes &= ~statusBars();
         }
-        if (clearsCompatInsets(windowType, legacyWindowFlags, windowingMode)) {
-            // Clear all types but forceConsumingTypes.
-            compatInsetsTypes &= forceConsumingTypes;
+        if (clearsCompatInsets(windowType, legacyWindowFlags, activityType, forceConsumingTypes)) {
+            compatInsetsTypes = 0;
         }
 
         return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
@@ -295,26 +294,27 @@
         return insets;
     }
 
-    public Insets calculateVisibleInsets(Rect frame, int windowType, int windowingMode,
+    public Insets calculateVisibleInsets(Rect frame, int windowType, @ActivityType int activityType,
             @SoftInputModeFlags int softInputMode, int windowFlags) {
-        final boolean clearsCompatInsets = clearsCompatInsets(
-                windowType, windowFlags, windowingMode);
         final int softInputAdjustMode = softInputMode & SOFT_INPUT_MASK_ADJUST;
         final int visibleInsetsTypes = softInputAdjustMode != SOFT_INPUT_ADJUST_NOTHING
                 ? systemBars() | ime()
                 : systemBars();
+        @InsetsType int forceConsumingTypes = 0;
         Insets insets = Insets.NONE;
         for (int i = mSources.size() - 1; i >= 0; i--) {
             final InsetsSource source = mSources.valueAt(i);
             if ((source.getType() & visibleInsetsTypes) == 0) {
                 continue;
             }
-            if (clearsCompatInsets && !source.hasFlags(FLAG_FORCE_CONSUMING)) {
-                continue;
+            if (source.hasFlags(FLAG_FORCE_CONSUMING)) {
+                forceConsumingTypes |= source.getType();
             }
             insets = Insets.max(source.calculateVisibleInsets(frame), insets);
         }
-        return insets;
+        return clearsCompatInsets(windowType, windowFlags, activityType, forceConsumingTypes)
+                ? Insets.NONE
+                : insets;
     }
 
     /**
@@ -662,10 +662,15 @@
         mSources.put(source.getId(), source);
     }
 
-    public static boolean clearsCompatInsets(int windowType, int windowFlags, int windowingMode) {
+    public static boolean clearsCompatInsets(int windowType, int windowFlags,
+            @ActivityType int activityType, @InsetsType int forceConsumingTypes) {
         return (windowFlags & FLAG_LAYOUT_NO_LIMITS) != 0
+                // For compatibility reasons, this excludes the wallpaper, the system error windows,
+                // and the app windows while any system bar is forcibly consumed.
                 && windowType != TYPE_WALLPAPER && windowType != TYPE_SYSTEM_ERROR
-                && !WindowConfiguration.inMultiWindowMode(windowingMode);
+                // This ensures the app content won't be obscured by compat insets even if the app
+                // has FLAG_LAYOUT_NO_LIMITS.
+                && (forceConsumingTypes == 0 || activityType != ACTIVITY_TYPE_STANDARD);
     }
 
     public void dump(String prefix, PrintWriter pw) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9a4cb72..30fd2cf 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -105,6 +105,8 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.Vibrator;
+import android.os.vibrator.Flags;
 import android.sysprop.DisplayProperties;
 import android.text.InputType;
 import android.text.TextUtils;
@@ -5411,6 +5413,9 @@
      */
     private PointerIcon mMousePointerIcon;
 
+    /** Vibrator for haptic feedback. */
+    private Vibrator mVibrator;
+
     /**
      * @hide
      */
@@ -27758,8 +27763,24 @@
                 && !isHapticFeedbackEnabled()) {
             return false;
         }
-        return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant,
-                (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0);
+
+        final boolean always = (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0;
+        if (Flags.useVibratorHapticFeedback()) {
+            if (!mAttachInfo.canPerformHapticFeedback()) {
+                return false;
+            }
+            getSystemVibrator().performHapticFeedback(
+                    feedbackConstant, always, "View#performHapticFeedback");
+            return true;
+        }
+        return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, always);
+    }
+
+    private Vibrator getSystemVibrator() {
+        if (mVibrator != null) {
+            return mVibrator;
+        }
+        return mVibrator = mContext.getSystemService(Vibrator.class);
     }
 
     /**
@@ -31239,6 +31260,11 @@
             return events;
         }
 
+        private boolean canPerformHapticFeedback() {
+            return mSession != null
+                    && (mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) == 0;
+        }
+
         @Nullable
         ScrollCaptureInternal getScrollCaptureInternal() {
             if (mScrollCaptureInternal != null) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e0fda7e..60d964e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2848,16 +2848,15 @@
         if (mLastWindowInsets == null || forceConstruct) {
             final Configuration config = getConfiguration();
             mLastWindowInsets = mInsetsController.calculateInsets(
-                    config.isScreenRound(), mAttachInfo.mAlwaysConsumeSystemBars,
-                    mWindowAttributes.type, config.windowConfiguration.getWindowingMode(),
-                    mWindowAttributes.softInputMode, mWindowAttributes.flags,
-                    (mWindowAttributes.systemUiVisibility
+                    config.isScreenRound(), mWindowAttributes.type,
+                    config.windowConfiguration.getActivityType(), mWindowAttributes.softInputMode,
+                    mWindowAttributes.flags, (mWindowAttributes.systemUiVisibility
                             | mWindowAttributes.subtreeSystemUiVisibility));
 
             mAttachInfo.mContentInsets.set(mLastWindowInsets.getSystemWindowInsets().toRect());
             mAttachInfo.mStableInsets.set(mLastWindowInsets.getStableInsets().toRect());
             mAttachInfo.mVisibleInsets.set(mInsetsController.calculateVisibleInsets(
-                    mWindowAttributes.type, config.windowConfiguration.getWindowingMode(),
+                    mWindowAttributes.type, config.windowConfiguration.getActivityType(),
                     mWindowAttributes.softInputMode, mWindowAttributes.flags).toRect());
         }
         return mLastWindowInsets;
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
new file mode 100644
index 0000000..92d3408
--- /dev/null
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.view.inputmethod"
+
+flag {
+    name: "refactor_insets_controller"
+    namespace: "inputmethod"
+    description: "Feature flag for refactoring InsetsController and removing ImeInsetsSourceConsumer"
+    bug: "298172246"
+}
\ No newline at end of file
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 2858f0a..e32c8e5 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -16,7 +16,7 @@
 
 package android.window;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
 
@@ -80,7 +80,7 @@
         final Rect bounds;
         final float density;
         final boolean isScreenRound;
-        final int windowingMode;
+        final int activityType;
         synchronized (ResourcesManager.getInstance()) {
             final Configuration config = mContext.getResources().getConfiguration();
             final WindowConfiguration winConfig = config.windowConfiguration;
@@ -90,11 +90,11 @@
             // as DisplayMetrics#density
             density = config.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
             isScreenRound = config.isScreenRound();
-            windowingMode = winConfig.getWindowingMode();
+            activityType = winConfig.getActivityType();
         }
         final IBinder token = Context.getToken(mContext);
         final Supplier<WindowInsets> insetsSupplier = () -> getWindowInsetsFromServerForDisplay(
-                mContext.getDisplayId(), token, bounds, isScreenRound, windowingMode);
+                mContext.getDisplayId(), token, bounds, isScreenRound, activityType);
         return new WindowMetrics(new Rect(bounds), insetsSupplier, density);
     }
 
@@ -105,23 +105,22 @@
      * @param token the token of Activity or WindowContext
      * @param bounds the window bounds to calculate insets for
      * @param isScreenRound if the display identified by displayId is round
-     * @param windowingMode the windowing mode of the window to calculate insets for
+     * @param activityType the activity type of the window to calculate insets for
      * @return WindowInsets calculated for the given window bounds, on the given display
      */
     private static WindowInsets getWindowInsetsFromServerForDisplay(int displayId, IBinder token,
-            Rect bounds, boolean isScreenRound, int windowingMode) {
+            Rect bounds, boolean isScreenRound, int activityType) {
         try {
             final InsetsState insetsState = new InsetsState();
-            final boolean alwaysConsumeSystemBars = WindowManagerGlobal.getWindowManagerService()
-                    .getWindowInsets(displayId, token, insetsState);
+            WindowManagerGlobal.getWindowManagerService().getWindowInsets(
+                    displayId, token, insetsState);
             final float overrideInvScale = CompatibilityInfo.getOverrideInvertedScale();
             if (overrideInvScale != 1f) {
                 insetsState.scale(overrideInvScale);
             }
             return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState */,
-                    isScreenRound, alwaysConsumeSystemBars, SOFT_INPUT_ADJUST_NOTHING,
-                    0 /* flags */, SYSTEM_UI_FLAG_VISIBLE,
-                    WindowManager.LayoutParams.INVALID_WINDOW_TYPE, windowingMode,
+                    isScreenRound, SOFT_INPUT_ADJUST_NOTHING, 0 /* flags */, SYSTEM_UI_FLAG_VISIBLE,
+                    WindowManager.LayoutParams.INVALID_WINDOW_TYPE, activityType,
                     null /* idSideMap */);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -157,7 +156,7 @@
                     currentDisplayInfo.displayId, null /* token */,
                     new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
                             currentDisplayInfo.getNaturalHeight()), isScreenRound,
-                    WINDOWING_MODE_FULLSCREEN);
+                    ACTIVITY_TYPE_UNDEFINED);
             // Set the hardware-provided insets.
             windowInsets = new WindowInsets.Builder(windowInsets).setRoundedCorners(
                             currentDisplayInfo.roundedCorners)
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 560e41b..b8d251f 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -8,3 +8,10 @@
     description: "Whether the feature to sync different window-related config updates is enabled"
     bug: "260873529"
 }
+
+flag {
+    namespace: "windowing_sdk"
+    name: "activity_embedding_overlay_presentation_flag"
+    description: "Whether the overlay presentation feature is enabled"
+    bug: "243518738"
+}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index bb86801..86ca077 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1113,15 +1113,14 @@
             if (insets != null) {
                 mLastForceConsumingTypes = insets.getForceConsumingTypes();
 
-                @InsetsType int compatInsetsTypes =
+                final boolean clearsCompatInsets = clearsCompatInsets(attrs.type, attrs.flags,
+                        getResources().getConfiguration().windowConfiguration.getActivityType(),
+                        mLastForceConsumingTypes);
+                final @InsetsType int compatInsetsTypes =
                         WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout();
-                if (clearsCompatInsets(attrs.type, attrs.flags,
-                        getResources().getConfiguration().windowConfiguration.getWindowingMode())) {
-                    compatInsetsTypes &= mLastForceConsumingTypes;
-                }
                 final Insets stableBarInsets = insets.getInsetsIgnoringVisibility(
                         WindowInsets.Type.systemBars());
-                final Insets systemInsets = compatInsetsTypes == 0
+                final Insets systemInsets = clearsCompatInsets
                         ? Insets.NONE
                         : Insets.min(insets.getInsets(compatInsetsTypes), stableBarInsets);
                 mLastTopInset = systemInsets.top;
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index ccae67f..6440cc3 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2071,10 +2071,67 @@
                           mixerBehavior);
 }
 
-static jint convertAudioMixToNative(JNIEnv *env,
-                                    AudioMix *nAudioMix,
-                                    const jobject jAudioMix)
-{
+static jint convertAudioMixingRuleToNative(JNIEnv *env, const jobject audioMixingRule,
+                                           std::vector<AudioMixMatchCriterion> *nCriteria) {
+    jobject jRuleCriteria = env->GetObjectField(audioMixingRule, gAudioMixingRuleFields.mCriteria);
+
+    jobjectArray jCriteria = static_cast<jobjectArray>(
+            env->CallObjectMethod(jRuleCriteria, gArrayListMethods.toArray));
+    env->DeleteLocalRef(jRuleCriteria);
+
+    jint numCriteria = env->GetArrayLength(jCriteria);
+    if (numCriteria > MAX_CRITERIA_PER_MIX) {
+        numCriteria = MAX_CRITERIA_PER_MIX;
+    }
+
+    nCriteria->resize(numCriteria);
+    for (jint i = 0; i < numCriteria; i++) {
+        AudioMixMatchCriterion &nCriterion = (*nCriteria)[i];
+
+        jobject jCriterion = env->GetObjectArrayElement(jCriteria, i);
+
+        nCriterion.mRule = env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mRule);
+
+        const uint32_t match_rule = nCriterion.mRule & ~RULE_EXCLUSION_MASK;
+        switch (match_rule) {
+            case RULE_MATCH_UID:
+                nCriterion.mValue.mUid =
+                        env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp);
+                break;
+            case RULE_MATCH_USERID:
+                nCriterion.mValue.mUserId =
+                        env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp);
+                break;
+            case RULE_MATCH_AUDIO_SESSION_ID: {
+                jint jAudioSessionId =
+                        env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp);
+                nCriterion.mValue.mAudioSessionId = static_cast<audio_session_t>(jAudioSessionId);
+            } break;
+            case RULE_MATCH_ATTRIBUTE_USAGE:
+            case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: {
+                jobject jAttributes =
+                        env->GetObjectField(jCriterion, gAudioMixMatchCriterionFields.mAttr);
+
+                auto paa = JNIAudioAttributeHelper::makeUnique();
+                jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAttributes, paa.get());
+                if (jStatus != AUDIO_JAVA_SUCCESS) {
+                    return jStatus;
+                }
+                if (match_rule == RULE_MATCH_ATTRIBUTE_USAGE) {
+                    nCriterion.mValue.mUsage = paa->usage;
+                } else {
+                    nCriterion.mValue.mSource = paa->source;
+                }
+                env->DeleteLocalRef(jAttributes);
+            } break;
+        }
+        env->DeleteLocalRef(jCriterion);
+    }
+    env->DeleteLocalRef(jCriteria);
+    return AUDIO_JAVA_SUCCESS;
+}
+
+static jint convertAudioMixToNative(JNIEnv *env, AudioMix *nAudioMix, const jobject jAudioMix) {
     nAudioMix->mMixType = env->GetIntField(jAudioMix, gAudioMixFields.mMixType);
     nAudioMix->mRouteFlags = env->GetIntField(jAudioMix, gAudioMixFields.mRouteFlags);
     nAudioMix->mDeviceType =
@@ -2094,69 +2151,16 @@
     env->DeleteLocalRef(jFormat);
 
     jobject jRule = env->GetObjectField(jAudioMix, gAudioMixFields.mRule);
-    jobject jRuleCriteria = env->GetObjectField(jRule, gAudioMixingRuleFields.mCriteria);
     nAudioMix->mAllowPrivilegedMediaPlaybackCapture =
             env->GetBooleanField(jRule, gAudioMixingRuleFields.mAllowPrivilegedPlaybackCapture);
     nAudioMix->mVoiceCommunicationCaptureAllowed =
             env->GetBooleanField(jRule, gAudioMixingRuleFields.mVoiceCommunicationCaptureAllowed);
+
+    jint status = convertAudioMixingRuleToNative(env, jRule, &(nAudioMix->mCriteria));
+
     env->DeleteLocalRef(jRule);
-    jobjectArray jCriteria = static_cast<jobjectArray>(
-            env->CallObjectMethod(jRuleCriteria, gArrayListMethods.toArray));
-    env->DeleteLocalRef(jRuleCriteria);
 
-    jint numCriteria = env->GetArrayLength(jCriteria);
-    if (numCriteria > MAX_CRITERIA_PER_MIX) {
-        numCriteria = MAX_CRITERIA_PER_MIX;
-    }
-
-    for (jint i = 0; i < numCriteria; i++) {
-        AudioMixMatchCriterion nCriterion;
-
-        jobject jCriterion = env->GetObjectArrayElement(jCriteria, i);
-
-        nCriterion.mRule = env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mRule);
-
-        const uint32_t match_rule = nCriterion.mRule & ~RULE_EXCLUSION_MASK;
-        switch (match_rule) {
-        case RULE_MATCH_UID:
-            nCriterion.mValue.mUid = env->GetIntField(jCriterion,
-                    gAudioMixMatchCriterionFields.mIntProp);
-            break;
-        case RULE_MATCH_USERID:
-            nCriterion.mValue.mUserId =
-                    env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp);
-            break;
-        case RULE_MATCH_AUDIO_SESSION_ID: {
-            jint jAudioSessionId =
-                    env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp);
-            nCriterion.mValue.mAudioSessionId = static_cast<audio_session_t>(jAudioSessionId);
-        } break;
-        case RULE_MATCH_ATTRIBUTE_USAGE:
-        case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: {
-            jobject jAttributes = env->GetObjectField(jCriterion, gAudioMixMatchCriterionFields.mAttr);
-
-            auto paa = JNIAudioAttributeHelper::makeUnique();
-            jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAttributes, paa.get());
-            if (jStatus != AUDIO_JAVA_SUCCESS) {
-                    return jStatus;
-            }
-            if (match_rule == RULE_MATCH_ATTRIBUTE_USAGE) {
-                nCriterion.mValue.mUsage = paa->usage;
-            } else {
-                nCriterion.mValue.mSource = paa->source;
-            }
-            env->DeleteLocalRef(jAttributes);
-            }
-            break;
-        }
-
-        nAudioMix->mCriteria.push_back(nCriterion);
-        env->DeleteLocalRef(jCriterion);
-    }
-
-    env->DeleteLocalRef(jCriteria);
-
-    return AUDIO_JAVA_SUCCESS;
+    return status;
 }
 
 static jint
@@ -2200,6 +2204,45 @@
     return nativeToJavaStatus(status);
 }
 
+static jint android_media_AudioSystem_updatePolicyMixes(JNIEnv *env, jobject clazz,
+                                                        jobjectArray mixes,
+                                                        jobjectArray updatedMixingRules) {
+    if (mixes == nullptr || updatedMixingRules == nullptr) {
+        return AUDIO_JAVA_BAD_VALUE;
+    }
+
+    jsize updatesCount = env->GetArrayLength(mixes);
+    if (updatesCount == 0 || updatesCount != env->GetArrayLength(updatedMixingRules)) {
+        return AUDIO_JAVA_BAD_VALUE;
+    }
+
+    std::vector<std::pair<AudioMix, std::vector<AudioMixMatchCriterion>>> updates(updatesCount);
+    for (int i = 0; i < updatesCount; i++) {
+        jobject jAudioMix = env->GetObjectArrayElement(mixes, i);
+        jobject jAudioMixingRule = env->GetObjectArrayElement(updatedMixingRules, i);
+        if (!env->IsInstanceOf(jAudioMix, gAudioMixClass) ||
+            !env->IsInstanceOf(jAudioMixingRule, gAudioMixingRuleClass)) {
+            return AUDIO_JAVA_BAD_VALUE;
+        }
+
+        jint ret;
+        if ((ret = convertAudioMixToNative(env, &updates[i].first, jAudioMix)) !=
+            AUDIO_JAVA_SUCCESS) {
+            return ret;
+        }
+        if ((ret = convertAudioMixingRuleToNative(env, jAudioMixingRule, &updates[i].second)) !=
+            AUDIO_JAVA_SUCCESS) {
+            return ret;
+        }
+    }
+
+    ALOGV("AudioSystem::updatePolicyMixes numMixes %d", updatesCount);
+    int status = AudioSystem::updatePolicyMixes(updates);
+    ALOGV("AudioSystem::updatePolicyMixes returned %d", status);
+
+    return nativeToJavaStatus(status);
+}
+
 static jint android_media_AudioSystem_setUidDeviceAffinities(JNIEnv *env, jobject clazz,
         jint uid, jintArray deviceTypes, jobjectArray deviceAddresses) {
     AudioDeviceTypeAddrVector deviceVector;
@@ -3158,6 +3201,10 @@
          MAKE_AUDIO_SYSTEM_METHOD(getAudioHwSyncForSession),
          MAKE_JNI_NATIVE_METHOD("registerPolicyMixes", "(Ljava/util/ArrayList;Z)I",
                                 android_media_AudioSystem_registerPolicyMixes),
+         MAKE_JNI_NATIVE_METHOD("updatePolicyMixes",
+                                "([Landroid/media/audiopolicy/AudioMix;[Landroid/media/audiopolicy/"
+                                "AudioMixingRule;)I",
+                                android_media_AudioSystem_updatePolicyMixes),
          MAKE_JNI_NATIVE_METHOD("setUidDeviceAffinities", "(I[I[Ljava/lang/String;)I",
                                 android_media_AudioSystem_setUidDeviceAffinities),
          MAKE_AUDIO_SYSTEM_METHOD(removeUidDeviceAffinities),
diff --git a/core/res/res/layout/autofill_fill_dialog.xml b/core/res/res/layout/autofill_fill_dialog.xml
index d1a4935..37d2fa0 100644
--- a/core/res/res/layout/autofill_fill_dialog.xml
+++ b/core/res/res/layout/autofill_fill_dialog.xml
@@ -85,14 +85,18 @@
         android:layout_marginStart="24dp"
         android:layout_marginEnd="24dp"
         android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton"
-        android:orientation="horizontal">
+        android:orientation="horizontal"
+        android:gravity="center_vertical">
 
         <Button
             android:id="@+id/autofill_dialog_no"
             android:layout_width="wrap_content"
-            android:layout_height="36dp"
-            android:layout_marginTop="6dp"
-            android:layout_marginBottom="6dp"
+            android:layout_height="40dp"
+            android:paddingStart="12dp"
+            android:paddingEnd="12dp"
+            android:paddingTop="0dp"
+            android:paddingBottom="0dp"
+            android:minWidth="0dp"
             style="?android:attr/borderlessButtonStyle"
             android:text="@string/autofill_save_no">
         </Button>
@@ -107,9 +111,8 @@
         <Button
             android:id="@+id/autofill_dialog_yes"
             android:layout_width="wrap_content"
-            android:layout_height="36dp"
-            android:layout_marginTop="6dp"
-            android:layout_marginBottom="6dp"
+            android:layout_height="40dp"
+            android:minWidth="0dp"
             style="@style/AutofillHalfSheetTonalButton"
             android:text="@string/autofill_save_yes"
             android:visibility="gone" >
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
index 85529d6..bed19a8 100644
--- a/core/res/res/layout/autofill_save.xml
+++ b/core/res/res/layout/autofill_save.xml
@@ -72,14 +72,18 @@
             android:layout_marginTop="32dp"
             android:layout_marginBottom="18dp"
             android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton"
-            android:orientation="horizontal">
+            android:orientation="horizontal"
+            android:gravity="center_vertical">
 
             <Button
                 android:id="@+id/autofill_save_no"
                 android:layout_width="wrap_content"
-                android:layout_height="36dp"
-                android:layout_marginTop="6dp"
-                android:layout_marginBottom="6dp"
+                android:layout_height="40dp"
+                android:paddingStart="12dp"
+                android:paddingEnd="12dp"
+                android:paddingTop="0dp"
+                android:paddingBottom="0dp"
+                android:minWidth="0dp"
                 style="?android:attr/borderlessButtonStyle"
                 android:text="@string/autofill_save_no">
             </Button>
@@ -94,9 +98,8 @@
             <Button
                 android:id="@+id/autofill_save_yes"
                 android:layout_width="wrap_content"
-                android:layout_height="36dp"
-                android:layout_marginTop="6dp"
-                android:layout_marginBottom="6dp"
+                android:layout_height="40dp"
+                android:minWidth="0dp"
                 style="@style/AutofillHalfSheetTonalButton"
                 android:text="@string/autofill_save_yes">
             </Button>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a72f779..03480e4 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1185,6 +1185,9 @@
     -->
     <integer name="config_shortPressOnSleepBehavior">0</integer>
 
+    <!-- Whether to silence telephony ringer on sleep key event -->
+    <bool name="config_silenceRingerOnSleepKey">false</bool>
+
     <!-- Control the behavior when the user long presses the stem primary button.
          Stem primary button is only used on watch form factor. If a device is not
          a watch, setting this config is no-op.
@@ -6182,6 +6185,8 @@
 
     <!-- Default value for Settings.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED -->
     <bool name="config_searchPressHoldNavHandleEnabledDefault">true</bool>
+    <!-- Default value for Settings.ASSIST_LONG_PRESS_HOME_ENABLED for search overlay -->
+    <bool name="config_searchLongPressHomeEnabledDefault">true</bool>
 
     <!-- The maximum byte size of the information contained in the bundle of
     HotwordDetectedResult. -->
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 164f713..13d04e5 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1500,12 +1500,8 @@
         <item name="fontFamily">google-sans-text-medium</item>
         <item name="textStyle">normal</item>
         <item name="textAllCaps">false</item>
-        <item name="layout_marginTop">6dp</item>
-        <item name="layout_marginBottom">6dp</item>
-        <item name="paddingStart">16dp</item>
-        <item name="paddingEnd">16dp</item>
-        <item name="paddingTop">8dp</item>
-        <item name="paddingBottom">8dp</item>
+        <item name="paddingStart">24dp</item>
+        <item name="paddingEnd">24dp</item>
     </style>
     <!-- @hide Tonal button for Autofill half screen dialog -->
     <style name="AutofillHalfSheetTonalButton" parent="AutofillHalfSheetButton">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ee0563b..7ea5974 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -464,6 +464,7 @@
   <java-symbol type="integer" name="config_toastDefaultGravity" />
   <java-symbol type="integer" name="config_triplePressOnPowerBehavior" />
   <java-symbol type="integer" name="config_shortPressOnSleepBehavior" />
+  <java-symbol type="bool" name="config_silenceRingerOnSleepKey" />
   <java-symbol type="integer" name="config_longPressOnStemPrimaryBehavior" />
   <java-symbol type="integer" name="config_shortPressOnStemPrimaryBehavior" />
   <java-symbol type="string" name="config_primaryShortPressTargetActivity" />
@@ -4914,6 +4915,7 @@
   <java-symbol type="bool" name="config_assistTouchGestureEnabledDefault" />
 
   <java-symbol type="bool" name="config_searchPressHoldNavHandleEnabledDefault" />
+  <java-symbol type="bool" name="config_searchLongPressHomeEnabledDefault" />
 
   <java-symbol type="integer" name="config_hotwordDetectedResultMaxBundleSize" />
 
diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
index 0676f89..aaaa3c7 100644
--- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
+++ b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
@@ -241,7 +241,8 @@
         final BatteryUsageStats.Builder builder =
                 new BatteryUsageStats.Builder(new String[]{"CustomConsumer1", "CustomConsumer2"},
                         /* includePowerModels */ true,
-                        /* includeProcessStats */true)
+                        /* includeProcessStats */ true,
+                        /* minConsumedPowerThreshold */ 0)
                         .setDischargePercentage(20)
                         .setDischargedPowerRange(1000, 2000)
                         .setDischargeDurationMs(1234)
@@ -325,7 +326,7 @@
     @Test
     public void testLargeAtomTruncated() {
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[0], true, false);
+                new BatteryUsageStats.Builder(new String[0], true, false, 0);
         // If not truncated, this BatteryUsageStats object would generate a proto buffer
         // significantly larger than 50 Kb
         for (int i = 0; i < 3000; i++) {
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index f45db23..8c93fbb 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -16,7 +16,7 @@
 
 package android.view;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.view.InsetsSource.ID_IME;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -81,8 +81,7 @@
                     Insets.of(10, 10, 10, 10), rect, rect, rect, rect));
             mController.calculateInsets(
                     false,
-                    false,
-                    TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+                    TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
                     SOFT_INPUT_ADJUST_RESIZE, 0, 0);
             mImeConsumer = mController.getImeSourceConsumer();
         });
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index b8f0d5c..1568174 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -16,7 +16,7 @@
 
 package android.view;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
 import static android.view.InsetsController.ANIMATION_TYPE_NONE;
 import static android.view.InsetsController.ANIMATION_TYPE_RESIZE;
@@ -171,8 +171,7 @@
             mController.onStateChanged(state);
             mController.calculateInsets(
                     false,
-                    false,
-                    TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+                    TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
                     SOFT_INPUT_ADJUST_RESIZE, 0, 0);
             mController.onFrameChanged(new Rect(0, 0, 100, 100));
         });
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index b06cd39..906d84e 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -16,8 +16,9 @@
 
 package android.view;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.InsetsSource.ID_IME;
 import static android.view.InsetsState.ISIDE_BOTTOM;
 import static android.view.InsetsState.ISIDE_TOP;
@@ -101,7 +102,7 @@
                 .setVisible(true);
         SparseIntArray typeSideMap = new SparseIntArray();
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                false, SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+                SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
                 typeSideMap);
         assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets());
         assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all()));
@@ -120,7 +121,7 @@
                 .setFrame(new Rect(0, 100, 100, 300))
                 .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                false, SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+                SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
                 null);
         assertEquals(100, insets.getStableInsetBottom());
         assertEquals(Insets.of(0, 0, 0, 100), insets.getInsetsIgnoringVisibility(systemBars()));
@@ -139,7 +140,7 @@
                 .setFrame(new Rect(80, 0, 100, 300))
                 .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+                0, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
         assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
         assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
         assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars()));
@@ -154,7 +155,7 @@
                 .setFrame(new Rect(80, 0, 100, 300))
                 .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+                0, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
         assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
         assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.statusBars()));
         assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.navigationBars()));
@@ -169,7 +170,7 @@
                 .setFrame(new Rect(0, 200, 100, 300))
                 .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                false, SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+                SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
                 null);
         assertEquals(0, insets.getSystemWindowInsetBottom());
         assertEquals(100, insets.getInsets(ime()).bottom);
@@ -185,12 +186,12 @@
                 .setFrame(new Rect(0, 200, 100, 300))
                 .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                false, SOFT_INPUT_ADJUST_NOTHING, 0, SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION,
-                WINDOWING_MODE_UNDEFINED, null);
+                SOFT_INPUT_ADJUST_NOTHING, 0, SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION,
+                ACTIVITY_TYPE_UNDEFINED, null);
         assertEquals(100, insets.getSystemWindowInsetTop());
-        insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false,
+        insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
                 SOFT_INPUT_ADJUST_NOTHING, 0, 0 /* legacySystemUiFlags */, TYPE_APPLICATION,
-                WINDOWING_MODE_UNDEFINED, null);
+                ACTIVITY_TYPE_UNDEFINED, null);
         assertEquals(0, insets.getSystemWindowInsetTop());
     }
 
@@ -200,12 +201,12 @@
                 .setFrame(new Rect(0, 0, 100, 100))
                 .setVisible(false);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                false, SOFT_INPUT_ADJUST_NOTHING, FLAG_FULLSCREEN, SYSTEM_UI_FLAG_LAYOUT_STABLE,
-                TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+                SOFT_INPUT_ADJUST_NOTHING, FLAG_FULLSCREEN, SYSTEM_UI_FLAG_LAYOUT_STABLE,
+                TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
         assertEquals(0, insets.getSystemWindowInsetTop());
-        insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false,
+        insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
                 SOFT_INPUT_ADJUST_NOTHING, 0, 0 /* legacySystemUiFlags */, TYPE_APPLICATION,
-                WINDOWING_MODE_UNDEFINED, null);
+                ACTIVITY_TYPE_UNDEFINED, null);
         assertEquals(0, insets.getSystemWindowInsetTop());
     }
 
@@ -213,22 +214,23 @@
     public void testCalculateInsets_flagLayoutNoLimits() {
         mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
                 .setFrame(new Rect(0, 0, 100, 100))
-                .setVisible(true);
+                .setVisible(true)
+                .setFlags(FLAG_FORCE_CONSUMING);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
-                0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+                SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
+                0 /* legacySystemUiFlags */, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
         assertEquals(0, insets.getSystemWindowInsetTop());
         insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
-                0 /* legacySystemUiFlags */, TYPE_SYSTEM_ERROR, WINDOWING_MODE_UNDEFINED, null);
+                SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
+                0 /* legacySystemUiFlags */, TYPE_SYSTEM_ERROR, ACTIVITY_TYPE_UNDEFINED, null);
         assertEquals(100, insets.getSystemWindowInsetTop());
         insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
-                0 /* legacySystemUiFlags */, TYPE_WALLPAPER, WINDOWING_MODE_UNDEFINED, null);
+                SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
+                0 /* legacySystemUiFlags */, TYPE_WALLPAPER, ACTIVITY_TYPE_UNDEFINED, null);
         assertEquals(100, insets.getSystemWindowInsetTop());
         insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
-                0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_FREEFORM, null);
+                SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
+                0 /* legacySystemUiFlags */, TYPE_APPLICATION, ACTIVITY_TYPE_STANDARD, null);
         assertEquals(100, insets.getSystemWindowInsetTop());
     }
 
@@ -243,7 +245,7 @@
                 .setVisible(true);
 
         Insets visibleInsets = mState.calculateVisibleInsets(
-                new Rect(0, 0, 100, 400), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+                new Rect(0, 0, 100, 400), TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
                 SOFT_INPUT_ADJUST_NOTHING, 0 /* windowFlags */);
         assertEquals(Insets.of(0, 300, 0, 0), visibleInsets);
     }
@@ -255,7 +257,7 @@
                 .setVisible(true);
 
         Insets visibleInsets = mState.calculateVisibleInsets(
-                new Rect(0, 0, 150, 400), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+                new Rect(0, 0, 150, 400), TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
                 SOFT_INPUT_ADJUST_NOTHING, 0 /* windowFlags */);
         assertEquals(Insets.of(0, 300, 0, 0), visibleInsets);
     }
@@ -269,7 +271,7 @@
                 .setFrame(new Rect(80, 0, 100, 300))
                 .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+                0, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
         assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
         assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
         assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars()));
@@ -284,7 +286,7 @@
                 .setFrame(new Rect(80, 0, 100, 300))
                 .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
-                false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+                0, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
         assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
         assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
         assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars()));
@@ -292,11 +294,11 @@
 
     @Test
     public void testCalculateInsets_emptyIme() {
-        WindowInsets insets1 = mState.calculateInsets(new Rect(), null, false, false,
-                SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+        WindowInsets insets1 = mState.calculateInsets(new Rect(), null, false,
+                SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
         mState.getOrCreateSource(ID_IME, ime());
-        WindowInsets insets2 = mState.calculateInsets(new Rect(), null, false, false,
-                SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+        WindowInsets insets2 = mState.calculateInsets(new Rect(), null, false,
+                SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
         assertEquals(Insets.NONE, insets1.getInsets(ime()));
         assertEquals(Insets.NONE, insets2.getInsets(ime()));
         assertEquals(insets1, insets2);
@@ -311,8 +313,8 @@
                 .setFrame(new Rect(0, 200, 100, 300))
                 .setVisible(true);
         mState.removeSource(ID_IME);
-        WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false,
-                SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+        WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
+                SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
         assertEquals(0, insets.getSystemWindowInsetBottom());
     }
 
@@ -527,7 +529,7 @@
                 .setFrame(new Rect(0, 100, 100, 300))
                 .setVisible(true);
         Insets visibleInsets = mState.calculateVisibleInsets(
-                new Rect(0, 0, 100, 300), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+                new Rect(0, 0, 100, 300), TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
                 SOFT_INPUT_ADJUST_PAN, 0 /* windowFlags */);
         assertEquals(Insets.of(0, 100, 0, 100), visibleInsets);
     }
@@ -546,7 +548,7 @@
                 .setFrame(new Rect(0, 100, 100, 300))
                 .setVisible(true);
         Insets visibleInsets = mState.calculateVisibleInsets(
-                new Rect(0, 0, 100, 300), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+                new Rect(0, 0, 100, 300), TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
                 SOFT_INPUT_ADJUST_NOTHING, 0 /* windowFlags */);
         assertEquals(Insets.of(0, 100, 0, 0), visibleInsets);
     }
@@ -565,7 +567,7 @@
                 .setFrame(new Rect(0, 100, 100, 300))
                 .setVisible(true);
         Insets visibleInsets = mState.calculateVisibleInsets(
-                new Rect(0, 0, 100, 300), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+                new Rect(0, 0, 100, 300), TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
                 SOFT_INPUT_ADJUST_PAN, FLAG_LAYOUT_NO_LIMITS);
         assertEquals(Insets.NONE, visibleInsets);
     }
@@ -599,8 +601,8 @@
                 new Rect(0, 0, 1, 2),
                 new Rect(197, 296, 200, 300),
                 new Rect(197, 296, 200, 300)));
-        DisplayCutout cutout = mState.calculateInsets(new Rect(1, 1, 199, 300), null, false, false,
-                SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+        DisplayCutout cutout = mState.calculateInsets(new Rect(1, 1, 199, 300), null, false,
+                SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
                 new SparseIntArray()).getDisplayCutout();
         assertEquals(0, cutout.getSafeInsetLeft());
         assertEquals(1, cutout.getSafeInsetTop());
@@ -625,8 +627,8 @@
                 new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 180, 380),
                 new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 380)));
         WindowInsets windowInsets = mState.calculateInsets(new Rect(1, 2, 197, 396), null, false,
-                false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
-                WINDOWING_MODE_UNDEFINED, new SparseIntArray());
+                SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
+                ACTIVITY_TYPE_UNDEFINED, new SparseIntArray());
         assertEquals(new RoundedCorner(POSITION_TOP_LEFT, 10, 9, 8),
                 windowInsets.getRoundedCorner(POSITION_TOP_LEFT));
         assertEquals(new RoundedCorner(POSITION_TOP_RIGHT, 10, 189, 8),
@@ -642,8 +644,8 @@
         mState.setDisplayFrame(new Rect(0, 0, 200, 400));
         mState.setDisplayShape(DisplayShape.createDefaultDisplayShape(200, 400, false));
         WindowInsets windowInsets = mState.calculateInsets(new Rect(10, 20, 200, 400), null, false,
-                false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
-                WINDOWING_MODE_UNDEFINED, new SparseIntArray());
+                SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
+                ACTIVITY_TYPE_UNDEFINED, new SparseIntArray());
 
         final DisplayShape expect =
                 DisplayShape.createDefaultDisplayShape(200, 400, false).setOffset(-10, -20);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index 72702e7..b828aac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.util.Slog;
 import android.view.IDisplayChangeWindowCallback;
 import android.view.IDisplayChangeWindowController;
@@ -40,6 +41,7 @@
  */
 public class DisplayChangeController {
     private static final String TAG = DisplayChangeController.class.getSimpleName();
+    private static final String HANDLE_DISPLAY_CHANGE_TRACE_TAG = "HandleRemoteDisplayChange";
 
     private final ShellExecutor mMainExecutor;
     private final IWindowManager mWmService;
@@ -81,9 +83,15 @@
     /** Query all listeners for changes that should happen on display change. */
     void dispatchOnDisplayChange(WindowContainerTransaction outWct, int displayId,
             int fromRotation, int toRotation, DisplayAreaInfo newDisplayAreaInfo) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+            Trace.beginSection("dispatchOnDisplayChange");
+        }
         for (OnDisplayChangingListener c : mDisplayChangeListener) {
             c.onDisplayChange(displayId, fromRotation, toRotation, newDisplayAreaInfo, outWct);
         }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+            Trace.endSection();
+        }
     }
 
     private void onDisplayChange(int displayId, int fromRotation, int toRotation,
@@ -94,6 +102,10 @@
             callback.continueDisplayChange(t);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to continue handling display change", e);
+        } finally {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+                Trace.endAsyncSection(HANDLE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+            }
         }
     }
 
@@ -103,6 +115,9 @@
         @Override
         public void onDisplayChange(int displayId, int fromRotation, int toRotation,
                 DisplayAreaInfo newDisplayAreaInfo, IDisplayChangeWindowCallback callback) {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+                Trace.beginAsyncSection(HANDLE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+            }
             mMainExecutor.execute(() -> DisplayChangeController.this
                     .onDisplayChange(displayId, fromRotation, toRotation,
                             newDisplayAreaInfo, callback));
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 3f013de..61b5fd5 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -29,6 +29,7 @@
 import android.media.audio.common.AidlConversion;
 import android.media.audiofx.AudioEffect;
 import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
 import android.media.audiopolicy.AudioProductStrategy;
 import android.os.Build;
 import android.os.IBinder;
@@ -1955,6 +1956,11 @@
     /** @hide */
     public static native int registerPolicyMixes(ArrayList<AudioMix> mixes, boolean register);
 
+    /** @hide */
+    public static native int updatePolicyMixes(
+            AudioMix[] mixes,
+            AudioMixingRule[] updatedMixingRules);
+
     /** @hide see AudioPolicy.setUidDeviceAffinities() */
     public static native int setUidDeviceAffinities(int uid, @NonNull int[] types,
             @NonNull String[] addresses);
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index e45ef40..0e7718b 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -54,6 +54,8 @@
 import android.media.PlayerBase;
 import android.media.VolumeInfo;
 import android.media.VolumePolicy;
+import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
 import android.media.audiopolicy.AudioPolicyConfig;
 import android.media.audiopolicy.AudioProductStrategy;
 import android.media.audiopolicy.AudioVolumeGroup;
@@ -356,6 +358,11 @@
 
     int removeMixForPolicy(in AudioPolicyConfig policyConfig, in IAudioPolicyCallback pcb);
 
+    @EnforcePermission("MODIFY_AUDIO_ROUTING")
+    int updateMixingRulesForPolicy(in AudioMix[] mixesToUpdate,
+                                   in AudioMixingRule[] updatedMixingRules,
+                                   in IAudioPolicyCallback pcb);
+
     int setFocusPropertiesForPolicy(int duckingBehavior, in IAudioPolicyCallback pcb);
 
     void setVolumePolicy(in VolumePolicy policy);
diff --git a/media/java/android/media/audiopolicy/AudioMix.aidl b/media/java/android/media/audiopolicy/AudioMix.aidl
new file mode 100644
index 0000000..d17a644
--- /dev/null
+++ b/media/java/android/media/audiopolicy/AudioMix.aidl
@@ -0,0 +1,3 @@
+package android.media.audiopolicy;
+
+parcelable AudioMix;
\ No newline at end of file
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index d0270d3..48b4766 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -21,12 +21,15 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 import android.media.AudioSystem;
 import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -38,12 +41,12 @@
  * @hide
  */
 @SystemApi
-public class AudioMix {
+public class AudioMix implements Parcelable {
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private AudioMixingRule mRule;
+    private @NonNull AudioMixingRule mRule;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private AudioFormat mFormat;
+    private @NonNull AudioFormat mFormat;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private int mRouteFlags;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -54,7 +57,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     int mCallbackFlags;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    String mDeviceAddress;
+    @NonNull String mDeviceAddress;
 
     // initialized in constructor, read by AudioPolicyConfig
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -63,10 +66,11 @@
     /**
      * All parameters are guaranteed valid through the Builder.
      */
-    private AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags, int callbackFlags,
-            int deviceType, String deviceAddress) {
-        mRule = rule;
-        mFormat = format;
+    private AudioMix(@NonNull AudioMixingRule rule, @NonNull AudioFormat format,
+            int routeFlags, int callbackFlags,
+            int deviceType, @Nullable String deviceAddress) {
+        mRule = Objects.requireNonNull(rule);
+        mFormat = Objects.requireNonNull(format);
         mRouteFlags = routeFlags;
         mMixType = rule.getTargetMixType();
         mCallbackFlags = callbackFlags;
@@ -187,6 +191,15 @@
     }
 
     /** @hide */
+    public void setAudioMixingRule(@NonNull AudioMixingRule rule) {
+        if (mRule.getTargetMixType() != rule.getTargetMixType()) {
+            throw new UnsupportedOperationException(
+                    "Target mix role of updated rule doesn't match the mix role of the AudioMix");
+        }
+        mRule = Objects.requireNonNull(rule);
+    }
+
+    /** @hide */
     public String getRegistration() {
         return mDeviceAddress;
     }
@@ -269,6 +282,49 @@
         return Objects.hash(mRouteFlags, mRule, mMixType, mFormat);
     }
 
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // write mix route flags
+        dest.writeInt(mRouteFlags);
+        // write callback flags
+        dest.writeInt(mCallbackFlags);
+        // write device information
+        dest.writeInt(mDeviceSystemType);
+        dest.writeString8(mDeviceAddress);
+        mFormat.writeToParcel(dest, flags);
+        mRule.writeToParcel(dest, flags);
+    }
+
+    public static final @NonNull Parcelable.Creator<AudioMix> CREATOR = new Parcelable.Creator<>() {
+        /**
+         * Rebuilds an AudioMix previously stored with writeToParcel().
+         *
+         * @param p Parcel object to read the AudioMix from
+         * @return a new AudioMix created from the data in the parcel
+         */
+        public AudioMix createFromParcel(Parcel p) {
+            final AudioMix.Builder mixBuilder = new AudioMix.Builder();
+            // read mix route flags
+            mixBuilder.setRouteFlags(p.readInt());
+            // read callback flags
+            mixBuilder.setCallbackFlags(p.readInt());
+            // read device information
+            mixBuilder.setDevice(p.readInt(), p.readString8());
+            mixBuilder.setFormat(AudioFormat.CREATOR.createFromParcel(p));
+            mixBuilder.setMixingRule(AudioMixingRule.CREATOR.createFromParcel(p));
+            return mixBuilder.build();
+        }
+
+        public AudioMix[] newArray(int size) {
+            return new AudioMix[size];
+        }
+    };
+
     /** @hide */
     @IntDef(flag = true,
             value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } )
@@ -298,7 +354,7 @@
          * @param rule a non-null {@link AudioMixingRule} instance.
          * @throws IllegalArgumentException
          */
-        public Builder(AudioMixingRule rule)
+        public Builder(@NonNull AudioMixingRule rule)
                 throws IllegalArgumentException {
             if (rule == null) {
                 throw new IllegalArgumentException("Illegal null AudioMixingRule argument");
@@ -313,7 +369,7 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
-        Builder setMixingRule(AudioMixingRule rule)
+        Builder setMixingRule(@NonNull AudioMixingRule rule)
                 throws IllegalArgumentException {
             if (rule == null) {
                 throw new IllegalArgumentException("Illegal null AudioMixingRule argument");
@@ -358,7 +414,7 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
-        public Builder setFormat(AudioFormat format)
+        public Builder setFormat(@NonNull AudioFormat format)
                 throws IllegalArgumentException {
             if (format == null) {
                 throw new IllegalArgumentException("Illegal null AudioFormat argument");
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.aidl b/media/java/android/media/audiopolicy/AudioMixingRule.aidl
new file mode 100644
index 0000000..5c06538
--- /dev/null
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.aidl
@@ -0,0 +1,3 @@
+package android.media.audiopolicy;
+
+parcelable AudioMixingRule;
\ No newline at end of file
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index 9c0b825f..e5debb8 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -26,8 +26,11 @@
 import android.media.MediaRecorder;
 import android.os.Build;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.lang.annotation.Retention;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -50,7 +53,7 @@
  * </pre>
  */
 @SystemApi
-public class AudioMixingRule {
+public class AudioMixingRule implements Parcelable {
 
     private AudioMixingRule(int mixType, Collection<AudioMixMatchCriterion> criteria,
                             boolean allowPrivilegedMediaPlaybackCapture,
@@ -130,7 +133,7 @@
             RULE_EXCLUSION_MASK | RULE_MATCH_AUDIO_SESSION_ID;
 
     /** @hide */
-    public static final class AudioMixMatchCriterion {
+    public static final class AudioMixMatchCriterion implements Parcelable {
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         final AudioAttributes mAttr;
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -139,18 +142,44 @@
         final int mRule;
 
         /** input parameters must be valid */
-        AudioMixMatchCriterion(AudioAttributes attributes, int rule) {
+        @VisibleForTesting
+        public AudioMixMatchCriterion(AudioAttributes attributes, int rule) {
             mAttr = attributes;
             mIntProp = Integer.MIN_VALUE;
             mRule = rule;
         }
         /** input parameters must be valid */
-        AudioMixMatchCriterion(Integer intProp, int rule) {
+        @VisibleForTesting
+        public AudioMixMatchCriterion(Integer intProp, int rule) {
             mAttr = null;
             mIntProp = intProp.intValue();
             mRule = rule;
         }
 
+        private AudioMixMatchCriterion(@NonNull Parcel in) {
+            Objects.requireNonNull(in);
+            mRule = in.readInt();
+            final int match_rule = mRule & ~RULE_EXCLUSION_MASK;
+            switch (match_rule) {
+                case RULE_MATCH_ATTRIBUTE_USAGE:
+                case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
+                    mAttr = AudioAttributes.CREATOR.createFromParcel(in);
+                    mIntProp = Integer.MIN_VALUE;
+                    break;
+                case RULE_MATCH_UID:
+                case RULE_MATCH_USERID:
+                case RULE_MATCH_AUDIO_SESSION_ID:
+                    mIntProp = in.readInt();
+                    mAttr = null;
+                    break;
+                default:
+                    // assume there was in int value to read as for now they come in pair
+                    in.readInt();
+                    throw new IllegalArgumentException(
+                            "Illegal rule value " + mRule + " in parcel");
+            }
+        }
+
         @Override
         public int hashCode() {
             return Objects.hash(mAttr, mIntProp, mRule);
@@ -170,7 +199,13 @@
                     && Objects.equals(mAttr, other.mAttr);
         }
 
-        void writeToParcel(Parcel dest) {
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeInt(mRule);
             final int match_rule = mRule & ~RULE_EXCLUSION_MASK;
             switch (match_rule) {
@@ -190,6 +225,22 @@
             }
         }
 
+        public static final @NonNull Parcelable.Creator<AudioMixMatchCriterion> CREATOR =
+                new Parcelable.Creator<>() {
+            /**
+             * Rebuilds an AudioMixMatchCriterion previously stored with writeToParcel().
+             *
+             * @param p Parcel object to read the AudioMix from
+             * @return a new AudioMixMatchCriterion created from the data in the parcel
+             */
+            public AudioMixMatchCriterion createFromParcel(Parcel p) {
+                return new AudioMixMatchCriterion(p);
+            }
+            public AudioMixMatchCriterion[] newArray(int size) {
+                return new AudioMixMatchCriterion[size];
+            }
+        };
+
         public AudioAttributes getAudioAttributes() { return mAttr; }
         public int getIntProp() { return mIntProp; }
         public int getRule() { return mRule; }
@@ -605,13 +656,14 @@
                 if (!(property instanceof AudioAttributes)) {
                     throw new IllegalArgumentException("Invalid AudioAttributes argument");
                 }
-                return addRuleInternal((AudioAttributes) property, null, rule);
+                return addRuleInternal(
+                        new AudioMixMatchCriterion((AudioAttributes) property, rule));
             } else {
                 // implies integer match rule
                 if (!(property instanceof Integer)) {
                     throw new IllegalArgumentException("Invalid Integer argument");
                 }
-                return addRuleInternal(null, (Integer) property, rule);
+                return addRuleInternal(new AudioMixMatchCriterion((Integer) property, rule));
             }
         }
 
@@ -636,12 +688,13 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
-        private Builder addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)
+        private Builder addRuleInternal(AudioMixMatchCriterion criterion)
                 throws IllegalArgumentException {
             // If mix type is invalid and added rule is valid only for the players / recorders,
             // adjust the mix type accordingly.
             // Otherwise, if the mix type was already deduced or set explicitly, verify the rule
             // is valid for the mix type.
+            final int rule = criterion.mRule;
             if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
                 if (isPlayerRule(rule)) {
                     mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
@@ -655,51 +708,16 @@
             }
             synchronized (mCriteria) {
                 int oppositeRule = rule ^ RULE_EXCLUSION_MASK;
-                if (mCriteria.stream().anyMatch(criterion -> criterion.mRule == oppositeRule)) {
+                if (mCriteria.stream().anyMatch(
+                        otherCriterion -> otherCriterion.mRule == oppositeRule)) {
                     throw new IllegalArgumentException("AudioMixingRule cannot contain RULE_MATCH_*"
                             + " and RULE_EXCLUDE_* for the same dimension.");
                 }
-                int ruleWithoutExclusion = rule & ~RULE_EXCLUSION_MASK;
-                switch (ruleWithoutExclusion) {
-                    case RULE_MATCH_ATTRIBUTE_USAGE:
-                    case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
-                        mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule));
-                        break;
-                    case RULE_MATCH_UID:
-                    case RULE_MATCH_USERID:
-                    case RULE_MATCH_AUDIO_SESSION_ID:
-                        mCriteria.add(new AudioMixMatchCriterion(intProp, rule));
-                        break;
-                    default:
-                        throw new IllegalStateException("Unreachable code in addRuleInternal()");
-                }
+                mCriteria.add(criterion);
             }
             return this;
         }
 
-        Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException {
-            final int rule = in.readInt();
-            final int match_rule = rule & ~RULE_EXCLUSION_MASK;
-            AudioAttributes attr = null;
-            Integer intProp = null;
-            switch (match_rule) {
-                case RULE_MATCH_ATTRIBUTE_USAGE:
-                case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
-                    attr =  AudioAttributes.CREATOR.createFromParcel(in);
-                    break;
-                case RULE_MATCH_UID:
-                case RULE_MATCH_USERID:
-                case RULE_MATCH_AUDIO_SESSION_ID:
-                    intProp = new Integer(in.readInt());
-                    break;
-                default:
-                    // assume there was in int value to read as for now they come in pair
-                    in.readInt();
-                    throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel");
-            }
-            return addRuleInternal(attr, intProp, rule);
-        }
-
         /**
          * Combines all of the matching and exclusion rules that have been set and return a new
          * {@link AudioMixingRule} object.
@@ -717,4 +735,52 @@
                     mVoiceCommunicationCaptureAllowed);
         }
     }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // write opt-out respect
+        dest.writeBoolean(mAllowPrivilegedPlaybackCapture);
+        // write voice communication capture allowed flag
+        dest.writeBoolean(mVoiceCommunicationCaptureAllowed);
+        // write specified mix type
+        dest.writeInt(mTargetMixType);
+        // write mix rules
+        dest.writeInt(mCriteria.size());
+        for (AudioMixingRule.AudioMixMatchCriterion criterion : mCriteria) {
+            criterion.writeToParcel(dest, flags);
+        }
+    }
+
+    public static final @NonNull Parcelable.Creator<AudioMixingRule> CREATOR =
+            new Parcelable.Creator<>() {
+
+        @Override
+        public AudioMixingRule createFromParcel(Parcel source) {
+            AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
+            // read opt-out respect
+            ruleBuilder.allowPrivilegedPlaybackCapture(source.readBoolean());
+            // read voice capture allowed flag
+            ruleBuilder.voiceCommunicationCaptureAllowed(source.readBoolean());
+            // read specified mix type
+            ruleBuilder.setTargetMixRole(source.readInt());
+            // read mix rules
+            int nbRules = source.readInt();
+            for (int j = 0; j < nbRules; j++) {
+                // read the matching rules
+                ruleBuilder.addRuleInternal(
+                        AudioMixMatchCriterion.CREATOR.createFromParcel(source));
+            }
+            return ruleBuilder.build();
+        }
+
+        @Override
+        public AudioMixingRule[] newArray(int size) {
+            return new AudioMixingRule[size];
+        }
+    };
 }
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 3e5de82..e9a6ed4 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -44,6 +44,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -408,6 +409,39 @@
     }
 
     /**
+     * Update {@link AudioMixingRule}-s of already registered {@link AudioMix}-es.
+     *
+     * @param mixingRuleUpdates - {@link List} of {@link Pair}-s, each pair containing
+     *  {@link AudioMix} to update and its new corresponding {@link AudioMixingRule}.
+     *
+     * @return {@link AudioManager#SUCCESS} if the update was successful,
+     *  {@link AudioManager#ERROR} otherwise.
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public int updateMixingRules(
+            @NonNull List<Pair<AudioMix, AudioMixingRule>> mixingRuleUpdates) {
+        Objects.requireNonNull(mixingRuleUpdates);
+
+        IAudioService service = getService();
+        try {
+            synchronized (mLock) {
+                final int status = service.updateMixingRulesForPolicy(
+                        mixingRuleUpdates.stream().map(p -> p.first).toArray(AudioMix[]::new),
+                        mixingRuleUpdates.stream().map(p -> p.second).toArray(
+                                AudioMixingRule[]::new),
+                        cb());
+                if (status == AudioManager.SUCCESS) {
+                    mConfig.updateMixingRules(mixingRuleUpdates);
+                }
+                return status;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Received remote exeception in updateMixingRules call: ", e);
+            return AudioManager.ERROR;
+        }
+    }
+
+    /**
      * @hide
      * Configures the audio framework so that all audio streams originating from the given UID
      * can only come from a set of audio devices.
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index 7a85d21..d277c7d 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -17,16 +17,17 @@
 package android.media.audiopolicy;
 
 import android.annotation.NonNull;
-import android.media.AudioFormat;
 import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -85,72 +86,20 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mMixes.size());
         for (AudioMix mix : mMixes) {
-            // write mix route flags
-            dest.writeInt(mix.getRouteFlags());
-            // write callback flags
-            dest.writeInt(mix.mCallbackFlags);
-            // write device information
-            dest.writeInt(mix.mDeviceSystemType);
-            dest.writeString(mix.mDeviceAddress);
-            // write mix format
-            dest.writeInt(mix.getFormat().getSampleRate());
-            dest.writeInt(mix.getFormat().getEncoding());
-            dest.writeInt(mix.getFormat().getChannelMask());
-            // write opt-out respect
-            dest.writeBoolean(mix.getRule().allowPrivilegedMediaPlaybackCapture());
-            // write voice communication capture allowed flag
-            dest.writeBoolean(mix.getRule().voiceCommunicationCaptureAllowed());
-            // write specified mix type
-            dest.writeInt(mix.getRule().getTargetMixRole());
-            // write mix rules
-            final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
-            dest.writeInt(criteria.size());
-            for (AudioMixMatchCriterion criterion : criteria) {
-                criterion.writeToParcel(dest);
-            }
+            mix.writeToParcel(dest, flags);
         }
     }
 
     private AudioPolicyConfig(Parcel in) {
-        mMixes = new ArrayList<AudioMix>();
         int nbMixes = in.readInt();
+        mMixes = new ArrayList<>(nbMixes);
         for (int i = 0 ; i < nbMixes ; i++) {
-            final AudioMix.Builder mixBuilder = new AudioMix.Builder();
-            // read mix route flags
-            int routeFlags = in.readInt();
-            mixBuilder.setRouteFlags(routeFlags);
-            // read callback flags
-            mixBuilder.setCallbackFlags(in.readInt());
-            // read device information
-            mixBuilder.setDevice(in.readInt(), in.readString());
-            // read mix format
-            int sampleRate = in.readInt();
-            int encoding = in.readInt();
-            int channelMask = in.readInt();
-            final AudioFormat format = new AudioFormat.Builder().setSampleRate(sampleRate)
-                    .setChannelMask(channelMask).setEncoding(encoding).build();
-            mixBuilder.setFormat(format);
-
-            AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
-            // read opt-out respect
-            ruleBuilder.allowPrivilegedPlaybackCapture(in.readBoolean());
-            // read voice capture allowed flag
-            ruleBuilder.voiceCommunicationCaptureAllowed(in.readBoolean());
-            // read specified mix type
-            ruleBuilder.setTargetMixRole(in.readInt());
-            // read mix rules
-            int nbRules = in.readInt();
-            for (int j = 0 ; j < nbRules ; j++) {
-                // read the matching rules
-                ruleBuilder.addRuleFromParcel(in);
-            }
-            mixBuilder.setMixingRule(ruleBuilder.build());
-            mMixes.add(mixBuilder.build());
+            mMixes.add(AudioMix.CREATOR.createFromParcel(in));
         }
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<AudioPolicyConfig> CREATOR
-            = new Parcelable.Creator<AudioPolicyConfig>() {
+    public static final @android.annotation.NonNull Parcelable.Creator<AudioPolicyConfig> CREATOR =
+            new Parcelable.Creator<>() {
         /**
          * Rebuilds an AudioPolicyConfig previously stored with writeToParcel().
          * @param p Parcel object to read the AudioPolicyConfig from
@@ -309,6 +258,23 @@
         }
     }
 
+    /**
+     * Update audio mixing rules for already registered {@link AudioMix}-es.
+     *
+     * @param audioMixingRuleUpdates {@link List} of {@link Pair}-s containing {@link AudioMix} to
+     *                                           be updated and the new {@link AudioMixingRule}.
+     */
+    public void updateMixingRules(
+            @NonNull List<Pair<AudioMix, AudioMixingRule>> audioMixingRuleUpdates) {
+        Objects.requireNonNull(audioMixingRuleUpdates).forEach(
+                update -> updateMixingRule(update.first, update.second));
+    }
+
+    private void updateMixingRule(AudioMix audioMixToUpdate, AudioMixingRule audioMixingRule) {
+        mMixes.stream().filter(audioMixToUpdate::equals).findAny().ifPresent(
+                mix -> mix.setAudioMixingRule(audioMixingRule));
+    }
+
     private static String mixTypeId(int type) {
         if (type == AudioMix.MIX_TYPE_PLAYERS) return "p";
         else if (type == AudioMix.MIX_TYPE_RECORDERS) return "r";
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java
deleted file mode 100644
index a26398a..0000000
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright (C) 2023 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.audiopolicytest;
-
-import static android.media.AudioFormat.CHANNEL_OUT_MONO;
-import static android.media.AudioFormat.CHANNEL_OUT_STEREO;
-import static android.media.AudioFormat.ENCODING_PCM_16BIT;
-import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_INJECTOR;
-import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_PLAYERS;
-import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID;
-import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_UID;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
-
-import android.media.AudioFormat;
-import android.media.AudioSystem;
-import android.media.audiopolicy.AudioMix;
-import android.media.audiopolicy.AudioMixingRule;
-import android.media.audiopolicy.AudioPolicyConfig;
-import android.os.Parcel;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.google.common.testing.EqualsTester;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Unit tests for AudioMix.
- *
- * Run with "atest AudioMixUnitTests".
- */
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class AudioMixUnitTests {
-    private static final AudioFormat OUTPUT_FORMAT_STEREO_44KHZ_PCM =
-            new AudioFormat.Builder()
-                    .setSampleRate(44000)
-                    .setChannelMask(CHANNEL_OUT_STEREO)
-                    .setEncoding(ENCODING_PCM_16BIT).build();
-    private static final AudioFormat OUTPUT_FORMAT_MONO_16KHZ_PCM =
-            new AudioFormat.Builder()
-                    .setSampleRate(16000)
-                    .setChannelMask(CHANNEL_OUT_MONO)
-                    .setEncoding(ENCODING_PCM_16BIT).build();
-    private static final AudioFormat INPUT_FORMAT_MONO_16KHZ_PCM =
-            new AudioFormat.Builder()
-                    .setSampleRate(16000)
-                    .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
-                    .setEncoding(ENCODING_PCM_16BIT).build();
-
-    @Test
-    public void testEquals() {
-        final EqualsTester equalsTester = new EqualsTester();
-
-        // --- Equality group 1
-        final AudioMix playbackAudioMixWithSessionId42AndUid123 =
-                new AudioMix.Builder(new AudioMixingRule.Builder()
-                        .setTargetMixRole(MIX_ROLE_PLAYERS)
-                        .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
-                        .addMixRule(RULE_MATCH_UID, 123).build())
-                        .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM)
-                        .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
-        final AudioMix playbackAudioMixWithUid123AndSessionId42 =
-                new AudioMix.Builder(new AudioMixingRule.Builder()
-                        .setTargetMixRole(MIX_ROLE_PLAYERS)
-                        .addMixRule(RULE_MATCH_UID, 123)
-                        .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42).build())
-                        .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM)
-                        .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
-        equalsTester.addEqualityGroup(
-                playbackAudioMixWithSessionId42AndUid123,
-                playbackAudioMixWithUid123AndSessionId42,
-                writeToAndFromParcel(playbackAudioMixWithSessionId42AndUid123),
-                writeToAndFromParcel(playbackAudioMixWithUid123AndSessionId42));
-
-        // --- Equality group 2
-        final AudioMix recordingAudioMixWithSessionId42AndUid123 =
-                new AudioMix.Builder(new AudioMixingRule.Builder()
-                        .setTargetMixRole(MIX_ROLE_INJECTOR)
-                        .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
-                        .addMixRule(RULE_MATCH_UID, 123).build())
-                        .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
-                        .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
-        final AudioMix recordingAudioMixWithUid123AndSessionId42 =
-                new AudioMix.Builder(new AudioMixingRule.Builder()
-                        .setTargetMixRole(MIX_ROLE_INJECTOR)
-                        .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
-                        .addMixRule(RULE_MATCH_UID, 123).build())
-                        .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
-                        .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
-        equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123,
-                recordingAudioMixWithUid123AndSessionId42,
-                writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123),
-                writeToAndFromParcel(recordingAudioMixWithUid123AndSessionId42));
-
-        // --- Equality group 3
-        final AudioMix recordingAudioMixWithSessionId42AndUid123Render =
-                new AudioMix.Builder(new AudioMixingRule.Builder()
-                        .setTargetMixRole(MIX_ROLE_PLAYERS)
-                        .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
-                        .addMixRule(RULE_MATCH_UID, 123).build())
-                        .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
-                        .setRouteFlags(
-                                AudioMix.ROUTE_FLAG_LOOP_BACK | AudioMix.ROUTE_FLAG_RENDER).build();
-        equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123Render,
-                writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123Render));
-
-        // --- Equality group 4
-        final AudioMix playbackAudioMixWithUid123 =
-                new AudioMix.Builder(new AudioMixingRule.Builder()
-                        .setTargetMixRole(MIX_ROLE_PLAYERS)
-                        .addMixRule(RULE_MATCH_UID, 123).build())
-                        .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
-                        .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
-        equalsTester.addEqualityGroup(playbackAudioMixWithUid123,
-                writeToAndFromParcel(playbackAudioMixWithUid123));
-
-        // --- Equality group 5
-        final AudioMix playbackAudioMixWithUid42 =
-                new AudioMix.Builder(new AudioMixingRule.Builder()
-                        .setTargetMixRole(MIX_ROLE_PLAYERS)
-                        .addMixRule(RULE_MATCH_UID, 42).build())
-                        .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
-                        .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
-        equalsTester.addEqualityGroup(playbackAudioMixWithUid42,
-                writeToAndFromParcel(playbackAudioMixWithUid42));
-
-        equalsTester.testEquals();
-    }
-
-    @Test
-    public void buildRenderToRemoteSubmix_success() {
-        final String deviceAddress = "address";
-        final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder()
-                .setTargetMixRole(MIX_ROLE_PLAYERS)
-                .addMixRule(RULE_MATCH_UID, 42).build())
-                .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
-                .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
-                .setDevice(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, /*address=*/deviceAddress).build();
-
-        assertEquals(deviceAddress, audioMix.getRegistration());
-        assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat());
-        assertEquals(AudioMix.ROUTE_FLAG_RENDER, audioMix.getRouteFlags());
-    }
-
-    @Test
-    public void buildLoopbackAndRenderToRemoteSubmix_success() {
-        final String deviceAddress = "address";
-        final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder()
-                .setTargetMixRole(MIX_ROLE_PLAYERS)
-                .addMixRule(RULE_MATCH_UID, 42).build())
-                .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
-                .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK_RENDER)
-                .setDevice(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, /*address=*/deviceAddress).build();
-
-        assertEquals(deviceAddress, audioMix.getRegistration());
-        assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat());
-        assertEquals(AudioMix.ROUTE_FLAG_LOOP_BACK_RENDER, audioMix.getRouteFlags());
-    }
-
-    @Test
-    public void buildRenderToSpeaker_success() {
-        final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder()
-                .setTargetMixRole(MIX_ROLE_PLAYERS)
-                .addMixRule(RULE_MATCH_UID, 42).build())
-                .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
-                .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
-                .setDevice(AudioSystem.DEVICE_OUT_SPEAKER, /*address=*/"").build();
-
-        assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat());
-        assertEquals(AudioMix.ROUTE_FLAG_RENDER, audioMix.getRouteFlags());
-    }
-
-    @Test
-    public void buildLoopbackForPlayerMix_success() {
-        final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder()
-                .setTargetMixRole(MIX_ROLE_PLAYERS)
-                .addMixRule(RULE_MATCH_UID, 42).build())
-                .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
-                .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
-
-        assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat());
-        assertEquals(AudioMix.ROUTE_FLAG_LOOP_BACK, audioMix.getRouteFlags());
-    }
-
-    @Test
-    public void buildLoopbackForInjectorMix_success() {
-        final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder()
-                .setTargetMixRole(MIX_ROLE_INJECTOR)
-                .addMixRule(RULE_MATCH_UID, 42).build())
-                .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
-                .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
-
-        assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat());
-        assertEquals(AudioMix.ROUTE_FLAG_LOOP_BACK, audioMix.getRouteFlags());
-    }
-
-    @Test
-    public void buildLoopbackWithIncompatibleDevice_throws() {
-        assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder(
-                new AudioMixingRule.Builder()
-                        .setTargetMixRole(MIX_ROLE_PLAYERS)
-                        .addMixRule(RULE_MATCH_UID, 42).build())
-                .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
-                .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK)
-                .setDevice(AudioSystem.DEVICE_OUT_SPEAKER, /*address=*/"").build());
-    }
-
-    @Test
-    public void buildRenderWithoutDevice_throws() {
-        assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder(
-                new AudioMixingRule.Builder()
-                        .setTargetMixRole(MIX_ROLE_PLAYERS)
-                        .addMixRule(RULE_MATCH_UID, 42).build())
-                .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
-                .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER).build());
-    }
-
-    @Test
-    public void buildRenderWithInputDevice_throws() {
-        assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder(
-                new AudioMixingRule.Builder()
-                        .setTargetMixRole(MIX_ROLE_PLAYERS)
-                        .addMixRule(RULE_MATCH_UID, 42).build())
-                .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
-                .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
-                .setDevice(AudioSystem.DEVICE_IN_BUILTIN_MIC, /*address=*/"").build());
-    }
-
-    @Test
-    public void buildRenderWithInjectorMix_throws() {
-        assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder(
-                new AudioMixingRule.Builder()
-                        .setTargetMixRole(MIX_ROLE_INJECTOR)
-                        .addMixRule(RULE_MATCH_UID, 42).build())
-                .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
-                .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
-                .setDevice(AudioSystem.DEVICE_OUT_SPEAKER, /*address=*/"").build());
-    }
-
-
-
-    private static AudioMix writeToAndFromParcel(AudioMix audioMix) {
-        AudioPolicyConfig apc = new AudioPolicyConfig(new ArrayList<>(List.of(audioMix)));
-        Parcel parcel = Parcel.obtain();
-        apc.writeToParcel(parcel, /*flags=*/0);
-        parcel.setDataPosition(0);
-        AudioMix unmarshalledMix =
-                AudioPolicyConfig.CREATOR.createFromParcel(parcel).getMixes().get(0);
-        parcel.recycle();
-        return unmarshalledMix;
-    }
-}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
deleted file mode 100644
index 3cbfd50..0000000
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Copyright (C) 2022 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.audiopolicytest;
-
-import static android.media.AudioAttributes.USAGE_MEDIA;
-import static android.media.MediaRecorder.AudioSource.VOICE_RECOGNITION;
-import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_INJECTOR;
-import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_PLAYERS;
-import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET;
-import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE;
-import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_AUDIO_SESSION_ID;
-import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_UID;
-import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
-import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE;
-import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID;
-import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_UID;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
-import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
-
-
-import android.media.AudioAttributes;
-import android.media.audiopolicy.AudioMixingRule;
-import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.hamcrest.CustomTypeSafeMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Unit tests for AudioPolicy.
- *
- * Run with "atest AudioMixingRuleUnitTests".
- */
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class AudioMixingRuleUnitTests {
-    private static final AudioAttributes USAGE_MEDIA_AUDIO_ATTRIBUTES =
-            new AudioAttributes.Builder().setUsage(USAGE_MEDIA).build();
-    private static final AudioAttributes CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES =
-            new AudioAttributes.Builder().setCapturePreset(VOICE_RECOGNITION).build();
-    private static final int TEST_UID = 42;
-    private static final int OTHER_UID = 77;
-    private static final int TEST_SESSION_ID = 1234;
-
-    @Test
-    public void testConstructValidRule() {
-        AudioMixingRule rule = new AudioMixingRule.Builder()
-                .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
-                .addMixRule(RULE_MATCH_UID, TEST_UID)
-                .excludeMixRule(RULE_MATCH_AUDIO_SESSION_ID, TEST_SESSION_ID)
-                .build();
-
-        // Based on the rules, the mix type should fall back to MIX_ROLE_PLAYERS,
-        // since the rules are valid for both MIX_ROLE_PLAYERS & MIX_ROLE_INJECTOR.
-        assertEquals(rule.getTargetMixRole(), MIX_ROLE_PLAYERS);
-        assertThat(rule.getCriteria(), containsInAnyOrder(
-                isAudioMixMatchUsageCriterion(USAGE_MEDIA),
-                isAudioMixMatchUidCriterion(TEST_UID),
-                isAudioMixExcludeSessionCriterion(TEST_SESSION_ID)));
-    }
-
-    @Test
-    public void testConstructRuleWithConflictingCriteriaFails() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new AudioMixingRule.Builder()
-                        .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
-                        .addMixRule(RULE_MATCH_UID, TEST_UID)
-                        // Conflicts with previous criterion.
-                        .addMixRule(RULE_EXCLUDE_UID, OTHER_UID)
-                        .build());
-    }
-
-    @Test
-    public void testRuleBuilderDedupsCriteria() {
-        AudioMixingRule rule = new AudioMixingRule.Builder()
-                .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
-                .addMixRule(RULE_MATCH_UID, TEST_UID)
-                // Identical to previous criterion.
-                .addMixRule(RULE_MATCH_UID, TEST_UID)
-                // Identical to first criterion.
-                .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
-                .build();
-
-        assertThat(rule.getCriteria(), hasSize(2));
-        assertThat(rule.getCriteria(), containsInAnyOrder(
-                isAudioMixMatchUsageCriterion(USAGE_MEDIA),
-                isAudioMixMatchUidCriterion(TEST_UID)));
-    }
-
-    @Test
-    public void failsWhenAddAttributeRuleCalledWithInvalidType() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new AudioMixingRule.Builder()
-                        // Rule match attribute usage requires AudioAttributes, not
-                        // just the int enum value of the usage.
-                        .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA)
-                        .build());
-    }
-
-    @Test
-    public void failsWhenExcludeAttributeRuleCalledWithInvalidType() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new AudioMixingRule.Builder()
-                        // Rule match attribute usage requires AudioAttributes, not
-                        // just the int enum value of the usage.
-                        .excludeMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA)
-                        .build());
-    }
-
-    @Test
-    public void failsWhenAddIntRuleCalledWithInvalidType() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new AudioMixingRule.Builder()
-                        // Rule match uid requires Integer not AudioAttributes.
-                        .addMixRule(RULE_MATCH_UID, USAGE_MEDIA_AUDIO_ATTRIBUTES)
-                        .build());
-    }
-
-    @Test
-    public void failsWhenExcludeIntRuleCalledWithInvalidType() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new AudioMixingRule.Builder()
-                        // Rule match uid requires Integer not AudioAttributes.
-                        .excludeMixRule(RULE_MATCH_UID, USAGE_MEDIA_AUDIO_ATTRIBUTES)
-                        .build());
-    }
-
-    @Test
-    public void injectorMixTypeDeductionWithGenericRuleSucceeds() {
-        AudioMixingRule rule = new AudioMixingRule.Builder()
-                // UID rule can be used both with MIX_ROLE_PLAYERS and MIX_ROLE_INJECTOR.
-                .addMixRule(RULE_MATCH_UID, TEST_UID)
-                // Capture preset rule is only valid for injector, MIX_ROLE_INJECTOR should
-                // be deduced.
-                .addMixRule(RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
-                        CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES)
-                .build();
-
-        assertEquals(rule.getTargetMixRole(), MIX_ROLE_INJECTOR);
-        assertThat(rule.getCriteria(), containsInAnyOrder(
-                isAudioMixMatchUidCriterion(TEST_UID),
-                isAudioMixMatchCapturePresetCriterion(VOICE_RECOGNITION)));
-    }
-
-    @Test
-    public void settingTheMixTypeToIncompatibleInjectorMixFails() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new AudioMixingRule.Builder()
-                        .addMixRule(RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
-                                CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES)
-                        // Capture preset cannot be defined for MIX_ROLE_PLAYERS.
-                        .setTargetMixRole(MIX_ROLE_PLAYERS)
-                        .build());
-    }
-
-    @Test
-    public void addingPlayersOnlyRuleWithInjectorsOnlyRuleFails() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new AudioMixingRule.Builder()
-                        // MIX_ROLE_PLAYERS only rule.
-                        .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
-                        // MIX ROLE_INJECTOR only rule.
-                        .addMixRule(RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
-                                CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES)
-                        .build());
-    }
-
-    @Test
-    public void sessionIdRuleCompatibleWithPlayersMix() {
-        int sessionId = 42;
-        AudioMixingRule rule = new AudioMixingRule.Builder()
-                .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, sessionId)
-                .setTargetMixRole(MIX_ROLE_PLAYERS)
-                .build();
-
-        assertEquals(rule.getTargetMixRole(), MIX_ROLE_PLAYERS);
-        assertThat(rule.getCriteria(), containsInAnyOrder(isAudioMixSessionCriterion(sessionId)));
-    }
-
-    @Test
-    public void sessionIdRuleCompatibleWithInjectorMix() {
-        AudioMixingRule rule = new AudioMixingRule.Builder()
-                .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, TEST_SESSION_ID)
-                .setTargetMixRole(MIX_ROLE_INJECTOR)
-                .build();
-
-        assertEquals(rule.getTargetMixRole(), MIX_ROLE_INJECTOR);
-        assertThat(rule.getCriteria(),
-                containsInAnyOrder(isAudioMixSessionCriterion(TEST_SESSION_ID)));
-    }
-
-    @Test
-    public void audioMixingRuleWithNoRulesFails() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new AudioMixingRule.Builder().build());
-    }
-
-
-    private static Matcher isAudioMixUidCriterion(int uid, boolean exclude) {
-        return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("uid mix criterion") {
-            @Override
-            public boolean matchesSafely(AudioMixMatchCriterion item) {
-                int expectedRule = exclude ? RULE_EXCLUDE_UID : RULE_MATCH_UID;
-                return item.getRule() == expectedRule && item.getIntProp() == uid;
-            }
-
-            @Override
-            public void describeMismatchSafely(
-                    AudioMixMatchCriterion item, Description mismatchDescription) {
-                mismatchDescription.appendText(
-                        String.format("is not %s criterion with uid %d",
-                                exclude ? "exclude" : "match", uid));
-            }
-        };
-    }
-
-    private static Matcher isAudioMixMatchUidCriterion(int uid) {
-        return isAudioMixUidCriterion(uid, /*exclude=*/ false);
-    }
-
-    private static Matcher isAudioMixCapturePresetCriterion(int audioSource, boolean exclude) {
-        return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("uid mix criterion") {
-            @Override
-            public boolean matchesSafely(AudioMixMatchCriterion item) {
-                int expectedRule = exclude
-                        ? RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET
-                        : RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
-                AudioAttributes attributes = item.getAudioAttributes();
-                return item.getRule() == expectedRule
-                        && attributes != null && attributes.getCapturePreset() == audioSource;
-            }
-
-            @Override
-            public void describeMismatchSafely(
-                    AudioMixMatchCriterion item, Description mismatchDescription) {
-                mismatchDescription.appendText(
-                        String.format("is not %s criterion with capture preset %d",
-                                exclude ? "exclude" : "match", audioSource));
-            }
-        };
-    }
-
-    private static Matcher isAudioMixMatchCapturePresetCriterion(int audioSource) {
-        return isAudioMixCapturePresetCriterion(audioSource, /*exclude=*/ false);
-    }
-
-    private static Matcher isAudioMixUsageCriterion(int usage, boolean exclude) {
-        return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("usage mix criterion") {
-            @Override
-            public boolean matchesSafely(AudioMixMatchCriterion item) {
-                int expectedRule =
-                        exclude ? RULE_EXCLUDE_ATTRIBUTE_USAGE : RULE_MATCH_ATTRIBUTE_USAGE;
-                AudioAttributes attributes = item.getAudioAttributes();
-                return item.getRule() == expectedRule
-                        && attributes != null && attributes.getUsage() == usage;
-            }
-
-            @Override
-            public void describeMismatchSafely(
-                    AudioMixMatchCriterion item, Description mismatchDescription) {
-                mismatchDescription.appendText(
-                        String.format("is not %s criterion with usage %d",
-                                exclude ? "exclude" : "match", usage));
-            }
-        };
-    }
-
-    private static Matcher isAudioMixMatchUsageCriterion(int usage) {
-        return isAudioMixUsageCriterion(usage, /*exclude=*/ false);
-    }
-
-    private static Matcher isAudioMixSessionCriterion(int sessionId, boolean exclude) {
-        return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("sessionId mix criterion") {
-            @Override
-            public boolean matchesSafely(AudioMixMatchCriterion item) {
-                int excludeRule =
-                        exclude ? RULE_EXCLUDE_AUDIO_SESSION_ID : RULE_MATCH_AUDIO_SESSION_ID;
-                return item.getRule() == excludeRule && item.getIntProp() == sessionId;
-            }
-
-            @Override
-            public void describeMismatchSafely(
-                    AudioMixMatchCriterion item, Description mismatchDescription) {
-                mismatchDescription.appendText(
-                        String.format("is not %s criterion with session id %d",
-                        exclude ? "exclude" : "match", sessionId));
-            }
-        };
-    }
-
-    private static Matcher isAudioMixSessionCriterion(int sessionId) {
-        return isAudioMixSessionCriterion(sessionId, /*exclude=*/ false);
-    }
-
-    private static Matcher isAudioMixExcludeSessionCriterion(int sessionId) {
-        return isAudioMixSessionCriterion(sessionId, /*exclude=*/ true);
-    }
-
-}
diff --git a/packages/EncryptedLocalTransport/Android.bp b/packages/EncryptedLocalTransport/Android.bp
index 09e5630..9ae6d96 100644
--- a/packages/EncryptedLocalTransport/Android.bp
+++ b/packages/EncryptedLocalTransport/Android.bp
@@ -27,9 +27,6 @@
     name: "EncryptedLocalTransport",
     defaults: ["platform_app_defaults"],
     srcs: ["src/**/*.java"],
-    optimize: {
-        proguard_flags_files: ["proguard.flags"],
-    },
     static_libs: ["LocalTransport"],
     platform_apis: true,
     certificate: "platform",
diff --git a/packages/EncryptedLocalTransport/proguard.flags b/packages/EncryptedLocalTransport/proguard.flags
deleted file mode 100644
index e4ce3c5..0000000
--- a/packages/EncryptedLocalTransport/proguard.flags
+++ /dev/null
@@ -1,2 +0,0 @@
--keep class com.android.localTransport.EncryptedLocalTransport
--keep class com.android.localTransport.EncryptedLocalTransportService
diff --git a/packages/LocalTransport/Android.bp b/packages/LocalTransport/Android.bp
index d4fa191..e7a273b 100644
--- a/packages/LocalTransport/Android.bp
+++ b/packages/LocalTransport/Android.bp
@@ -27,9 +27,7 @@
     name: "LocalTransport",
     defaults: ["platform_app_defaults"],
     srcs: ["src/**/*.java"],
-    optimize: {
-        proguard_flags_files: ["proguard.flags"],
-    },
+    libs: ["keepanno-annotations"],
     platform_apis: true,
     certificate: "platform",
     privileged: true,
diff --git a/packages/LocalTransport/proguard.flags b/packages/LocalTransport/proguard.flags
deleted file mode 100644
index c1f51b8..0000000
--- a/packages/LocalTransport/proguard.flags
+++ /dev/null
@@ -1,5 +0,0 @@
--keep class com.android.localTransport.LocalTransport
--keep class com.android.localTransport.LocalTransportParameters
--keep class com.android.localTransport.LocalTransportService
-
-
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 73f8730..933be11 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -38,6 +38,9 @@
 import android.util.Base64;
 import android.util.Log;
 
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+
 import libcore.io.IoUtils;
 
 import java.io.BufferedOutputStream;
@@ -127,6 +130,13 @@
         return mParameters;
     }
 
+
+    @UsesReflection({
+            // As the runtime class name is used to generate the returned name, and the returned
+            // name may be used used with reflection, generate the necessary keep rules.
+            @KeepTarget(classConstant = LocalTransport.class),
+            @KeepTarget(extendsClassConstant = LocalTransport.class)
+    })
     @Override
     public String name() {
         return new ComponentName(mContext, this.getClass()).flattenToShortString();
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 3d35bad..5dcb9d2 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -8,7 +8,6 @@
 }
 
 android_library {
-
     name: "SettingsLib",
 
     static_libs: [
@@ -26,44 +25,20 @@
         "iconloader",
 
         "WifiTrackerLibRes",
-        "SettingsLibHelpUtils",
-        "SettingsLibRestrictedLockUtils",
-        "SettingsLibActionBarShadow",
-        "SettingsLibAppPreference",
-        "SettingsLibSearchWidget",
-        "SettingsLibSettingsSpinner",
-        "SettingsLibIllustrationPreference",
-        "SettingsLibLayoutPreference",
-        "SettingsLibMainSwitchPreference",
-        "SettingsLibActionButtonsPreference",
-        "SettingsLibEntityHeaderWidgets",
-        "SettingsLibBarChartPreference",
-        "SettingsLibProgressBar",
-        "SettingsLibAdaptiveIcon",
-        "SettingsLibRadioButtonPreference",
-        "SettingsLibSelectorWithWidgetPreference",
-        "SettingsLibDisplayUtils",
-        "SettingsLibUtils",
-        "SettingsLibEmergencyNumber",
-        "SettingsLibTopIntroPreference",
-        "SettingsLibBannerMessagePreference",
-        "SettingsLibFooterPreference",
-        "SettingsLibUsageProgressBarPreference",
-        "SettingsLibCollapsingToolbarBaseActivity",
-        "SettingsLibTwoTargetPreference",
-        "SettingsLibSettingsTransition",
-        "SettingsLibButtonPreference",
         "SettingsLibDeviceStateRotationLock",
-        "SettingsLibProfileSelector",
+        "SettingsLibDisplayUtils",
+        "SettingsLibEmergencyNumber",
+        "SettingsLibSearchWidget",
+        "SettingsLibUtils",
+        "SettingsLibWidget",
         "setupdesign",
         "zxing-core-1.7",
         "androidx.room_room-runtime",
         "settingslib_flags_lib",
-
     ],
 
     plugins: ["androidx.room_room-compiler-plugin"],
-
+    use_resource_processor: true,
     resource_dirs: ["res"],
 
     srcs: [
@@ -72,6 +47,43 @@
     ],
 }
 
+// Group all the libraries with namespace "com.android.settingslib.widget", to allow SettingsLib to
+// set use_resource_processor = true.
+// We can remove SettingsLibWidget when all these libraries have its own namespace.
+android_library {
+    name: "SettingsLibWidget",
+    visibility: ["//visibility:private"],
+    manifest: "AndroidManifest-SettingsLibWidget.xml",
+    static_libs: [
+        "SettingsLibActionBarShadow",
+        "SettingsLibActionButtonsPreference",
+        "SettingsLibAdaptiveIcon",
+        "SettingsLibAppPreference",
+        "SettingsLibBannerMessagePreference",
+        "SettingsLibBarChartPreference",
+        "SettingsLibButtonPreference",
+        "SettingsLibCollapsingToolbarBaseActivity",
+        "SettingsLibEntityHeaderWidgets",
+        "SettingsLibFooterPreference",
+        "SettingsLibHelpUtils",
+        "SettingsLibIllustrationPreference",
+        "SettingsLibLayoutPreference",
+        "SettingsLibMainSwitchPreference",
+        "SettingsLibProfileSelector",
+        "SettingsLibProgressBar",
+        "SettingsLibRadioButtonPreference",
+        "SettingsLibRestrictedLockUtils",
+        "SettingsLibSelectorWithWidgetPreference",
+        "SettingsLibSettingsSpinner",
+        "SettingsLibSettingsTransition",
+        "SettingsLibTopIntroPreference",
+        "SettingsLibTwoTargetPreference",
+        "SettingsLibUsageProgressBarPreference",
+    ],
+
+    resource_dirs: [],
+}
+
 // NOTE: Keep this module in sync with ./common.mk
 java_defaults {
     name: "SettingsLibDefaults",
diff --git a/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml b/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml
new file mode 100644
index 0000000..38a7d6a
--- /dev/null
+++ b/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<manifest package="com.android.settingslib.widget" />
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index af06d73..b1d1ea5e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -126,10 +126,10 @@
             switchSummary = isChecked()
                     ? getUpdatableEnterpriseString(
                             getContext(), ENABLED_BY_ADMIN_SWITCH_SUMMARY,
-                            R.string.enabled_by_admin)
+                            com.android.settingslib.widget.R.string.enabled_by_admin)
                     : getUpdatableEnterpriseString(
                             getContext(), DISABLED_BY_ADMIN_SWITCH_SUMMARY,
-                            R.string.disabled_by_admin);
+                            com.android.settingslib.widget.R.string.disabled_by_admin);
         } else {
             switchSummary = mRestrictedSwitchSummary;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 6eb2f38..96bb4b5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1776,7 +1776,8 @@
             final int userId = UserHandle.getUserId(this.info.uid);
             if (UserManager.get(context).isManagedProfile(userId)) {
                 this.labelDescription = context.getString(
-                        com.android.settingslib.R.string.accessibility_work_profile_app_description,
+                        com.android.settingslib.utils.R
+                                .string.accessibility_work_profile_app_description,
                         this.label);
             } else {
                 this.labelDescription = this.label;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java
index cb4eba4..f5257b0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java
@@ -46,7 +46,8 @@
         View layout = View.inflate(mContext, R.layout.broadcast_dialog, null);
         final Window window = getWindow();
         window.setContentView(layout);
-        window.setWindowAnimations(R.style.Theme_AlertDialog_SettingsLib);
+        window.setWindowAnimations(
+                com.android.settingslib.widget.R.style.Theme_AlertDialog_SettingsLib);
 
         TextView title = layout.findViewById(R.id.dialog_title);
         TextView subTitle = layout.findViewById(R.id.dialog_subtitle);
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
index 1f4cafce..d53c3a7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
@@ -153,7 +153,7 @@
         }
         final ImageView icon = holder.itemView.findViewById(android.R.id.icon);
         final int iconSize = getContext().getResources().getDimensionPixelSize(
-                R.dimen.secondary_app_icon_size);
+                com.android.settingslib.widget.R.dimen.secondary_app_icon_size);
         if (icon != null && iconSize > 0) {
             ViewGroup.LayoutParams params = icon.getLayoutParams();
             params.height = iconSize;
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
index ebdfbea..251cd36 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
@@ -32,7 +32,6 @@
 import android.util.Log;
 
 import com.android.launcher3.icons.BaseIconFactory;
-import com.android.settingslib.R;
 import com.android.settingslib.Utils;
 
 /**
@@ -81,7 +80,7 @@
         mPackageManager = pm;
         mIconDrawableFactory = iconDrawableFactory;
         mImportantConversationColor = context.getResources().getColor(
-                R.color.important_conversation, null);
+                com.android.launcher3.icons.R.color.important_conversation, null);
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index 21eb35a..2d6f058 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -167,7 +167,8 @@
         ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon);
         bindFrictionImage(frictionImageView);
 
-        final View divider = view.findViewById(R.id.two_target_divider);
+        final View divider =
+                view.findViewById(com.android.settingslib.widget.R.id.two_target_divider);
         divider.setVisibility(shouldShowDivider() ? View.VISIBLE : View.INVISIBLE);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index c45d774..015356e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -331,7 +331,8 @@
                 return;
             } else if (!isDefaultNetwork && mDefaultNetworkCapabilities != null
                     && mDefaultNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
-                statusLabel = mContext.getString(R.string.wifi_connected_low_quality);
+                statusLabel = mContext.getString(
+                        com.android.wifitrackerlib.R.string.wifi_connected_low_quality);
                 return;
             }
         }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java
index 74c2fc8..32a16716 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java
@@ -54,7 +54,7 @@
         mPreference = new PrimarySwitchPreference(mContext);
         LayoutInflater inflater = LayoutInflater.from(mContext);
         mHolder = PreferenceViewHolder.createInstanceForTests(inflater.inflate(
-                com.android.settingslib.R.layout.preference_two_target, null));
+                com.android.settingslib.widget.R.layout.preference_two_target, null));
         mWidgetView = mHolder.itemView.findViewById(android.R.id.widget_frame);
         inflater.inflate(R.layout.preference_widget_primary_switch, mWidgetView, true);
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
index 06343f5..ff84dc9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
@@ -55,7 +55,8 @@
     @Test
     public void onCreate_userAddedChildViewsBeMovedToContentFrame() {
         CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout();
-        View contentFrameView = layout.findViewById(R.id.content_frame);
+        View contentFrameView =
+                layout.findViewById(com.android.settingslib.widget.R.id.content_frame);
 
         TextView textView = contentFrameView.findViewById(R.id.text_hello_world);
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
index 471dac0..8246aff 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
@@ -29,8 +29,6 @@
 import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
 
-import com.android.settingslib.R;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -95,7 +93,7 @@
         @Override
         protected void onCreate(@Nullable Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
-            setTheme(R.style.Theme_AppCompat);
+            setTheme(androidx.appcompat.R.style.Theme_AppCompat);
             getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
         }
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java
index 7b08fee..2f8967e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java
@@ -30,8 +30,6 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
-import com.android.settingslib.R;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -51,7 +49,7 @@
 
     @Before
     public void setUp() {
-        mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
+        mActivity.setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight);
 
         mController.initialize(mTestUtils.createLearnMoreButtonLauncher());
         mController.updateEnforcedAdmin(ENFORCED_ADMIN, ENFORCEMENT_ADMIN_USER_ID);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
index 7b88566..f168122 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
@@ -33,8 +33,6 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
-import com.android.settingslib.R;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -57,7 +55,7 @@
 
     @Before
     public void setUp() {
-        mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
+        mActivity.setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
index 66a2ea6..6831222 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
@@ -62,7 +62,7 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mActivity = spy(ActivityController.of(new FragmentActivity()).get());
-        mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
+        mActivity.setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight);
         mUnderTest = new TestCreateUserDialogController();
         mPhotoRestrictedByBase = false;
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
index f595cd3..a95257a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
@@ -99,7 +99,7 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mActivity = spy(ActivityController.of(new FragmentActivity()).get());
-        mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
+        mActivity.setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight);
         mController = new TestEditUserInfoController();
         mPhotoRestrictedByBase = false;
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java
index 6cbae05..10862403 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java
@@ -105,15 +105,15 @@
 
         icon.setBackgroundColor(mContext, tile);
 
-        assertThat(icon.mBackgroundColor)
-                .isEqualTo(mContext.getColor(R.color.homepage_generic_icon_background));
+        assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor(
+                com.android.settingslib.widget.R.color.homepage_generic_icon_background));
     }
 
     @Test
     public void onBindTile_externalTileWithBackgroundColorHint_shouldUpdateIcon() {
         final Tile tile = spy(new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE));
         mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT,
-                R.color.bt_outline_color);
+                com.android.settingslib.widget.R.color.bt_outline_color);
         doReturn(Icon.createWithResource(mContext, R.drawable.ic_system_update))
                 .when(tile).getIcon(mContext);
 
@@ -121,8 +121,8 @@
                 new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK));
         icon.setBackgroundColor(mContext, tile);
 
-        assertThat(icon.mBackgroundColor)
-                .isEqualTo(mContext.getColor(R.color.bt_outline_color));
+        assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor(
+                com.android.settingslib.widget.R.color.bt_outline_color));
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java
index 71d55bc..b2bc53d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java
@@ -21,8 +21,6 @@
 import android.content.res.Resources;
 import android.graphics.Paint;
 
-import com.android.settingslib.R;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
index 049c90e..a26f200 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -58,14 +58,15 @@
     @Test
     public void setLearnMoreText_shouldSetAsTextInLearnMore() {
         final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(
-                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null));
+                LayoutInflater.from(mContext)
+                        .inflate(com.android.settingslib.widget.R.layout.preference_footer, null));
         mFooterPreference.setLearnMoreText("Custom learn more");
         mFooterPreference.setLearnMoreAction(view -> { /* do nothing */ } /* listener */);
 
         mFooterPreference.onBindViewHolder(holder);
 
         assertThat(((TextView) holder.findViewById(
-                R.id.settingslib_learn_more)).getText().toString())
+                com.android.settingslib.widget.R.id.settingslib_learn_more)).getText().toString())
                 .isEqualTo("Custom learn more");
     }
 
@@ -94,8 +95,9 @@
     @Test
     public void onBindViewHolder_whenTitleIsNull_shouldNotRaiseNpe() {
         PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
-                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
-        when(viewHolder.findViewById(R.id.title)).thenReturn(null);
+                LayoutInflater.from(mContext)
+                        .inflate(com.android.settingslib.widget.R.layout.preference_footer, null)));
+        when(viewHolder.findViewById(androidx.core.R.id.title)).thenReturn(null);
 
         Throwable actualThrowable = null;
         try {
@@ -110,8 +112,10 @@
     @Test
     public void onBindViewHolder_whenLearnMoreIsNull_shouldNotRaiseNpe() {
         PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
-                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
-        when(viewHolder.findViewById(R.id.settingslib_learn_more)).thenReturn(null);
+                LayoutInflater.from(mContext)
+                        .inflate(com.android.settingslib.widget.R.layout.preference_footer, null)));
+        when(viewHolder.findViewById(com.android.settingslib.widget.R.id.settingslib_learn_more))
+                .thenReturn(null);
 
         Throwable actualThrowable = null;
         try {
@@ -126,7 +130,8 @@
     @Test
     public void onBindViewHolder_whenIconFrameIsNull_shouldNotRaiseNpe() {
         PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
-                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+                LayoutInflater.from(mContext)
+                        .inflate(com.android.settingslib.widget.R.layout.preference_footer, null)));
         when(viewHolder.findViewById(R.id.icon_frame)).thenReturn(null);
 
         Throwable actualThrowable = null;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java
index aaec909..23b4c2a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java
@@ -32,8 +32,6 @@
 
 import androidx.preference.PreferenceViewHolder;
 
-import com.android.settingslib.R;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 29bc409..089ddbe 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -331,6 +331,23 @@
         "tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt",
         "tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt",
         "tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt",
+
+        /* Dream tests */
+        "tests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java",
+        "tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java",
+        "tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java",
+        "tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java",
+        "tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java",
+        "tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java",
+        "tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java",
+        "tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt",
+        "tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt",
+        "tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java",
+        "tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java",
+        "tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java",
+        "tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java",
+        "tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java",
+        "tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java",
     ],
     path: "tests/src",
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 0d7d9cc..017ac60 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -446,7 +446,9 @@
             for (key in listOf(".blue600", ".blue400")) {
                 addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
                     PorterDuffColorFilter(
-                        context.getColor(com.android.settingslib.R.color.settingslib_color_blue400),
+                        context.getColor(
+                            com.android.settingslib.color.R.color.settingslib_color_blue400
+                        ),
                         PorterDuff.Mode.SRC_ATOP
                     )
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index e7bbf97..f68078a 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -22,6 +22,7 @@
 import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
 import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
 import android.os.Handler
+import android.os.Trace
 import android.util.Log
 import android.view.Display
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -34,9 +35,14 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 
 /** Provides a [Flow] of [Display] as returned by [DisplayManager]. */
@@ -49,7 +55,25 @@
      *
      * When `null`, it means there is no pending display waiting to be enabled.
      */
-    val pendingDisplayId: Flow<Int?>
+    val pendingDisplay: Flow<PendingDisplay?>
+
+    /** Represents a connected display that has not been enabled yet. */
+    interface PendingDisplay {
+        /** Id of the pending display. */
+        val id: Int
+
+        /** Enables the display, making it available to the system. */
+        suspend fun enable()
+
+        /**
+         * Ignores the pending display. When called, this specific display id doesn't appear as
+         * pending anymore until the display is disconnected and reconnected again.
+         */
+        suspend fun ignore()
+
+        /** Disables the display, making it unavailable to the system. */
+        suspend fun disable()
+    }
 }
 
 @SysUISingleton
@@ -62,7 +86,8 @@
     @Background backgroundCoroutineDispatcher: CoroutineDispatcher
 ) : DisplayRepository {
 
-    override val displays: Flow<Set<Display>> =
+    // Displays are enabled only after receiving them in [onDisplayAdded]
+    private val enabledDisplays: StateFlow<Set<Display>> =
         conflatedCallbackFlow {
                 val callback =
                     object : DisplayListener {
@@ -99,27 +124,38 @@
             displayManager.displays?.toSet() ?: emptySet()
         }
 
-    override val pendingDisplayId: Flow<Int?> =
+    /** Propagate to the listeners only enabled displays */
+    override val displays: Flow<Set<Display>> = enabledDisplays
+
+    private val enabledDisplayIds: Flow<Set<Int>> =
+        enabledDisplays
+            .map { enabledDisplaysSet -> enabledDisplaysSet.map { it.displayId }.toSet() }
+            .debugLog("enabledDisplayIds")
+
+    private val ignoredDisplayIds = MutableStateFlow<Set<Int>>(emptySet())
+
+    /* keeps connected displays until they are disconnected. */
+    private val connectedDisplayIds: StateFlow<Set<Int>> =
         conflatedCallbackFlow {
                 val callback =
                     object : DisplayConnectionListener {
-                        private val pendingIds = mutableSetOf<Int>()
+                        private val connectedIds = mutableSetOf<Int>()
                         override fun onDisplayConnected(id: Int) {
-                            pendingIds += id
-                            trySend(id)
+                            if (DEBUG) {
+                                Log.d(TAG, "$id connected")
+                            }
+                            connectedIds += id
+                            ignoredDisplayIds.value -= id
+                            trySend(connectedIds.toSet())
                         }
 
                         override fun onDisplayDisconnected(id: Int) {
-                            if (id in pendingIds) {
-                                pendingIds -= id
-                                trySend(null)
-                            } else {
-                                Log.e(
-                                    TAG,
-                                    "onDisplayDisconnected received for unknown display. " +
-                                        "id=$id, knownIds=$pendingIds"
-                                )
+                            connectedIds -= id
+                            if (DEBUG) {
+                                Log.d(TAG, "$id disconnected. Connected ids: $connectedIds")
                             }
+                            ignoredDisplayIds.value -= id
+                            trySend(connectedIds.toSet())
                         }
                     }
                 displayManager.registerDisplayListener(
@@ -130,15 +166,80 @@
                 awaitClose { displayManager.unregisterDisplayListener(callback) }
             }
             .distinctUntilChanged()
+            .debugLog("connectedDisplayIds")
             .flowOn(backgroundCoroutineDispatcher)
             .stateIn(
                 applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = null
+                initialValue = emptySet()
             )
 
+    /**
+     * Pending displays are the ones connected, but not enabled and not ignored. A connected display
+     * is ignored after the user makes the decision to use it or not. For now, the initial decision
+     * from the user is final and not reversible.
+     */
+    private val pendingDisplayIds: Flow<Set<Int>> =
+        combine(enabledDisplayIds, connectedDisplayIds, ignoredDisplayIds) {
+                enabledDisplaysIds,
+                connectedDisplayIds,
+                ignoredDisplayIds ->
+                if (DEBUG) {
+                    Log.d(
+                        TAG,
+                        "combining enabled: $enabledDisplaysIds, " +
+                            "connected: $connectedDisplayIds, ignored: $ignoredDisplayIds"
+                    )
+                }
+                connectedDisplayIds - enabledDisplaysIds - ignoredDisplayIds
+            }
+            .debugLog("pendingDisplayIds")
+
+    override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?> =
+        pendingDisplayIds
+            .map { pendingDisplayIds ->
+                val id = pendingDisplayIds.maxOrNull() ?: return@map null
+                object : DisplayRepository.PendingDisplay {
+                    override val id = id
+                    override suspend fun enable() {
+                        traceSection("DisplayRepository#enable($id)") {
+                            displayManager.enableConnectedDisplay(id)
+                        }
+                        // After the display has been enabled, it is automatically ignored.
+                        ignore()
+                    }
+
+                    override suspend fun ignore() {
+                        traceSection("DisplayRepository#ignore($id)") {
+                            ignoredDisplayIds.value += id
+                        }
+                    }
+
+                    override suspend fun disable() {
+                        ignore()
+                        traceSection("DisplayRepository#disable($id)") {
+                            displayManager.disableConnectedDisplay(id)
+                        }
+                    }
+                }
+            }
+            .debugLog("pendingDisplay")
+
+    private fun <T> Flow<T>.debugLog(flowName: String): Flow<T> {
+        return if (DEBUG) {
+            this.onEach {
+                Log.d(TAG, "$flowName: $it")
+                Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, "$TAG#$flowName", 0)
+                Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, "$TAG#$flowName", "$it", 0)
+            }
+        } else {
+            this
+        }
+    }
+
     private companion object {
         const val TAG = "DisplayRepository"
+        val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
index ef6fa26..11ed96d 100644
--- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
@@ -16,14 +16,12 @@
 
 package com.android.systemui.display.domain.interactor
 
-import android.hardware.display.DisplayManager
 import android.view.Display
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.display.data.repository.DisplayRepository
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.util.traceSection
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -52,13 +50,18 @@
         CONNECTED_SECURE,
     }
 
-    /** Represents a connected display that has not been enabled yet. */
+    /** Represents a connected display that has not been enabled yet for the UI layer. */
     interface PendingDisplay {
         /** Enables the display, making it available to the system. */
-        fun enable()
+        suspend fun enable()
 
-        /** Disables the display, making it unavailable to the system. */
-        fun disable()
+        /**
+         * Ignores the pending display.
+         *
+         * When called, this specific display id doesn't appear as pending anymore until the display
+         * is disconnected and reconnected again.
+         */
+        suspend fun ignore()
     }
 }
 
@@ -66,7 +69,6 @@
 class ConnectedDisplayInteractorImpl
 @Inject
 constructor(
-    private val displayManager: DisplayManager,
     keyguardRepository: KeyguardRepository,
     displayRepository: DisplayRepository,
 ) : ConnectedDisplayInteractor {
@@ -92,28 +94,19 @@
 
     // Provides the pending display only if the lockscreen is unlocked
     override val pendingDisplay: Flow<PendingDisplay?> =
-        displayRepository.pendingDisplayId.combine(keyguardRepository.isKeyguardUnlocked) {
-            pendingDisplayId,
-            keyguardUnlocked ->
-            if (pendingDisplayId != null && keyguardUnlocked) {
-                pendingDisplayId.toPendingDisplay()
+        displayRepository.pendingDisplay.combine(keyguardRepository.isKeyguardShowing) {
+            repositoryPendingDisplay,
+            keyguardShowing ->
+            if (repositoryPendingDisplay != null && !keyguardShowing) {
+                repositoryPendingDisplay.toInteractorPendingDisplay()
             } else {
                 null
             }
         }
 
-    private fun Int.toPendingDisplay() =
+    private fun DisplayRepository.PendingDisplay.toInteractorPendingDisplay(): PendingDisplay =
         object : PendingDisplay {
-            val id = this@toPendingDisplay
-            override fun enable() {
-                traceSection("DisplayRepository#enable($id)") {
-                    displayManager.enableConnectedDisplay(id)
-                }
-            }
-            override fun disable() {
-                traceSection("DisplayRepository#enable($id)") {
-                    displayManager.disableConnectedDisplay(id)
-                }
-            }
+            override suspend fun enable() = this@toInteractorPendingDisplay.enable()
+            override suspend fun ignore() = this@toInteractorPendingDisplay.ignore()
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index 174c6ff..ecc9d0e 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -24,15 +24,22 @@
 import android.widget.TextView
 import com.android.systemui.R
 
-/** Dialog used to decide what to do with a connected display. */
+/**
+ * Dialog used to decide what to do with a connected display.
+ *
+ * [onCancelMirroring] is called **only** if mirroring didn't start, or when the dismiss button is
+ * pressed.
+ */
 class MirroringConfirmationDialog(
     context: Context,
     private val onStartMirroringClickListener: View.OnClickListener,
-    private val onDismissClickListener: View.OnClickListener,
+    private val onCancelMirroring: View.OnClickListener,
 ) : Dialog(context, R.style.Theme_SystemUI_Dialog) {
 
     private lateinit var mirrorButton: TextView
     private lateinit var dismissButton: TextView
+    private var enabledPressed = false
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         window?.apply {
@@ -45,10 +52,15 @@
         mirrorButton =
             requireViewById<TextView>(R.id.enable_display).apply {
                 setOnClickListener(onStartMirroringClickListener)
+                enabledPressed = true
             }
         dismissButton =
-            requireViewById<TextView>(R.id.cancel).apply {
-                setOnClickListener(onDismissClickListener)
+            requireViewById<TextView>(R.id.cancel).apply { setOnClickListener(onCancelMirroring) }
+
+        setOnDismissListener {
+            if (!enabledPressed) {
+                onCancelMirroring.onClick(null)
             }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index ece33b7..86ef439 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -19,13 +19,16 @@
 import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
 import com.android.systemui.display.ui.view.MirroringConfirmationDialog
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
 
 /**
  * Shows/hides a dialog to allow the user to decide whether to use the external display for
@@ -38,6 +41,7 @@
     private val context: Context,
     private val connectedDisplayInteractor: ConnectedDisplayInteractor,
     @Application private val scope: CoroutineScope,
+    @Background private val bgDispatcher: CoroutineDispatcher
 ) {
 
     private var dialog: Dialog? = null
@@ -61,10 +65,13 @@
             MirroringConfirmationDialog(
                     context,
                     onStartMirroringClickListener = {
-                        pendingDisplay.enable()
+                        scope.launch(bgDispatcher) { pendingDisplay.enable() }
                         hideDialog()
                     },
-                    onDismissClickListener = { hideDialog() }
+                    onCancelMirroring = {
+                        scope.launch(bgDispatcher) { pendingDisplay.ignore() }
+                        hideDialog()
+                    }
                 )
                 .apply { show() }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 0fb1b48..d8b31a2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -43,6 +43,10 @@
     val ADD_TRANSIENT_HUN_IN_STACK_STATE_ANIMATOR =
         unreleasedFlag("add_transient_hun_in_stack_state_animator", teamfood = false)
 
+    // TODO(b/298308067): Tracking Bug
+    val SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX =
+        unreleasedFlag("swipe_uncleared_transient_view_fix", teamfood = false)
+
     // TODO(b/254512751): Tracking Bug
     val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
         unreleasedFlag("notification_pipeline_developer_logging")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 6bc9abf..257006e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -126,11 +126,11 @@
         }
 
     override fun start() {
+        bindKeyguardRootView()
         if (featureFlags.isEnabled(Flags.LAZY_INFLATE_KEYGUARD)) {
             keyguardRootView.removeAllViews()
             initializeViews()
         } else {
-            bindKeyguardRootView()
             val notificationPanel =
                 notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
             unbindKeyguardBottomArea(notificationPanel)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index a9d2b30..4be572f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -55,7 +55,6 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.screenrecord.MediaProjectionPermissionDialog;
 import com.android.systemui.screenrecord.ScreenShareOption;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -73,7 +72,6 @@
 
     private final FeatureFlags mFeatureFlags;
     private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
-    private final ActivityStarter mActivityStarter;
 
     private String mPackageName;
     private int mUid;
@@ -89,10 +87,8 @@
 
     @Inject
     public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
-            Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
-            ActivityStarter activityStarter) {
+            Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) {
         mFeatureFlags = featureFlags;
-        mActivityStarter = activityStarter;
         mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
     }
 
@@ -313,16 +309,8 @@
                 // Start activity from the current foreground user to avoid creating a separate
                 // SystemUI process without access to recent tasks because it won't have
                 // WM Shell running inside.
-                // It is also important to make sure the shade is dismissed, otherwise users won't
-                // see the app selector.
                 mUserSelectingTask = true;
-                mActivityStarter.startActivity(
-                        intent,
-                        /* dismissShade= */ true,
-                        /* animationController= */ null,
-                        /* showOverLockscreenWhenLocked= */ false,
-                        UserHandle.of(ActivityManager.getCurrentUser())
-                );
+                startActivityAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser()));
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error granting projection permission", e);
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 8d3b745..1d820a1 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -70,6 +70,7 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.preference.PreferenceManager;
@@ -85,9 +86,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
+import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.people.NotificationHelper;
 import com.android.systemui.people.PeopleBackupFollowUpJob;
 import com.android.systemui.people.PeopleSpaceUtils;
@@ -99,6 +102,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.wm.shell.bubbles.Bubbles;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -119,7 +123,8 @@
 
 /** Manager for People Space widget. */
 @SysUISingleton
-public class PeopleSpaceWidgetManager {
+public class PeopleSpaceWidgetManager implements Dumpable {
+
     private static final String TAG = "PeopleSpaceWidgetMgr";
     private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
 
@@ -160,7 +165,8 @@
             CommonNotifCollection notifCollection,
             PackageManager packageManager, Optional<Bubbles> bubblesOptional,
             UserManager userManager, NotificationManager notificationManager,
-            BroadcastDispatcher broadcastDispatcher, @Background Executor bgExecutor) {
+            BroadcastDispatcher broadcastDispatcher, @Background Executor bgExecutor,
+            DumpManager dumpManager) {
         if (DEBUG) Log.d(TAG, "constructor");
         mContext = context;
         mAppWidgetManager = AppWidgetManager.getInstance(context);
@@ -180,6 +186,7 @@
         mManager = this;
         mBroadcastDispatcher = broadcastDispatcher;
         mBgExecutor = bgExecutor;
+        dumpManager.registerNormalDumpable(TAG, this);
     }
 
     /** Initializes {@PeopleSpaceWidgetManager}. */
@@ -1364,4 +1371,40 @@
                 .filter(id -> !TextUtils.isEmpty(id))
                 .collect(Collectors.toSet());
     }
+
+    @Override
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+        Trace.traceBegin(Trace.TRACE_TAG_APP, TAG + ".dump");
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+        Map<String, ?> all = sp.getAll();
+        pw.println("People widget list:");
+        for (Map.Entry<String, ?> entry : all.entrySet()) {
+            String key = entry.getKey();
+            PeopleBackupHelper.SharedFileEntryType keyType = getEntryType(entry);
+            switch (keyType) {
+                case WIDGET_ID:
+                    SharedPreferences widgetSp = mContext.getSharedPreferences(key,
+                            Context.MODE_PRIVATE);
+                    pw.print("People widget (valid) [");
+                    pw.print(key);
+                    pw.print("] shortcut id: \"");
+                    pw.print(widgetSp.getString(SHORTCUT_ID, EMPTY_STRING));
+                    pw.print("\", user id: ");
+                    pw.print(widgetSp.getInt(USER_ID, INVALID_USER_ID));
+                    pw.print(", package: ");
+                    pw.println(widgetSp.getString(PACKAGE_NAME, EMPTY_STRING));
+                    break;
+                case PEOPLE_TILE_KEY:
+                case CONTACT_URI:
+                    pw.print("Extra data [");
+                    pw.print(key);
+                    pw.print(" : ");
+                    pw.print((Set<String>) entry.getValue());
+                    pw.println("]");
+                    break;
+            }
+        }
+
+        Trace.traceEnd(Trace.TRACE_TAG_APP);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index db7c003..4bd380e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -52,6 +52,7 @@
 
     private val displayManager = mock<DisplayManager>()
     private val displayListener = kotlinArgumentCaptor<DisplayManager.DisplayListener>()
+    private val connectedDisplayListener = kotlinArgumentCaptor<DisplayManager.DisplayListener>()
 
     private val testHandler = FakeHandler(Looper.getMainLooper())
     private val testScope = TestScope(UnconfinedTestDispatcher())
@@ -114,7 +115,7 @@
 
             // Let's make sure it has *NOT* been unregistered, as there is still a subscriber.
             setDisplays(1)
-            displayListener.value.onDisplayAdded(1)
+            sendOnDisplayAdded(1)
             assertThat(firstSubscriber?.ids()).containsExactly(1)
         }
 
@@ -127,7 +128,7 @@
             val value by latestDisplayFlowValue()
 
             setDisplays(1)
-            displayListener.value.onDisplayAdded(1)
+            sendOnDisplayAdded(1)
 
             assertThat(value?.ids()).containsExactly(1)
         }
@@ -138,13 +139,13 @@
             val value by latestDisplayFlowValue()
 
             setDisplays(1, 2, 3, 4)
-            displayListener.value.onDisplayAdded(1)
-            displayListener.value.onDisplayAdded(2)
-            displayListener.value.onDisplayAdded(3)
-            displayListener.value.onDisplayAdded(4)
+            sendOnDisplayAdded(1)
+            sendOnDisplayAdded(2)
+            sendOnDisplayAdded(3)
+            sendOnDisplayAdded(4)
 
             setDisplays(1, 2, 3)
-            displayListener.value.onDisplayRemoved(4)
+            sendOnDisplayRemoved(4)
 
             assertThat(value?.ids()).containsExactly(1, 2, 3)
         }
@@ -155,10 +156,10 @@
             val value by latestDisplayFlowValue()
 
             setDisplays(1, 2, 3, 4)
-            displayListener.value.onDisplayAdded(1)
-            displayListener.value.onDisplayAdded(2)
-            displayListener.value.onDisplayAdded(3)
-            displayListener.value.onDisplayAdded(4)
+            sendOnDisplayAdded(1)
+            sendOnDisplayAdded(2)
+            sendOnDisplayAdded(3)
+            sendOnDisplayAdded(4)
 
             displayListener.value.onDisplayChanged(4)
 
@@ -168,22 +169,22 @@
     @Test
     fun onDisplayConnected_pendingDisplayReceived() =
         testScope.runTest {
-            val pendingDisplay by latestPendingDisplayFlowValue()
+            val pendingDisplay by lastPendingDisplay()
 
-            displayListener.value.onDisplayConnected(1)
+            sendOnDisplayConnected(1)
 
-            assertThat(pendingDisplay).isEqualTo(1)
+            assertThat(pendingDisplay!!.id).isEqualTo(1)
         }
 
     @Test
     fun onDisplayDisconnected_pendingDisplayNull() =
         testScope.runTest {
-            val pendingDisplay by latestPendingDisplayFlowValue()
-            displayListener.value.onDisplayConnected(1)
+            val pendingDisplay by lastPendingDisplay()
+            sendOnDisplayConnected(1)
 
             assertThat(pendingDisplay).isNotNull()
 
-            displayListener.value.onDisplayDisconnected(1)
+            sendOnDisplayDisconnected(1)
 
             assertThat(pendingDisplay).isNull()
         }
@@ -191,24 +192,162 @@
     @Test
     fun onDisplayDisconnected_unknownDisplay_doesNotSendNull() =
         testScope.runTest {
-            val pendingDisplay by latestPendingDisplayFlowValue()
-            displayListener.value.onDisplayConnected(1)
+            val pendingDisplay by lastPendingDisplay()
+            sendOnDisplayConnected(1)
 
             assertThat(pendingDisplay).isNotNull()
 
-            displayListener.value.onDisplayDisconnected(2)
+            sendOnDisplayDisconnected(2)
 
             assertThat(pendingDisplay).isNotNull()
         }
 
     @Test
-    fun onDisplayConnected_multipleTimes_sendsOnlyTheLastOne() =
+    fun onDisplayConnected_multipleTimes_sendsOnlyTheMaximum() =
         testScope.runTest {
-            val pendingDisplay by latestPendingDisplayFlowValue()
-            displayListener.value.onDisplayConnected(1)
-            displayListener.value.onDisplayConnected(2)
+            val pendingDisplay by lastPendingDisplay()
 
-            assertThat(pendingDisplay).isEqualTo(2)
+            sendOnDisplayConnected(1)
+            sendOnDisplayConnected(2)
+
+            assertThat(pendingDisplay!!.id).isEqualTo(2)
+        }
+
+    @Test
+    fun onPendingDisplay_enable_displayEnabled() =
+        testScope.runTest {
+            val pendingDisplay by lastPendingDisplay()
+
+            sendOnDisplayConnected(1)
+            pendingDisplay!!.enable()
+
+            verify(displayManager).enableConnectedDisplay(eq(1))
+        }
+
+    @Test
+    fun onPendingDisplay_enableBySysui_disabledBySomeoneElse_pendingDisplayStillIgnored() =
+        testScope.runTest {
+            val pendingDisplay by lastPendingDisplay()
+
+            sendOnDisplayConnected(1)
+            pendingDisplay!!.enable()
+            // to mock the display being really enabled:
+            sendOnDisplayAdded(1)
+
+            // Simulate the display being disabled by someone else. Now, sysui will have it in the
+            // "pending displays" list again, but it should be ignored.
+            sendOnDisplayRemoved(1)
+
+            assertThat(pendingDisplay).isNull()
+        }
+
+    @Test
+    fun onPendingDisplay_ignoredBySysui_enabledDisabledBySomeoneElse_pendingDisplayStillIgnored() =
+        testScope.runTest {
+            val pendingDisplay by lastPendingDisplay()
+
+            sendOnDisplayConnected(1)
+            pendingDisplay!!.ignore()
+
+            // to mock the display being enabled and disabled by someone else:
+            sendOnDisplayAdded(1)
+            sendOnDisplayRemoved(1)
+
+            // Sysui already decided to ignore it, so the pending display should be null.
+            assertThat(pendingDisplay).isNull()
+        }
+
+    @Test
+    fun onPendingDisplay_disable_displayDisabled() =
+        testScope.runTest {
+            val pendingDisplay by lastPendingDisplay()
+
+            sendOnDisplayConnected(1)
+            pendingDisplay!!.disable()
+
+            verify(displayManager).disableConnectedDisplay(eq(1))
+        }
+
+    @Test
+    fun onPendingDisplay_ignore_pendingDisplayNull() =
+        testScope.runTest {
+            val pendingDisplay by lastPendingDisplay()
+            sendOnDisplayConnected(1)
+
+            pendingDisplay!!.ignore()
+
+            assertThat(pendingDisplay).isNull()
+            verify(displayManager, never()).disableConnectedDisplay(eq(1))
+            verify(displayManager, never()).enableConnectedDisplay(eq(1))
+        }
+
+    @Test
+    fun onPendingDisplay_enabled_pendingDisplayNull() =
+        testScope.runTest {
+            val pendingDisplay by lastPendingDisplay()
+
+            sendOnDisplayConnected(1)
+            assertThat(pendingDisplay).isNotNull()
+
+            setDisplays(1)
+            sendOnDisplayAdded(1)
+
+            assertThat(pendingDisplay).isNull()
+        }
+
+    @Test
+    fun onPendingDisplay_multipleConnected_oneEnabled_pendingDisplayNotNull() =
+        testScope.runTest {
+            val pendingDisplay by lastPendingDisplay()
+
+            sendOnDisplayConnected(1)
+            sendOnDisplayConnected(2)
+
+            assertThat(pendingDisplay).isNotNull()
+
+            setDisplays(1)
+            sendOnDisplayAdded(1)
+
+            assertThat(pendingDisplay).isNotNull()
+            assertThat(pendingDisplay!!.id).isEqualTo(2)
+
+            setDisplays(1, 2)
+            sendOnDisplayAdded(2)
+
+            assertThat(pendingDisplay).isNull()
+        }
+
+    @Test
+    fun pendingDisplay_connectedDisconnectedAndReconnected_expectedPendingDisplayState() =
+        testScope.runTest {
+            val pendingDisplay by lastPendingDisplay()
+
+            // Plug the cable
+            sendOnDisplayConnected(1)
+
+            // Enable it
+            assertThat(pendingDisplay).isNotNull()
+            pendingDisplay!!.enable()
+
+            // Enabled
+            verify(displayManager).enableConnectedDisplay(1)
+            setDisplays(1)
+            sendOnDisplayAdded(1)
+
+            // No more pending displays
+            assertThat(pendingDisplay).isNull()
+
+            // Let's disconnect the cable
+            setDisplays()
+            sendOnDisplayRemoved(1)
+            sendOnDisplayDisconnected(1)
+
+            assertThat(pendingDisplay).isNull()
+
+            // Let's reconnect it
+            sendOnDisplayConnected(1)
+
+            assertThat(pendingDisplay).isNotNull()
         }
 
     private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
@@ -216,6 +355,23 @@
     // Wrapper to capture the displayListener.
     private fun TestScope.latestDisplayFlowValue(): FlowValue<Set<Display>?> {
         val flowValue = collectLastValue(displayRepository.displays)
+        captureAddedRemovedListener()
+        return flowValue
+    }
+
+    private fun TestScope.lastPendingDisplay(): FlowValue<DisplayRepository.PendingDisplay?> {
+        val flowValue = collectLastValue(displayRepository.pendingDisplay)
+        captureAddedRemovedListener()
+        verify(displayManager)
+            .registerDisplayListener(
+                connectedDisplayListener.capture(),
+                eq(testHandler),
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
+            )
+        return flowValue
+    }
+
+    private fun captureAddedRemovedListener() {
         verify(displayManager)
             .registerDisplayListener(
                 displayListener.capture(),
@@ -226,18 +382,20 @@
                         DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
                 )
             )
-        return flowValue
+    }
+    private fun sendOnDisplayAdded(id: Int) {
+        displayListener.value.onDisplayAdded(id)
+    }
+    private fun sendOnDisplayRemoved(id: Int) {
+        displayListener.value.onDisplayRemoved(id)
     }
 
-    private fun TestScope.latestPendingDisplayFlowValue(): FlowValue<Int?> {
-        val flowValue = collectLastValue(displayRepository.pendingDisplayId)
-        verify(displayManager)
-            .registerDisplayListener(
-                displayListener.capture(),
-                eq(testHandler),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
-            )
-        return flowValue
+    private fun sendOnDisplayDisconnected(id: Int) {
+        connectedDisplayListener.value.onDisplayDisconnected(id)
+    }
+
+    private fun sendOnDisplayConnected(id: Int) {
+        connectedDisplayListener.value.onDisplayConnected(id)
     }
 
     private fun setDisplays(displays: List<Display>) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
index 50617a1..26ee094 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.display.domain.interactor
 
-import android.hardware.display.DisplayManager
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.Display
@@ -27,12 +26,11 @@
 import com.android.systemui.coroutines.FlowValue
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.display.data.repository.createPendingDisplay
 import com.android.systemui.display.data.repository.display
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -41,7 +39,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito
 
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
@@ -49,20 +46,15 @@
 @SmallTest
 class ConnectedDisplayInteractorTest : SysuiTestCase() {
 
-    private val displayManager = mock<DisplayManager>()
     private val fakeDisplayRepository = FakeDisplayRepository()
     private val fakeKeyguardRepository = FakeKeyguardRepository()
     private val connectedDisplayStateProvider: ConnectedDisplayInteractor =
-        ConnectedDisplayInteractorImpl(
-            displayManager,
-            fakeKeyguardRepository,
-            fakeDisplayRepository
-        )
+        ConnectedDisplayInteractorImpl(fakeKeyguardRepository, fakeDisplayRepository)
     private val testScope = TestScope(UnconfinedTestDispatcher())
 
     @Before
     fun setup() {
-        fakeKeyguardRepository.setKeyguardUnlocked(true)
+        fakeKeyguardRepository.setKeyguardShowing(false)
     }
 
     @Test
@@ -148,7 +140,7 @@
     fun pendingDisplay_propagated() =
         testScope.runTest {
             val value by lastPendingDisplay()
-            val pendingDisplayId = 4
+            val pendingDisplayId = createPendingDisplay()
 
             fakeDisplayRepository.emit(pendingDisplayId)
 
@@ -156,51 +148,29 @@
         }
 
     @Test
-    fun onPendingDisplay_enable_displayEnabled() =
+    fun onPendingDisplay_keyguardShowing_returnsPendingDisplay() =
         testScope.runTest {
+            fakeKeyguardRepository.setKeyguardShowing(true)
             val pendingDisplay by lastPendingDisplay()
 
-            fakeDisplayRepository.emit(1)
-            pendingDisplay!!.enable()
-
-            Mockito.verify(displayManager).enableConnectedDisplay(eq(1))
-        }
-
-    @Test
-    fun onPendingDisplay_disable_displayDisabled() =
-        testScope.runTest {
-            val pendingDisplay by lastPendingDisplay()
-
-            fakeDisplayRepository.emit(1)
-            pendingDisplay!!.disable()
-
-            Mockito.verify(displayManager).disableConnectedDisplay(eq(1))
-        }
-
-    @Test
-    fun onPendingDisplay_keyguardUnlocked_returnsPendingDisplay() =
-        testScope.runTest {
-            fakeKeyguardRepository.setKeyguardUnlocked(false)
-            val pendingDisplay by lastPendingDisplay()
-
-            fakeDisplayRepository.emit(1)
+            fakeDisplayRepository.emit(createPendingDisplay())
             assertThat(pendingDisplay).isNull()
 
-            fakeKeyguardRepository.setKeyguardUnlocked(true)
+            fakeKeyguardRepository.setKeyguardShowing(false)
 
             assertThat(pendingDisplay).isNotNull()
         }
 
     @Test
-    fun onPendingDisplay_keyguardLocked_returnsNull() =
+    fun onPendingDisplay_keyguardShowing_returnsNull() =
         testScope.runTest {
-            fakeKeyguardRepository.setKeyguardUnlocked(true)
+            fakeKeyguardRepository.setKeyguardShowing(false)
             val pendingDisplay by lastPendingDisplay()
 
-            fakeDisplayRepository.emit(1)
+            fakeDisplayRepository.emit(createPendingDisplay())
             assertThat(pendingDisplay).isNotNull()
 
-            fakeKeyguardRepository.setKeyguardUnlocked(false)
+            fakeKeyguardRepository.setKeyguardShowing(true)
 
             assertThat(pendingDisplay).isNull()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
index 7059647..46f7582 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
@@ -68,6 +68,28 @@
         verify(onStartMirroringCallback, never()).onClick(any())
     }
 
+    @Test
+    fun onCancel_afterEnablingMirroring_cancelCallbackNotCalled() {
+        dialog.show()
+        dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+        dialog.cancel()
+
+        verify(onCancelCallback, never()).onClick(any())
+        verify(onStartMirroringCallback).onClick(any())
+    }
+
+    @Test
+    fun onDismiss_afterEnablingMirroring_cancelCallbackNotCalled() {
+        dialog.show()
+        dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+        dialog.dismiss()
+
+        verify(onCancelCallback, never()).onClick(any())
+        verify(onStartMirroringCallback).onClick(any())
+    }
+
     @After
     fun teardown() {
         if (::dialog.isInitialized) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 57307fc..781ad6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -3,9 +3,10 @@
 import android.animation.Animator
 import android.animation.AnimatorSet
 import android.animation.ValueAnimator
-import android.testing.AndroidTestingRunner
 import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.complication.ComplicationHostViewController
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
@@ -29,8 +30,9 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+@RoboPilotTest
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
index 9f534ef..21192fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
@@ -15,8 +15,9 @@
  */
 package com.android.systemui.dreams
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -29,8 +30,9 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+@RoboPilotTest
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class DreamOverlayCallbackControllerTest : SysuiTestCase() {
 
     @Mock private lateinit var callback: DreamOverlayCallbackController.Callback
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 8786520..7c36642 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -28,21 +28,22 @@
 import android.content.res.Resources;
 import android.graphics.Region;
 import android.os.Handler;
-import android.testing.AndroidTestingRunner;
 import android.view.AttachedSurfaceControl;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.dream.lowlight.LowLightTransitionCoordinator;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.complication.ComplicationHostViewController;
-import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.complication.ComplicationHostViewController;
+import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
 import com.android.systemui.statusbar.BlurUtils;
 
 import org.junit.Before;
@@ -52,8 +53,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@RoboPilotTest
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
     private static final int MAX_BURN_IN_OFFSET = 20;
     private static final long BURN_IN_PROTECTION_UPDATE_INTERVAL = 10;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
index 25d4c47..be7638e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
@@ -22,10 +22,11 @@
 
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
@@ -37,8 +38,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@RoboPilotTest
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class DreamOverlayNotificationCountProviderTest extends SysuiTestCase {
     @Mock
     NotificationListener mNotificationListener;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index d99f0da..8379f73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -37,7 +37,6 @@
 import android.service.dreams.IDreamOverlayCallback;
 import android.service.dreams.IDreamOverlayClient;
 import android.service.dreams.IDreamOverlayClientCallback;
-import android.testing.AndroidTestingRunner;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
@@ -45,10 +44,12 @@
 
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.complication.ComplicationLayoutEngine;
 import com.android.systemui.dreams.complication.HideComplicationTouchHandler;
@@ -71,8 +72,9 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+@RoboPilotTest
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class DreamOverlayServiceTest extends SysuiTestCase {
     private static final ComponentName LOW_LIGHT_COMPONENT = new ComponentName("package",
             "lowlight");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 44a78ac..2ef227c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -26,10 +26,10 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.complication.Complication;
 import com.android.systemui.flags.FeatureFlags;
@@ -48,8 +48,9 @@
 
 import java.util.Collection;
 
+@RoboPilotTest
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class DreamOverlayStateControllerTest extends SysuiTestCase {
     @Mock
     DreamOverlayStateController.Callback mCallback;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
index a78886f..12cb332 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
@@ -21,10 +21,10 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
@@ -36,8 +36,9 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 
+@RoboPilotTest
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class DreamOverlayStatusBarItemsProviderTest extends SysuiTestCase {
     @Mock
     DreamOverlayStatusBarItemsProvider.Callback mCallback;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index 4e74f451..af2dab5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -41,12 +41,13 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
 import android.view.View;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.R;
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.core.FakeLogBuffer;
@@ -71,8 +72,9 @@
 import java.util.Optional;
 import java.util.concurrent.Executor;
 
+@RoboPilotTest
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
     private static final String NOTIFICATION_INDICATOR_FORMATTER_STRING =
             "{count, plural, =1 {# notification} other {# notifications}}";
@@ -92,8 +94,6 @@
     @Mock
     AlarmManager mAlarmManager;
     @Mock
-    AlarmManager.AlarmClockInfo mAlarmClockInfo;
-    @Mock
     NextAlarmController mNextAlarmController;
     @Mock
     DateFormatUtil mDateFormatUtil;
@@ -203,8 +203,9 @@
 
     @Test
     public void testOnViewAttachedShowsAlarmIconWhenAlarmExists() {
-        when(mAlarmClockInfo.getTriggerTime()).thenReturn(1L);
-        when(mAlarmManager.getNextAlarmClock(anyInt())).thenReturn(mAlarmClockInfo);
+        final AlarmManager.AlarmClockInfo alarmClockInfo =
+                new AlarmManager.AlarmClockInfo(1L, null);
+        when(mAlarmManager.getNextAlarmClock(anyInt())).thenReturn(alarmClockInfo);
         mController.onViewAttached();
         verify(mView).showIcon(
                 eq(DreamOverlayStatusBarView.STATUS_ICON_ALARM_SET), eq(true), any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
index eed4dbc..d32788d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
@@ -23,13 +23,14 @@
 import static org.mockito.Mockito.when;
 
 import android.os.Handler;
-import android.testing.AndroidTestingRunner;
 import android.view.MotionEvent;
 import android.view.View;
 
 import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.complication.Complication;
 import com.android.systemui.dreams.DreamOverlayStateController;
@@ -48,8 +49,9 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+@RoboPilotTest
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class HideComplicationTouchHandlerTest extends SysuiTestCase {
     private static final int RESTORE_TIMEOUT = 1000;
     private static final int HIDE_DELAY = 500;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
index 6a17889..4a7700f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
@@ -23,10 +23,10 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.assist.AssistManager.VisualQueryAttentionListener;
@@ -41,8 +41,9 @@
 
 import kotlinx.coroutines.CoroutineScope;
 
+@RoboPilotTest
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class AssistantAttentionConditionTest extends SysuiTestCase {
     @Mock
     Condition.Callback mCallback;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
index 68c7965..cd2efde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
@@ -25,12 +25,13 @@
 import static org.mockito.Mockito.when;
 
 import android.app.DreamManager;
-import android.testing.AndroidTestingRunner;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.shared.condition.Condition;
 
@@ -43,8 +44,9 @@
 
 import kotlinx.coroutines.CoroutineScope;
 
+@RoboPilotTest
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class DreamConditionTest extends SysuiTestCase {
     @Mock
     Condition.Callback mCallback;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 3f9b198..90076fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -31,16 +31,17 @@
 import android.content.pm.UserInfo;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.testing.AndroidTestingRunner;
 import android.view.GestureDetector;
 import android.view.GestureDetector.OnGestureListener;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.dreams.touch.scrim.ScrimController;
@@ -56,6 +57,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -63,8 +65,9 @@
 import java.util.Collections;
 import java.util.Optional;
 
+@RoboPilotTest
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
     @Mock
     CentralSurfaces mCentralSurfaces;
@@ -107,6 +110,12 @@
     @Mock
     LockPatternUtils mLockPatternUtils;
 
+    @Mock
+    Region mRegion;
+
+    @Captor
+    ArgumentCaptor<Rect> mRectCaptor;
+
     FakeUserTracker mUserTracker;
 
     private static final float TOUCH_REGION = .3f;
@@ -153,10 +162,10 @@
      */
     @Test
     public void testSessionStart() {
-        final Region region = Region.obtain();
-        mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, region);
+        mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion);
 
-        final Rect bounds = region.getBounds();
+        verify(mRegion).op(mRectCaptor.capture(), eq(Region.Op.UNION));
+        final Rect bounds = mRectCaptor.getValue();
 
         final Rect expected = new Rect();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
index 2b98214..ff6d97d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
@@ -21,12 +21,13 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.testing.AndroidTestingRunner;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shared.system.InputChannelCompat;
@@ -42,8 +43,9 @@
 
 import java.util.Optional;
 
+@RoboPilotTest
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class ShadeTouchHandlerTest extends SysuiTestCase {
     @Mock
     CentralSurfaces mCentralSurfaces;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
index 79c535a..da39381 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
@@ -22,10 +22,11 @@
 import static org.mockito.Mockito.verify;
 
 import android.os.PowerManager;
-import android.testing.AndroidTestingRunner;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -37,8 +38,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@RoboPilotTest
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class BouncerlessScrimControllerTest extends SysuiTestCase {
     @Mock
     BouncerlessScrimController.Callback mCallback;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
index ac9822d..81f6fe3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
@@ -22,10 +22,10 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -38,8 +38,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@RoboPilotTest
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class ScrimManagerTest extends SysuiTestCase {
     @Mock
     ScrimController mBouncerlessScrimController;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 6f51d1b..2ac625d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -30,20 +30,24 @@
     }
 }
 
+/** Creates a mock [DisplayRepository.PendingDisplay]. */
+fun createPendingDisplay(id: Int = 0): DisplayRepository.PendingDisplay =
+    mock<DisplayRepository.PendingDisplay> { whenever(this.id).thenReturn(id) }
+
 /** Fake [DisplayRepository] implementation for testing. */
 class FakeDisplayRepository : DisplayRepository {
     private val flow = MutableSharedFlow<Set<Display>>()
-    private val pendingDisplayFlow = MutableSharedFlow<Int?>()
+    private val pendingDisplayFlow = MutableSharedFlow<DisplayRepository.PendingDisplay?>()
 
     /** Emits [value] as [displays] flow value. */
     suspend fun emit(value: Set<Display>) = flow.emit(value)
 
-    /** Emits [value] as [pendingDisplayId] flow value. */
-    suspend fun emit(value: Int?) = pendingDisplayFlow.emit(value)
+    /** Emits [value] as [pendingDisplay] flow value. */
+    suspend fun emit(value: DisplayRepository.PendingDisplay?) = pendingDisplayFlow.emit(value)
 
     override val displays: Flow<Set<Display>>
         get() = flow
 
-    override val pendingDisplayId: Flow<Int?>
+    override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?>
         get() = pendingDisplayFlow
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/Android.bp b/services/companion/java/com/android/server/companion/virtual/Android.bp
index 7a7d376..6526c78 100644
--- a/services/companion/java/com/android/server/companion/virtual/Android.bp
+++ b/services/companion/java/com/android/server/companion/virtual/Android.bp
@@ -1,9 +1,6 @@
 java_aconfig_library {
     name: "virtualdevice_flags_lib",
     aconfig_declarations: "virtualdevice_flags",
-    static_libs: [
-        "android.companion.virtual.flags-aconfig-java",
-    ],
 }
 
 aconfig_declarations {
diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
index 6675568..ce4067c 100644
--- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
+++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
@@ -19,6 +19,7 @@
 import static android.hardware.camera2.CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_UNSUPPORTED;
 
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -146,6 +147,7 @@
      *
      * @param runningUids uids of the application running on the virtual display
      */
+    @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
     public void blockCameraAccessIfNeeded(Set<Integer> runningUids) {
         synchronized (mLock) {
             for (int i = 0; i < mAppsToBlockOnVirtualDevice.size(); i++) {
@@ -181,6 +183,7 @@
     }
 
     @Override
+    @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
     public void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) {
         synchronized (mLock) {
             InjectionSessionData data = mPackageToSessionData.get(packageName);
@@ -243,6 +246,7 @@
     /**
      * Turns on blocking for a particular camera and package.
      */
+    @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
     private void startBlocking(String packageName, String cameraId) {
         try {
             Slog.d(
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index cf6092e..9e7b897 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -136,7 +136,8 @@
     @Nullable private final SecureWindowCallback mSecureWindowCallback;
     @Nullable private final Set<String> mDisplayCategories;
 
-    private final boolean mShowTasksInHostDeviceRecents;
+    @GuardedBy("mGenericWindowPolicyControllerLock")
+    private boolean mShowTasksInHostDeviceRecents;
 
     /**
      * Creates a window policy controller that is generic to the different use cases of virtual
@@ -204,6 +205,15 @@
         mDisplayId = displayId;
     }
 
+    /**
+     * Set whether to show activities in recents on the host device.
+     */
+    public void setShowInHostDeviceRecents(boolean showInHostDeviceRecents) {
+        synchronized (mGenericWindowPolicyControllerLock) {
+            mShowTasksInHostDeviceRecents = showInHostDeviceRecents;
+        }
+    }
+
     /** Register a listener for running applications changes. */
     public void registerRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) {
         synchronized (mGenericWindowPolicyControllerLock) {
@@ -347,7 +357,9 @@
 
     @Override
     public boolean canShowTasksInHostDeviceRecents() {
-        return mShowTasksInHostDeviceRecents;
+        synchronized (mGenericWindowPolicyControllerLock) {
+            return mShowTasksInHostDeviceRecents;
+        }
     }
 
     @Override
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 307f7bf..eeaa423 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.companion.virtual;
 
+import static android.text.TextUtils.formatSimple;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.StringDef;
@@ -350,7 +352,7 @@
     }
 
     private static String createPhys(@PhysType String type) {
-        return String.format("virtual%s:%d", type, sNextPhysId.getAndIncrement());
+        return formatSimple("virtual%s:%d", type, sNextPhysId.getAndIncrement());
     }
 
     private void setUniqueIdAssociation(int displayId, String phys) {
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
index a831401..e9241dd 100644
--- a/services/companion/java/com/android/server/companion/virtual/SensorController.java
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -323,8 +323,5 @@
         SensorCreationException(String message) {
             super(message);
         }
-        SensorCreationException(String message, Exception cause) {
-            super(message, cause);
-        }
     }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index f23fe2a..56afeb1 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -19,12 +19,15 @@
 import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_ENABLED;
 import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
 import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.StringRes;
 import android.annotation.UserIdInt;
 import android.app.Activity;
@@ -41,6 +44,7 @@
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.flags.Flags;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorEvent;
 import android.content.AttributionSource;
@@ -78,12 +82,15 @@
 import android.os.UserManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.view.Display;
 import android.view.WindowManager;
 import android.widget.Toast;
 
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.BlockedAppStreamingActivity;
@@ -93,7 +100,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -128,8 +134,8 @@
     private final int mOwnerUid;
     private final VirtualDeviceLog mVirtualDeviceLog;
     private final String mOwnerPackageName;
-    private int mDeviceId;
-    private @Nullable String mPersistentDeviceId;
+    private final int mDeviceId;
+    private @Nullable final String mPersistentDeviceId;
     // Thou shall not hold the mVirtualDeviceLock over the mInputController calls.
     // Holding the lock can lead to lock inversion with GlobalWindowManagerLock.
     // 1. After display is created the window manager calls into VDM during construction
@@ -145,6 +151,8 @@
     private final IBinder mAppToken;
     private final VirtualDeviceParams mParams;
     @GuardedBy("mVirtualDeviceLock")
+    private final SparseIntArray mDevicePolicies;
+    @GuardedBy("mVirtualDeviceLock")
     private final SparseArray<VirtualDisplayWrapper> mVirtualDisplays = new SparseArray<>();
     private final IVirtualDeviceActivityListener mActivityListener;
     private final IVirtualDeviceSoundEffectListener mSoundEffectListener;
@@ -152,7 +160,7 @@
     @GuardedBy("mVirtualDeviceLock")
     private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
     @NonNull
-    private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
+    private final Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
     // The default setting for showing the pointer on new displays.
     @GuardedBy("mVirtualDeviceLock")
     private boolean mDefaultShowPointerIcon = true;
@@ -259,6 +267,7 @@
         mDeviceId = deviceId;
         mAppToken = token;
         mParams = params;
+        mDevicePolicies = params.getDevicePolicies();
         mDisplayManager = displayManager;
         if (inputController == null) {
             mInputController = new InputController(
@@ -321,7 +330,13 @@
     /** Returns the policy specified for this policy type */
     public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
             @VirtualDeviceParams.PolicyType int policyType) {
-        return mParams.getDevicePolicy(policyType);
+        if (Flags.dynamicPolicy()) {
+            synchronized (mVirtualDeviceLock) {
+                return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
+            }
+        } else {
+            return mParams.getDevicePolicy(policyType);
+        }
     }
 
     /** Returns device-specific audio session id for playback. */
@@ -410,15 +425,13 @@
     public void close() {
         super.close_enforcePermission();
         // Remove about-to-be-closed virtual device from the service before butchering it.
-        boolean removed = mService.removeVirtualDevice(mDeviceId);
-        mVirtualDeviceLog.logClosed(mDeviceId, mOwnerUid);
-        mDeviceId = Context.DEVICE_ID_INVALID;
-
-        // Device is already closed.
-        if (!removed) {
+        if (!mService.removeVirtualDevice(mDeviceId)) {
+            // Device is already closed.
             return;
         }
 
+        mVirtualDeviceLog.logClosed(mDeviceId, mOwnerUid);
+
         final long ident = Binder.clearCallingIdentity();
         try {
             VirtualDisplayWrapper[] virtualDisplaysToBeReleased;
@@ -460,6 +473,7 @@
     }
 
     @Override
+    @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
     public void onRunningAppsChanged(ArraySet<Integer> runningUids) {
         mCameraAccessController.blockCameraAccessIfNeeded(runningUids);
         mRunningAppsChangedCallback.accept(runningUids);
@@ -507,6 +521,27 @@
 
     @Override // Binder call
     @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
+            @VirtualDeviceParams.DevicePolicy int devicePolicy) {
+        super.setDevicePolicy_enforcePermission();
+        switch (policyType) {
+            case POLICY_TYPE_RECENTS:
+                synchronized (mVirtualDeviceLock) {
+                    mDevicePolicies.put(policyType, devicePolicy);
+                    for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                        mVirtualDisplays.valueAt(i).getWindowPolicyController()
+                                .setShowInHostDeviceRecents(devicePolicy == DEVICE_POLICY_DEFAULT);
+                    }
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("Device policy " + policyType
+                        + " cannot be changed at runtime. ");
+        }
+    }
+
+    @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
         super.createVirtualDpad_enforcePermission();
         Objects.requireNonNull(config);
@@ -718,11 +753,9 @@
         try {
             synchronized (mVirtualDeviceLock) {
                 mDefaultShowPointerIcon = showPointerIcon;
-                for (int i = 0; i < mVirtualDisplays.size(); i++) {
-                    final int displayId = mVirtualDisplays.keyAt(i);
-                    mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId);
-                }
             }
+            getDisplayIds().forEach(
+                    displayId -> mInputController.setShowPointerIcon(showPointerIcon, displayId));
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -786,6 +819,7 @@
         mParams.dump(fout, "        ");
         fout.println("    mVirtualDisplayIds: ");
         synchronized (mVirtualDeviceLock) {
+            fout.println("    mDevicePolicies: " + mDevicePolicies);
             for (int i = 0; i < mVirtualDisplays.size(); i++) {
                 fout.println("      " + mVirtualDisplays.keyAt(i));
             }
@@ -812,9 +846,7 @@
                         this::onSecureWindowShown,
                         this::shouldInterceptIntent,
                         displayCategories,
-                        mParams.getDevicePolicy(
-                                VirtualDeviceParams.POLICY_TYPE_RECENTS)
-                                == VirtualDeviceParams.DEVICE_POLICY_DEFAULT);
+                        mParams.getDevicePolicy(POLICY_TYPE_RECENTS) == DEVICE_POLICY_DEFAULT);
         gwpc.registerRunningAppsChangedListener(/* listener= */ this);
         return gwpc;
     }
@@ -830,6 +862,7 @@
                 this, gwpc, packageName);
         gwpc.setDisplayId(displayId);
 
+        boolean showPointer;
         synchronized (mVirtualDeviceLock) {
             if (mVirtualDisplays.contains(displayId)) {
                 gwpc.unregisterRunningAppsChangedListener(this);
@@ -839,11 +872,12 @@
 
             PowerManager.WakeLock wakeLock = createAndAcquireWakeLockForDisplay(displayId);
             mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock));
+            showPointer = mDefaultShowPointerIcon;
         }
 
         final long token = Binder.clearCallingIdentity();
         try {
-            mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId);
+            mInputController.setShowPointerIcon(showPointer, displayId);
             mInputController.setPointerAcceleration(1f, displayId);
             mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
                     displayId);
@@ -869,6 +903,7 @@
         }
     }
 
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
     private void onActivityBlocked(int displayId, ActivityInfo activityInfo) {
         Intent intent = BlockedAppStreamingActivity.createIntent(
                 activityInfo, mAssociationInfo.getDisplayName());
@@ -950,6 +985,7 @@
         }
     }
 
+    @SuppressWarnings("AndroidFrameworkRequiresPermission")
     private void checkVirtualInputDeviceDisplayIdAssociation(int displayId) {
         if (mContext.checkCallingPermission(android.Manifest.permission.INJECT_EVENTS)
                     == PackageManager.PERMISSION_GRANTED) {
@@ -1031,8 +1067,8 @@
      */
     void showToastWhereUidIsRunning(int uid, String text, @Toast.Duration int duration,
             Looper looper) {
-        ArrayList<Integer> displayIdsForUid = getDisplayIdsWhereUidIsRunning(uid);
-        if (displayIdsForUid.isEmpty()) {
+        IntArray displayIdsForUid = getDisplayIdsWhereUidIsRunning(uid);
+        if (displayIdsForUid.size() == 0) {
             return;
         }
         DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
@@ -1045,8 +1081,8 @@
         }
     }
 
-    private ArrayList<Integer> getDisplayIdsWhereUidIsRunning(int uid) {
-        ArrayList<Integer> displayIdsForUid = new ArrayList<>();
+    private IntArray getDisplayIdsWhereUidIsRunning(int uid) {
+        IntArray displayIdsForUid = new IntArray();
         synchronized (mVirtualDeviceLock) {
             for (int i = 0; i < mVirtualDisplays.size(); i++) {
                 if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index a135878..4da9298 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -23,6 +23,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.app.ActivityOptions;
 import android.companion.AssociationInfo;
@@ -96,6 +97,7 @@
     private final CompanionDeviceManager.OnAssociationsChangedListener mCdmAssociationListener =
             new CompanionDeviceManager.OnAssociationsChangedListener() {
                 @Override
+                @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
                 public void onAssociationsChanged(@NonNull List<AssociationInfo> associations) {
                     syncVirtualDevicesToCdmAssociations(associations);
                 }
@@ -241,6 +243,7 @@
         return true;
     }
 
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     private void syncVirtualDevicesToCdmAssociations(List<AssociationInfo> associations) {
         Set<VirtualDeviceImpl> virtualDevicesToRemove = new HashSet<>();
         synchronized (mVirtualDeviceManagerLock) {
@@ -266,6 +269,7 @@
         }
     }
 
+    @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
     private void registerCdmAssociationListener() {
         final CompanionDeviceManager cdm = getContext().getSystemService(
                 CompanionDeviceManager.class);
@@ -273,6 +277,7 @@
                 mCdmAssociationListener);
     }
 
+    @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
     private void unregisterCdmAssociationListener() {
         final CompanionDeviceManager cdm = getContext().getSystemService(
                 CompanionDeviceManager.class);
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 557e4ac..838aae8 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1409,4 +1409,10 @@
      */
     public abstract boolean isPackageQuarantined(@NonNull String packageName,
             @UserIdInt int userId);
+
+    /**
+     * Return a list of all historical install sessions for the given user.
+     */
+    public abstract ParceledListSlice<PackageInstaller.SessionInfo> getHistoricalSessions(
+            int userId);
 }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 60d323a..2249607 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -32,6 +32,7 @@
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.RequiresNoPermission;
+import android.annotation.SuppressLint;
 import android.app.StatsManager;
 import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -83,6 +84,7 @@
 import android.os.health.HealthStatsWriter;
 import android.os.health.UidHealthStats;
 import android.power.PowerStatsInternal;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
@@ -175,6 +177,7 @@
                     .replaceWith("?");
     private static final int MAX_LOW_POWER_STATS_SIZE = 32768;
     private static final int POWER_STATS_QUERY_TIMEOUT_MILLIS = 2000;
+    private static final String MIN_CONSUMED_POWER_THRESHOLD_KEY = "min_consumed_power_threshold";
     private static final String EMPTY = "Empty";
 
     private final HandlerThread mHandlerThread;
@@ -863,12 +866,17 @@
             final BatteryUsageStats bus;
             switch (atomTag) {
                 case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET:
+                    @SuppressLint("MissingPermission")
+                    final double minConsumedPowerThreshold =
+                            DeviceConfig.getFloat(DeviceConfig.NAMESPACE_BATTERY_STATS,
+                                    MIN_CONSUMED_POWER_THRESHOLD_KEY, 0);
                     final BatteryUsageStatsQuery querySinceReset =
                             new BatteryUsageStatsQuery.Builder()
                                     .setMaxStatsAgeMs(0)
                                     .includeProcessStateData()
                                     .includeVirtualUids()
                                     .includePowerModels()
+                                    .setMinConsumedPowerThreshold(minConsumedPowerThreshold)
                                     .build();
                     bus = getBatteryUsageStats(List.of(querySinceReset)).get(0);
                     break;
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 9219623..095d907 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -1975,9 +1975,10 @@
         return mProviderMap.dumpProviderProto(fd, pw, name, args);
     }
 
-    private Boolean isAuthorityRedirectedForCloneProfileCached(String auth) {
+    private boolean isAuthorityRedirectedForCloneProfileCached(String auth) {
         if (mCloneProfileAuthorityRedirectionCache.containsKey(auth)) {
-            return mCloneProfileAuthorityRedirectionCache.get(auth);
+            final Boolean retVal = mCloneProfileAuthorityRedirectionCache.get(auth);
+            return retVal == null ? false : retVal.booleanValue();
         } else {
             boolean isAuthRedirected = isAuthorityRedirectedForCloneProfile(auth);
             mCloneProfileAuthorityRedirectionCache.put(auth, isAuthRedirected);
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 36b8283..bbda952 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -117,20 +117,21 @@
     // All the aconfig flags under the listed DeviceConfig scopes will be synced to native level.
     @VisibleForTesting
     static final String[] sDeviceConfigAconfigScopes = new String[] {
-      "core_experiments_team_internal",
-      "camera_platform",
-      "power",
-      "vibrator",
-      "haptics",
-      "text",
-      "arc_next",
-      "test_suites",
-      "hardware_backed_security_mainline",
-      "threadnetwork",
-      "media_solutions",
-      "responsible_apis",
-      "rust",
-      "pixel_biometrics",
+        "biometrics_framework",
+        "core_experiments_team_internal",
+        "camera_platform",
+        "power",
+        "vibrator",
+        "haptics",
+        "text",
+        "arc_next",
+        "test_suites",
+        "hardware_backed_security_mainline",
+        "threadnetwork",
+        "media_solutions",
+        "responsible_apis",
+        "rust",
+        "pixel_biometrics",
     };
 
     private final String[] mGlobalSettings;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3d5b7fa..0aa9cc1 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -140,6 +140,7 @@
 import android.media.VolumePolicy;
 import android.media.audiofx.AudioEffect;
 import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
 import android.media.audiopolicy.AudioPolicy;
 import android.media.audiopolicy.AudioPolicyConfig;
 import android.media.audiopolicy.AudioProductStrategy;
@@ -12050,6 +12051,49 @@
         }
     }
 
+    /**
+     * Update {@link AudioMixingRule}-s for already registered {@link AudioMix}-es.
+     *
+     * @param mixesToUpdate - array of already registered {@link AudioMix}-es to update.
+     * @param updatedMixingRules - array of {@link AudioMixingRule}-s corresponding to
+     *                           {@code mixesToUpdate} mixes. The array must be same size as
+     *                           {@code mixesToUpdate} and i-th {@link AudioMixingRule} must
+     *                           correspond to i-th {@link AudioMix} from mixesToUpdate array.
+     * @param pcb - {@link IAudioPolicyCallback} corresponding to the registered
+     *              {@link AudioPolicy} all {@link AudioMix}-es for {@code mixesToUpdate}
+     *              are part of.
+     * @return {@link AudioManager#SUCCESS} iff the mixing rules were updated successfully,
+     *     {@link AudioManager#ERROR} otherwise.
+     */
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public int updateMixingRulesForPolicy(
+            @NonNull AudioMix[] mixesToUpdate,
+            @NonNull AudioMixingRule[] updatedMixingRules,
+            @NonNull IAudioPolicyCallback pcb) {
+        super.updateMixingRulesForPolicy_enforcePermission();
+        Objects.requireNonNull(mixesToUpdate);
+        Objects.requireNonNull(updatedMixingRules);
+        Objects.requireNonNull(pcb);
+        if (mixesToUpdate.length != updatedMixingRules.length) {
+            Log.e(TAG, "Provided list of audio mixes to update and corresponding mixing rules "
+                    + "have mismatching length (mixesToUpdate.length = " + mixesToUpdate.length
+                    + ", updatedMixingRules.length = " + updatedMixingRules.length +  ").");
+            return AudioManager.ERROR;
+        }
+        if (DEBUG_AP) {
+            Log.d(TAG, "updateMixingRules for " + pcb.asBinder() + "with mix rules: ");
+        }
+        synchronized (mAudioPolicies) {
+            final AudioPolicyProxy app =
+                    checkUpdateForPolicy(pcb, "Cannot add AudioMix in audio policy");
+            if (app == null) {
+                return AudioManager.ERROR;
+            }
+            return app.updateMixingRules(mixesToUpdate, updatedMixingRules) == AudioSystem.SUCCESS
+                    ? AudioManager.SUCCESS : AudioManager.ERROR;
+        }
+    }
+
     /** see AudioPolicy.setUidDeviceAffinity() */
     public int setUidDeviceAffinity(IAudioPolicyCallback pcb, int uid,
             @NonNull int[] deviceTypes, @NonNull String[] deviceAddresses) {
@@ -12787,6 +12831,35 @@
 
         }
 
+        @AudioSystem.AudioSystemError int updateMixingRules(
+                                        @NonNull AudioMix[] mixesToUpdate,
+                                        @NonNull AudioMixingRule[] updatedMixingRules) {
+            Objects.requireNonNull(mixesToUpdate);
+            Objects.requireNonNull(updatedMixingRules);
+
+            if (mixesToUpdate.length != updatedMixingRules.length) {
+                Log.e(TAG, "Provided list of audio mixes to update and corresponding mixing rules "
+                        + "have mismatching length (mixesToUpdate.length = " + mixesToUpdate.length
+                        + ", updatedMixingRules.length = " + updatedMixingRules.length +  ").");
+                return AudioSystem.BAD_VALUE;
+            }
+
+            synchronized (mMixes) {
+                try (SafeCloseable unused = ClearCallingIdentityContext.create()) {
+                    int ret = mAudioSystem.updateMixingRules(mixesToUpdate, updatedMixingRules);
+                    if (ret == AudioSystem.SUCCESS) {
+                        for (int i = 0; i < mixesToUpdate.length; i++) {
+                            AudioMix audioMixToUpdate = mixesToUpdate[i];
+                            AudioMixingRule audioMixingRule = updatedMixingRules[i];
+                            mMixes.stream().filter(audioMixToUpdate::equals).findAny().ifPresent(
+                                    mix -> mix.setAudioMixingRule(audioMixingRule));
+                        }
+                    }
+                    return ret;
+                }
+            }
+        }
+
         int setUidDeviceAffinities(int uid, @NonNull int[] types, @NonNull String[] addresses) {
             final Integer Uid = new Integer(uid);
             if (mUidDeviceAffinities.remove(Uid) != null) {
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index e70b649..4f46dd1 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -26,6 +26,7 @@
 import android.media.ISoundDose;
 import android.media.ISoundDoseCallback;
 import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
 import android.os.IBinder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -601,6 +602,21 @@
     }
 
     /**
+     * Update already {@link AudioMixingRule}-s for already registered {@link AudioMix}-es.
+     *
+     * @param mixes              - array of registered {@link AudioMix}-es to update.
+     * @param updatedMixingRules - array of {@link AudioMixingRule}-s corresponding to
+     *                           {@code mixesToUpdate} mixes. The array must be same size as
+     *                           {@code mixesToUpdate} and i-th {@link AudioMixingRule} must
+     *                           correspond to i-th {@link AudioMix} from mixesToUpdate array.
+     */
+    public int updateMixingRules(@NonNull AudioMix[] mixes,
+            @NonNull AudioMixingRule[] updatedMixingRules) {
+        invalidateRoutingCache();
+        return AudioSystem.updatePolicyMixes(mixes, updatedMixingRules);
+    }
+
+    /**
      * Same as {@link AudioSystem#setUidDeviceAffinities(int, int[], String[])}
      * @param uid
      * @param types
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index 0d6635d..2d763bc 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -407,6 +407,12 @@
         }
     }
 
+    void stop() {
+        if (mEglContext != null && mEglDisplay != null) {
+            EGL14.eglDestroyContext(mEglDisplay, mEglContext);
+        }
+    }
+
     /**
      * Draws an animation frame showing the color fade activated at the
      * specified level.
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index e6b5057..40dbabf 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -3536,7 +3536,8 @@
 
         DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
                 int displayId, int displayState) {
-            return new DisplayPowerState(blanker, colorFade, displayId, displayState);
+            return new DisplayPowerState(blanker, colorFade, displayId, displayState,
+                    new Handler(/*async=*/ true));
         }
 
         DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 252c29d..051c886 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -319,8 +319,6 @@
     // Must only be accessed on the handler thread.
     private DisplayPowerState mPowerState;
 
-
-
     // The currently active screen on unblocker.  This field is non-null whenever
     // we are waiting for a callback to release it and unblock the screen.
     private ScreenOnUnblocker mPendingScreenOnUnblocker;
@@ -2871,7 +2869,8 @@
 
         DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
                 int displayId, int displayState) {
-            return new DisplayPowerState(blanker, colorFade, displayId, displayState);
+            return new DisplayPowerState(blanker, colorFade, displayId, displayState,
+                    new Handler(/*async=*/ true));
         }
 
         DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 2c257a1..85c6a6d 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -74,8 +74,9 @@
     private volatile boolean mStopped;
 
     DisplayPowerState(
-            DisplayBlanker blanker, ColorFade colorFade, int displayId, int displayState) {
-        mHandler = new Handler(true /*async*/);
+            DisplayBlanker blanker, ColorFade colorFade, int displayId, int displayState,
+            Handler handler) {
+        mHandler = handler;
         mChoreographer = Choreographer.getInstance();
         mBlanker = blanker;
         mColorFade = colorFade;
@@ -317,6 +318,7 @@
         mStopped = true;
         mPhotonicModulator.interrupt();
         dismissColorFade();
+        stopColorFade();
         mCleanListener = null;
         mHandler.removeCallbacksAndMessages(null);
     }
@@ -376,6 +378,11 @@
         }
     }
 
+    // Clears up color fade resources.
+    private void stopColorFade() {
+        if (mColorFade != null) mColorFade.stop();
+    }
+
     private final Runnable mScreenUpdateRunnable = new Runnable() {
         @Override
         public void run() {
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 1cfc7d7..69a6c13 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -33,6 +33,7 @@
 import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.content.pm.PackageManager.MATCH_APEX;
+import static android.content.pm.PackageManager.MATCH_ARCHIVED_PACKAGES;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
@@ -1507,7 +1508,7 @@
                     resolveExternalPackageName(p);
 
             return packageInfo;
-        } else if ((flags & MATCH_UNINSTALLED_PACKAGES) != 0
+        } else if ((flags & (MATCH_UNINSTALLED_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0
                 && PackageUserStateUtils.isAvailable(state, flags)) {
             PackageInfo pi = new PackageInfo();
             pi.packageName = ps.getPackageName();
@@ -1516,6 +1517,7 @@
             pi.sharedUserId = (sharedUser != null) ? sharedUser.getName() : null;
             pi.firstInstallTime = state.getFirstInstallTimeMillis();
             pi.lastUpdateTime = ps.getLastUpdateTime();
+            pi.isArchived = isArchived(state);
 
             ApplicationInfo ai = new ApplicationInfo();
             ai.packageName = ps.getPackageName();
@@ -1614,7 +1616,7 @@
 
             return generatePackageInfo(ps, flags, userId);
         }
-        if (!matchFactoryOnly && (flags & MATCH_KNOWN_PACKAGES) != 0) {
+        if (!matchFactoryOnly && (flags & (MATCH_KNOWN_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0) {
             final PackageStateInternal ps = mSettings.getPackage(packageName);
             if (ps == null) return null;
             if (filterSharedLibPackage(ps, filterCallingUid, userId, flags)) {
@@ -1678,9 +1680,11 @@
         final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
         final boolean listApex = (flags & MATCH_APEX) != 0;
         final boolean listFactory = (flags & MATCH_FACTORY_ONLY) != 0;
+        // Only list archived apps, not fully uninstalled ones. Other entries are unaffected.
+        final boolean listArchivedOnly = !listUninstalled && (flags & MATCH_ARCHIVED_PACKAGES) != 0;
 
         ArrayList<PackageInfo> list;
-        if (listUninstalled) {
+        if (listUninstalled || listArchivedOnly) {
             list = new ArrayList<>(mSettings.getPackages().size());
             for (PackageStateInternal ps : mSettings.getPackages().values()) {
                 if (listFactory) {
@@ -1696,6 +1700,9 @@
                 if (!listApex && ps.getPkg() != null && ps.getPkg().isApex()) {
                     continue;
                 }
+                if (listArchivedOnly && !isArchived(ps.getUserStateOrDefault(userId))) {
+                    continue;
+                }
                 if (filterSharedLibPackage(ps, callingUid, userId, flags)) {
                     continue;
                 }
@@ -1739,6 +1746,13 @@
         return new ParceledListSlice<>(list);
     }
 
+    // TODO(b/288142708) Check for userState.isInstalled() here once this bug is fixed.
+    // If an app has isInstalled() == true - it should not be filtered above in any case, currently
+    // it is.
+    private static boolean isArchived(PackageUserStateInternal userState) {
+        return userState.getArchiveState() != null;
+    }
+
     public final ResolveInfo createForwardingResolveInfoUnchecked(WatchedIntentFilter filter,
             int sourceUserId, int targetUserId) {
         ResolveInfo forwardingResolveInfo = new ResolveInfo();
@@ -2612,7 +2626,7 @@
                 return UserHandle.getUid(userId, p.getUid());
             }
         }
-        if ((flags & MATCH_KNOWN_PACKAGES) != 0) {
+        if ((flags & (MATCH_KNOWN_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0) {
             final PackageStateInternal ps = mSettings.getPackage(packageName);
             if (ps != null && PackageStateUtils.isMatch(ps, flags)
                     && !shouldFilterApplication(ps, callingUid, userId)) {
@@ -3671,7 +3685,7 @@
                         ps.getAppId()));
             }
         }
-        if ((flags & MATCH_KNOWN_PACKAGES) != 0) {
+        if ((flags & (MATCH_KNOWN_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0) {
             if (PackageStateUtils.isMatch(ps, flags)
                     && !shouldFilterApplication(ps, callingUid, userId)) {
                 return mPermissionManager.getGidsForUid(
@@ -4525,7 +4539,8 @@
         flags = updateFlagsForPackage(flags, userId);
         enforceCrossUserPermission(Binder.getCallingUid(), userId, true /* requireFullPermission */,
                 false /* checkShell */, "get packages holding permissions");
-        final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
+        final boolean listUninstalled =
+                (flags & (MATCH_KNOWN_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0;
 
         ArrayList<PackageInfo> list = new ArrayList<>();
         boolean[] tmpBools = new boolean[permissions.length];
diff --git a/services/core/java/com/android/server/pm/PackageArchiverService.java b/services/core/java/com/android/server/pm/PackageArchiverService.java
index 9c31dc9..29f49c8 100644
--- a/services/core/java/com/android/server/pm/PackageArchiverService.java
+++ b/services/core/java/com/android/server/pm/PackageArchiverService.java
@@ -17,17 +17,26 @@
 package com.android.server.pm;
 
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
+import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.content.Context;
+import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.IPackageArchiverService;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
+import android.content.pm.PackageArchiver;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.ParcelableException;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -36,6 +45,7 @@
 import com.android.server.pm.pkg.ArchiveState;
 import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo;
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -51,7 +61,12 @@
  */
 public class PackageArchiverService extends IPackageArchiverService.Stub {
 
-    private static final String TAG = "PackageArchiver";
+    /**
+     * The maximum time granted for an app store to start a foreground service when unarchival
+     * is requested.
+     */
+    // TODO(b/297358628) Make this configurable through a flag.
+    private static final int DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS = 120 * 1000;
 
     private final Context mContext;
     private final PackageManagerService mPm;
@@ -82,17 +97,14 @@
         snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
                 "archiveApp");
         verifyCaller(providedUid, binderUid);
-        PackageStateInternal ps = getPackageState(packageName, snapshot, binderUid, userId);
-        verifyInstaller(packageName, ps);
-
-        // TODO(b/291569242) Verify that this list is not empty and return failure with
-        //  intentsender
-        List<LauncherActivityInfo> mainActivities = getLauncherApps().getActivityList(
-                ps.getPackageName(),
-                new UserHandle(userId));
-
-        // TODO(b/282952870) Bug: should happen after the uninstall completes successfully
-        storeArchiveState(ps, mainActivities, userId);
+        ArchiveState archiveState;
+        try {
+            archiveState = createArchiveState(packageName, userId);
+            // TODO(b/282952870) Should be reverted if uninstall fails/cancels
+            storeArchiveState(packageName, archiveState, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new ParcelableException(e);
+        }
 
         // TODO(b/278553670) Add special strings for the delete dialog
         mPm.mInstallerService.uninstall(
@@ -100,25 +112,154 @@
                 callerPackageName, DELETE_KEEP_DATA, intentSender, userId);
     }
 
-    private static void verifyInstaller(String packageName, PackageStateInternal ps) {
-        if (ps.getInstallSource().mUpdateOwnerPackageName == null
-                && ps.getInstallSource().mInstallerPackageName == null) {
+    private ArchiveState createArchiveState(String packageName, int userId)
+            throws PackageManager.NameNotFoundException {
+        PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(),
+                Binder.getCallingUid(), userId);
+        String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
+        if (responsibleInstallerPackage == null) {
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("No installer found to archive app %s.",
+                            packageName));
+        }
+
+        List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps, userId);
+        List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>();
+        for (int i = 0; i < mainActivities.size(); i++) {
+            // TODO(b/278553670) Extract and store launcher icons
+            ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
+                    mainActivities.get(i).getLabel().toString(),
+                    Path.of("/TODO"), null);
+            archiveActivityInfos.add(activityInfo);
+        }
+
+        return new ArchiveState(archiveActivityInfos, responsibleInstallerPackage);
+    }
+
+    @Override
+    public void requestUnarchive(
+            @NonNull String packageName,
+            @NonNull String callerPackageName,
+            @NonNull UserHandle userHandle) {
+        Objects.requireNonNull(packageName);
+        Objects.requireNonNull(callerPackageName);
+        Objects.requireNonNull(userHandle);
+
+        Computer snapshot = mPm.snapshotComputer();
+        int userId = userHandle.getIdentifier();
+        int binderUid = Binder.getCallingUid();
+        int providedUid = snapshot.getPackageUid(callerPackageName, 0, userId);
+        snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
+                "unarchiveApp");
+        verifyCaller(providedUid, binderUid);
+        PackageStateInternal ps;
+        try {
+            ps = getPackageState(packageName, snapshot, binderUid, userId);
+            verifyArchived(ps, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new ParcelableException(e);
+        }
+        String installerPackage = getResponsibleInstallerPackage(ps);
+        if (installerPackage == null) {
             throw new ParcelableException(
                     new PackageManager.NameNotFoundException(
-                            TextUtils.formatSimple("No installer found to archive app %s.",
+                            TextUtils.formatSimple("No installer found to unarchive app %s.",
                                     packageName)));
         }
+
+        mPm.mHandler.post(() -> unarchiveInternal(packageName, userHandle, installerPackage));
+    }
+
+    private void verifyArchived(PackageStateInternal ps, int userId)
+            throws PackageManager.NameNotFoundException {
+        PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
+        // TODO(b/288142708) Check for isInstalled false here too.
+        if (userState.getArchiveState() == null) {
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("Package %s is not currently archived.",
+                            ps.getPackageName()));
+        }
+    }
+
+    @RequiresPermission(
+            allOf = {
+                    Manifest.permission.INTERACT_ACROSS_USERS,
+                    android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
+                    android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
+                    android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND},
+            conditional = true)
+    private void unarchiveInternal(String packageName, UserHandle userHandle,
+            String installerPackage) {
+        int userId = userHandle.getIdentifier();
+        Intent unarchiveIntent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE);
+        unarchiveIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        unarchiveIntent.putExtra(PackageArchiver.EXTRA_UNARCHIVE_PACKAGE_NAME, packageName);
+        unarchiveIntent.putExtra(PackageArchiver.EXTRA_UNARCHIVE_ALL_USERS,
+                userId == UserHandle.USER_ALL);
+        unarchiveIntent.setPackage(installerPackage);
+
+        // If the unarchival is requested for all users, the current user is used for unarchival.
+        UserHandle userForUnarchival = userId == UserHandle.USER_ALL
+                ? UserHandle.of(mPm.mUserManager.getCurrentUserId())
+                : userHandle;
+        mContext.sendOrderedBroadcastAsUser(
+                unarchiveIntent,
+                userForUnarchival,
+                /* receiverPermission = */ null,
+                AppOpsManager.OP_NONE,
+                createUnarchiveOptions(),
+                /* resultReceiver= */ null,
+                /* scheduler= */ null,
+                /* initialCode= */ 0,
+                /* initialData= */ null,
+                /* initialExtras= */ null);
+    }
+
+    private List<LauncherActivityInfo> getLauncherActivityInfos(PackageStateInternal ps,
+            int userId) throws PackageManager.NameNotFoundException {
+        List<LauncherActivityInfo> mainActivities =
+                Binder.withCleanCallingIdentity(() -> getLauncherApps().getActivityList(
+                        ps.getPackageName(),
+                        new UserHandle(userId)));
+        if (mainActivities.isEmpty()) {
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("The app %s does not have a main activity.",
+                            ps.getPackageName()));
+        }
+
+        return mainActivities;
+    }
+
+    @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
+            android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
+            android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND})
+    private Bundle createUnarchiveOptions() {
+        BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setTemporaryAppAllowlist(getUnarchiveForegroundTimeout(),
+                TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                REASON_PACKAGE_UNARCHIVE, "");
+        return options.toBundle();
+    }
+
+    private static int getUnarchiveForegroundTimeout() {
+        return DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS;
+    }
+
+    private String getResponsibleInstallerPackage(PackageStateInternal ps) {
+        return ps.getInstallSource().mUpdateOwnerPackageName == null
+                ? ps.getInstallSource().mInstallerPackageName
+                : ps.getInstallSource().mUpdateOwnerPackageName;
     }
 
     @NonNull
     private static PackageStateInternal getPackageState(String packageName,
-            Computer snapshot, int callingUid, int userId) {
+            Computer snapshot, int callingUid, int userId)
+            throws PackageManager.NameNotFoundException {
         PackageStateInternal ps = snapshot.getPackageStateFiltered(packageName, callingUid,
                 userId);
         if (ps == null) {
-            throw new ParcelableException(
-                    new PackageManager.NameNotFoundException(
-                            TextUtils.formatSimple("Package %s not found.", packageName)));
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("Package %s not found.", packageName));
         }
         return ps;
     }
@@ -130,38 +271,25 @@
         return mLauncherApps;
     }
 
-    private void storeArchiveState(PackageStateInternal ps,
-            List<LauncherActivityInfo> mainActivities, int userId) {
-        List<ArchiveActivityInfo> activityInfos = new ArrayList<>();
-        for (int i = 0; i < mainActivities.size(); i++) {
-            // TODO(b/278553670) Extract and store launcher icons
-            ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
-                    mainActivities.get(i).getLabel().toString(),
-                    Path.of("/TODO"), null);
-            activityInfos.add(activityInfo);
-        }
-
-        InstallSource installSource = ps.getInstallSource();
-        String installerPackageName = installSource.mUpdateOwnerPackageName != null
-                ? installSource.mUpdateOwnerPackageName : installSource.mInstallerPackageName;
-
+    private void storeArchiveState(String packageName, ArchiveState archiveState, int userId)
+            throws PackageManager.NameNotFoundException {
         synchronized (mPm.mLock) {
-            PackageSetting packageSetting = getPackageSettingLocked(ps.getPackageName(), userId);
+            PackageSetting packageSetting = getPackageSettingLocked(packageName, userId);
             packageSetting
                     .modifyUserState(userId)
-                    .setArchiveState(new ArchiveState(activityInfos, installerPackageName));
+                    .setArchiveState(archiveState);
         }
     }
 
     @NonNull
     @GuardedBy("mPm.mLock")
-    private PackageSetting getPackageSettingLocked(String packageName, int userId) {
+    private PackageSetting getPackageSettingLocked(String packageName, int userId)
+            throws PackageManager.NameNotFoundException {
         PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
         // Shouldn't happen, we already verify presence of the package in getPackageState()
         if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) {
-            throw new ParcelableException(
-                    new PackageManager.NameNotFoundException(
-                            TextUtils.formatSimple("Package %s not found.", packageName)));
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("Package %s not found.", packageName));
         }
         return ps;
     }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java b/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
new file mode 100644
index 0000000..d40a715
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.PackageInstaller.PreapprovalDetails;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageInstaller.SessionParams;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.CharArrayWriter;
+import java.io.File;
+
+/**
+ * A historical session object that stores minimal session info.
+ */
+public final class PackageInstallerHistoricalSession {
+    public final int sessionId;
+    public final int userId;
+    private final String mParams;
+    private final long mCreatedMillis;
+
+    private final File mStageDir;
+    private final String mStageCid;
+
+    private final long mUpdatedMillis;
+
+    private final long mCommittedMillis;
+
+    private final int mOriginalInstallerUid;
+
+    private final String mOriginalInstallerPackageName;
+
+    private final int mInstallerUid;
+
+    private final InstallSource mInstallSource;
+
+    private final float mClientProgress;
+
+    private final float mProgress;
+    private final boolean mSealed;
+
+    private final boolean mPreapprovalRequested;
+    private final boolean mCommitted;
+
+    private final boolean mStageDirInUse;
+
+    private final boolean mPermissionsManuallyAccepted;
+
+    private final int mFinalStatus;
+    private final String mFinalMessage;
+
+    private final int mFds;
+    private final int mBridges;
+
+    private final String mPreapprovalDetails;
+    private final int mParentSessionId;
+    private final boolean mDestroyed;
+    private final int[] mChildSessionIds;
+    private final boolean mSessionApplied;
+    private final boolean mSessionReady;
+    private final boolean mSessionFailed;
+    private final int mSessionErrorCode;
+    private final String mSessionErrorMessage;
+
+    PackageInstallerHistoricalSession(int sessionId, int userId, int originalInstallerUid,
+            String originalInstallerPackageName, InstallSource installSource, int installerUid,
+            long createdMillis, long updatedMillis, long committedMillis, File stageDir,
+            String stageCid, float clientProgress, float progress, boolean committed,
+            boolean preapprovalRequested, boolean sealed, boolean permissionsManuallyAccepted,
+            boolean stageDirInUse, boolean destroyed, int fds, int bridges, int finalStatus,
+            String finalMessage, SessionParams params, int parentSessionId,
+            int[] childSessionIds, boolean sessionApplied, boolean sessionFailed,
+            boolean sessionReady, int sessionErrorCode, String sessionErrorMessage,
+            PreapprovalDetails preapprovalDetails) {
+        this.sessionId = sessionId;
+        this.userId = userId;
+        this.mOriginalInstallerUid = originalInstallerUid;
+        this.mOriginalInstallerPackageName = originalInstallerPackageName;
+        this.mInstallSource = installSource;
+        this.mInstallerUid = installerUid;
+        this.mCreatedMillis = createdMillis;
+        this.mUpdatedMillis = updatedMillis;
+        this.mCommittedMillis = committedMillis;
+        this.mStageDir = stageDir;
+        this.mStageCid = stageCid;
+        this.mClientProgress = clientProgress;
+        this.mProgress = progress;
+        this.mCommitted = committed;
+        this.mPreapprovalRequested = preapprovalRequested;
+        this.mSealed = sealed;
+        this.mPermissionsManuallyAccepted = permissionsManuallyAccepted;
+        this.mStageDirInUse = stageDirInUse;
+        this.mDestroyed = destroyed;
+        this.mFds = fds;
+        this.mBridges = bridges;
+        this.mFinalStatus = finalStatus;
+        this.mFinalMessage = finalMessage;
+
+        CharArrayWriter writer = new CharArrayWriter();
+        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "    ");
+        params.dump(pw);
+        this.mParams = writer.toString();
+
+        this.mParentSessionId = parentSessionId;
+        this.mChildSessionIds = childSessionIds;
+        this.mSessionApplied = sessionApplied;
+        this.mSessionFailed = sessionFailed;
+        this.mSessionReady = sessionReady;
+        this.mSessionErrorCode = sessionErrorCode;
+        this.mSessionErrorMessage = sessionErrorMessage;
+        if (preapprovalDetails != null) {
+            this.mPreapprovalDetails = preapprovalDetails.toString();
+        } else {
+            this.mPreapprovalDetails = null;
+        }
+    }
+
+    void dump(IndentingPrintWriter pw) {
+        pw.println("Session " + sessionId + ":");
+        pw.increaseIndent();
+
+        pw.printPair("userId", userId);
+        pw.printPair("mOriginalInstallerUid", mOriginalInstallerUid);
+        pw.printPair("mOriginalInstallerPackageName", mOriginalInstallerPackageName);
+        pw.printPair("installerPackageName", mInstallSource.mInstallerPackageName);
+        pw.printPair("installInitiatingPackageName", mInstallSource.mInitiatingPackageName);
+        pw.printPair("installOriginatingPackageName", mInstallSource.mOriginatingPackageName);
+        pw.printPair("mInstallerUid", mInstallerUid);
+        pw.printPair("createdMillis", mCreatedMillis);
+        pw.printPair("updatedMillis", mUpdatedMillis);
+        pw.printPair("committedMillis", mCommittedMillis);
+        pw.printPair("stageDir", mStageDir);
+        pw.printPair("stageCid", mStageCid);
+        pw.println();
+
+        pw.print(mParams);
+
+        pw.printPair("mClientProgress", mClientProgress);
+        pw.printPair("mProgress", mProgress);
+        pw.printPair("mCommitted", mCommitted);
+        pw.printPair("mPreapprovalRequested", mPreapprovalRequested);
+        pw.printPair("mSealed", mSealed);
+        pw.printPair("mPermissionsManuallyAccepted", mPermissionsManuallyAccepted);
+        pw.printPair("mStageDirInUse", mStageDirInUse);
+        pw.printPair("mDestroyed", mDestroyed);
+        pw.printPair("mFds", mFds);
+        pw.printPair("mBridges", mBridges);
+        pw.printPair("mFinalStatus", mFinalStatus);
+        pw.printPair("mFinalMessage", mFinalMessage);
+        pw.printPair("mParentSessionId", mParentSessionId);
+        pw.printPair("mChildSessionIds", mChildSessionIds);
+        pw.printPair("mSessionApplied", mSessionApplied);
+        pw.printPair("mSessionFailed", mSessionFailed);
+        pw.printPair("mSessionReady", mSessionReady);
+        pw.printPair("mSessionErrorCode", mSessionErrorCode);
+        pw.printPair("mSessionErrorMessage", mSessionErrorMessage);
+        pw.printPair("mPreapprovalDetails", mPreapprovalDetails);
+        pw.println();
+
+        pw.decreaseIndent();
+    }
+
+    /**
+     * Generates a {@link SessionInfo} object.
+     */
+    public SessionInfo generateInfo() {
+        final SessionInfo info = new SessionInfo();
+        info.sessionId = sessionId;
+        info.userId = userId;
+        info.installerPackageName = mInstallSource.mInstallerPackageName;
+        info.installerAttributionTag = mInstallSource.mInstallerAttributionTag;
+        info.progress = mProgress;
+        info.sealed = mSealed;
+        info.isCommitted = mCommitted;
+        info.isPreapprovalRequested = mPreapprovalRequested;
+
+        info.parentSessionId = mParentSessionId;
+        info.childSessionIds = mChildSessionIds;
+        info.isSessionApplied = mSessionApplied;
+        info.isSessionReady = mSessionReady;
+        info.isSessionFailed = mSessionFailed;
+        info.setSessionErrorCode(mSessionErrorCode, mSessionErrorMessage);
+        info.createdMillis = mCreatedMillis;
+        info.updatedMillis = mUpdatedMillis;
+        info.installerUid = mInstallerUid;
+        return info;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 10cd51a..e360256 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -111,7 +111,6 @@
 
 import org.xmlpull.v1.XmlPullParserException;
 
-import java.io.CharArrayWriter;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -230,7 +229,7 @@
 
     /** Historical sessions kept around for debugging purposes */
     @GuardedBy("mSessions")
-    private final List<String> mHistoricalSessions = new ArrayList<>();
+    private final List<PackageInstallerHistoricalSession> mHistoricalSessions = new ArrayList<>();
 
     @GuardedBy("mSessions")
     private final SparseIntArray mHistoricalSessionsByInstaller = new SparseIntArray();
@@ -570,14 +569,11 @@
 
     @GuardedBy("mSessions")
     private void addHistoricalSessionLocked(PackageInstallerSession session) {
-        CharArrayWriter writer = new CharArrayWriter();
-        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "    ");
-        session.dump(pw);
         if (mHistoricalSessions.size() > HISTORICAL_SESSIONS_THRESHOLD) {
             Slog.d(TAG, "Historical sessions size reaches threshold, clear the oldest");
             mHistoricalSessions.subList(0, HISTORICAL_CLEAR_SIZE).clear();
         }
-        mHistoricalSessions.add(writer.toString());
+        mHistoricalSessions.add(session.createHistoricalSession());
 
         int installerUid = session.getInstallerUid();
         // Increment the number of sessions by this installerUid.
@@ -1223,6 +1219,24 @@
         return new ParceledListSlice<>(result);
     }
 
+    ParceledListSlice<SessionInfo> getHistoricalSessions(int userId) {
+        final int callingUid = Binder.getCallingUid();
+        final Computer snapshot = mPm.snapshotComputer();
+        snapshot.enforceCrossUserPermission(callingUid, userId, true, false, "getAllSessions");
+
+        final List<SessionInfo> result = new ArrayList<>();
+        synchronized (mSessions) {
+            for (int i = 0; i < mHistoricalSessions.size(); i++) {
+                final PackageInstallerHistoricalSession session = mHistoricalSessions.get(i);
+                if (userId == UserHandle.USER_ALL || session.userId == userId) {
+                    result.add(session.generateInfo());
+                }
+            }
+        }
+        result.removeIf(info -> shouldFilterSession(snapshot, callingUid, info));
+        return new ParceledListSlice<>(result);
+    }
+
     @Override
     public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
                 IntentSender statusReceiver, int userId) {
@@ -1837,7 +1851,7 @@
             pw.increaseIndent();
             N = mHistoricalSessions.size();
             for (int i = 0; i < N; i++) {
-                pw.print(mHistoricalSessions.get(i));
+                mHistoricalSessions.get(i).dump(pw);
                 pw.println();
             }
             pw.println();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 1554072..1bdade2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1169,6 +1169,25 @@
         }
     }
 
+    PackageInstallerHistoricalSession createHistoricalSession() {
+        final float progress;
+        final float clientProgress;
+        synchronized (mProgressLock) {
+            progress = mProgress;
+            clientProgress = mClientProgress;
+        }
+        synchronized (mLock) {
+            return new PackageInstallerHistoricalSession(sessionId, userId, mOriginalInstallerUid,
+                    mOriginalInstallerPackageName, mInstallSource, mInstallerUid, createdMillis,
+                    updatedMillis, committedMillis, stageDir, stageCid, clientProgress, progress,
+                    isCommitted(), isPreapprovalRequested(), mSealed, mPermissionsManuallyAccepted,
+                    mStageDirInUse, mDestroyed, mFds.size(), mBridges.size(), mFinalStatus,
+                    mFinalMessage, params, mParentSessionId, getChildSessionIdsLocked(),
+                    mSessionApplied, mSessionFailed, mSessionReady, mSessionErrorCode,
+                    mSessionErrorMessage, mPreapprovalDetails);
+        }
+    }
+
     /**
      * Returns {@code true} if the {@link SessionInfo} object should be produced with potentially
      * sensitive data scrubbed from its fields.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7390bbe..bb4fd66 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6976,6 +6976,11 @@
             return mDistractingPackageHelper.getDistractingPackageRestrictionsAsUser(snapshot,
                     packageNames, userId, callingUid);
         }
+
+        @Override
+        public ParceledListSlice<PackageInstaller.SessionInfo> getHistoricalSessions(int userId) {
+            return mInstallerService.getHistoricalSessions(userId);
+        }
     }
 
     private void setEnabledOverlayPackages(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 3e4dd16..c6aba2a 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1743,6 +1743,10 @@
             android.util.EventLog.writeEvent(0x534e4554, "109824443", -1, "");
             throw new SecurityException("Shortcut package name mismatch");
         }
+        final int callingUid = injectBinderCallingUid();
+        if (UserHandle.getUserId(callingUid) != si.getUserId()) {
+            throw new SecurityException("User-ID in shortcut doesn't match the caller");
+        }
     }
 
     private void verifyShortcutInfoPackages(
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 27812df..4eceb77 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -802,9 +802,7 @@
         // If available for the target user, or trying to match uninstalled packages and it's
         // a system app.
         return PackageUserStateUtils.isAvailable(state, flags)
-                || (pkgSetting.isSystem()
-                && ((flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0
-                || (flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) != 0));
+                || (pkgSetting.isSystem() && matchUninstalledOrHidden(flags));
     }
 
     private static boolean checkUseInstalledOrHidden(long flags,
@@ -819,9 +817,15 @@
         // If available for the target user, or trying to match uninstalled packages and it's
         // a system app.
         return PackageUserStateUtils.isAvailable(state, flags)
-                || (appInfo != null && appInfo.isSystemApp()
-                && ((flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0
-                || (flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) != 0));
+                || (appInfo != null && appInfo.isSystemApp() && matchUninstalledOrHidden(flags));
+    }
+
+    private static boolean matchUninstalledOrHidden(long flags) {
+        return (flags
+                & (PackageManager.MATCH_KNOWN_PACKAGES
+                        | PackageManager.MATCH_ARCHIVED_PACKAGES
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS))
+                != 0;
     }
 
     private static void assignFieldsComponentInfoParsedMainComponent(
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index b01a89e..fc6b12c 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1024,7 +1024,9 @@
             }
 
             synchronized (mLock) {
-                mAttributions.put(source.getToken(), source);
+                // Change the token for the AttributionSource we're storing, so that we don't store
+                // a strong reference to the original token inside the map itself.
+                mAttributions.put(source.getToken(), source.withDefaultToken());
             }
         }
 
@@ -1032,7 +1034,7 @@
             synchronized (mLock) {
                 final AttributionSource cachedSource = mAttributions.get(source.getToken());
                 if (cachedSource != null) {
-                    return cachedSource.equals(source);
+                    return cachedSource.equalsExceptToken(source);
                 }
                 return false;
             }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
index e342453..54f7ebc 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
@@ -87,9 +87,10 @@
         // still return true if the caller requested MATCH_UNINSTALLED_PACKAGES
         final boolean matchAnyUser = (flags & PackageManager.MATCH_ANY_USER) != 0;
         final boolean matchUninstalled = (flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0;
+        final boolean matchArchived = (flags & PackageManager.MATCH_ARCHIVED_PACKAGES) != 0;
         return matchAnyUser
                 || (state.isInstalled()
-                && (!state.isHidden() || matchUninstalled));
+                && (!state.isHidden() || matchUninstalled || matchArchived));
     }
 
     public static boolean reportIfDebug(boolean result, long flags) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b3aa09b..63794d5 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -559,6 +559,7 @@
     boolean mWakeOnDpadKeyPress;
     boolean mWakeOnAssistKeyPress;
     boolean mWakeOnBackKeyPress;
+    boolean mSilenceRingerOnSleepKey;
     long mWakeUpToLastStateTimeout;
     int mSearchKeyBehavior;
     ComponentName mSearchKeyTargetActivity;
@@ -1423,6 +1424,15 @@
     }
 
     private void sleepRelease(long eventTime) {
+        if (mSilenceRingerOnSleepKey) {
+            TelecomManager telecomManager = getTelecommService();
+            if (telecomManager != null && telecomManager.isRinging()) {
+                telecomManager.silenceRinger();
+                Slog.i(TAG, "sleepRelease() silence ringer");
+                return;
+            }
+        }
+
         switch (mShortPressOnSleepBehavior) {
             case SHORT_PRESS_SLEEP_GO_TO_SLEEP:
             case SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME:
@@ -2347,6 +2357,8 @@
                 com.android.internal.R.string.config_primaryShortPressTargetActivity));
         mShortPressOnSleepBehavior = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_shortPressOnSleepBehavior);
+        mSilenceRingerOnSleepKey = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_silenceRingerOnSleepKey);
         mAllowStartActivityForLongPressOnPowerDuringSetup = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowStartActivityForLongPressOnPowerInSetup);
 
@@ -3342,6 +3354,13 @@
                     return true;
                 }
                 break;
+            case KeyEvent.KEYCODE_DEL:
+            case KeyEvent.KEYCODE_GRAVE:
+                if (firstDown && event.isMetaPressed()) {
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
+                    injectBackGesture(event.getDownTime());
+                    return true;
+                }
             case KeyEvent.KEYCODE_DPAD_UP:
                 if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
@@ -3353,9 +3372,14 @@
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_LEFT:
-                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
-                    enterStageSplitFromRunningApp(true /* leftOrTop */);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+                if (firstDown && event.isMetaPressed()) {
+                    if (event.isCtrlPressed()) {
+                        enterStageSplitFromRunningApp(true /* leftOrTop */);
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+                    } else {
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
+                        injectBackGesture(event.getDownTime());
+                    }
                     return true;
                 }
                 break;
@@ -3618,6 +3642,25 @@
         return (metaState & KeyEvent.META_META_ON) != 0;
     }
 
+    @SuppressLint("MissingPermission")
+    private void injectBackGesture(long downtime) {
+        // Create and inject down event
+        KeyEvent downEvent = new KeyEvent(downtime, downtime, KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */,
+                KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
+                KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+                InputDevice.SOURCE_KEYBOARD);
+        mInputManager.injectInputEvent(downEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+
+
+        // Create and inject up event
+        KeyEvent upEvent = KeyEvent.changeAction(downEvent, KeyEvent.ACTION_UP);
+        mInputManager.injectInputEvent(upEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+
+        downEvent.recycle();
+        upEvent.recycle();
+    }
+
     private boolean handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
         // First we always handle the home key here, so applications
         // can never break it, although if keyguard is on, we do let
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 3a32733..f6fa9f2 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -170,10 +170,11 @@
                 && mStats.isProcessStateDataAvailable();
         final boolean includeVirtualUids =  ((query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS) != 0);
+        final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
 
         final BatteryUsageStats.Builder batteryUsageStatsBuilder = new BatteryUsageStats.Builder(
                 mStats.getCustomEnergyConsumerNames(), includePowerModels,
-                includeProcessStateData);
+                includeProcessStateData, minConsumedPowerThreshold);
         // TODO(b/188068523): use a monotonic clock to ensure resilience of order and duration
         // of stats sessions to wall-clock adjustments
         batteryUsageStatsBuilder.setStatsStartTimestamp(mStats.getStartClockTime());
@@ -307,10 +308,12 @@
         final boolean includeProcessStateData = ((query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0)
                 && mStats.isProcessStateDataAvailable();
+        final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
 
         final String[] customEnergyConsumerNames = mStats.getCustomEnergyConsumerNames();
         final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
-                customEnergyConsumerNames, includePowerModels, includeProcessStateData);
+                customEnergyConsumerNames, includePowerModels, includeProcessStateData,
+                minConsumedPowerThreshold);
         if (mBatteryUsageStatsStore == null) {
             Log.e(TAG, "BatteryUsageStatsStore is unavailable");
             return builder.build();
diff --git a/services/core/java/com/android/server/security/Android.bp b/services/core/java/com/android/server/security/Android.bp
new file mode 100644
index 0000000..3f644c44
--- /dev/null
+++ b/services/core/java/com/android/server/security/Android.bp
@@ -0,0 +1,17 @@
+aconfig_declarations {
+    name: "com.android.server.security.flags-aconfig",
+    package: "com.android.server.security",
+    srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "com.android.server.security.flags-aconfig-java",
+    aconfig_declarations: "com.android.server.security.flags-aconfig",
+}
+
+java_aconfig_library {
+    name: "com.android.server.security.flags-aconfig-java-host",
+    aconfig_declarations: "com.android.server.security.flags-aconfig",
+    host_supported: true,
+    test: true,
+}
diff --git a/services/core/java/com/android/server/security/OWNERS b/services/core/java/com/android/server/security/OWNERS
index 5bcc98b6..f408d7c 100644
--- a/services/core/java/com/android/server/security/OWNERS
+++ b/services/core/java/com/android/server/security/OWNERS
@@ -1,4 +1,6 @@
 # Bug component: 36824
 
+include /core/java/android/security/OWNERS
+
 per-file *AttestationVerification* = file:/core/java/android/security/attestationverification/OWNERS
 per-file FileIntegrity*.java = victorhsieh@google.com
diff --git a/services/core/java/com/android/server/security/flags.aconfig b/services/core/java/com/android/server/security/flags.aconfig
new file mode 100644
index 0000000..0440989f
--- /dev/null
+++ b/services/core/java/com/android/server/security/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.security"
+
+flag {
+    name: "deprecate_fsv_sig"
+    namespace: "hardware_backed_security"
+    description: "Feature flag for deprecating .fsv_sig"
+    bug: "277916185"
+}
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
index 3fb845f..e4f9607 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -19,7 +19,7 @@
 import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
 import android.os.vibrator.persistence.ParsedVibration;
 import android.os.vibrator.persistence.VibrationXmlParser;
 import android.text.TextUtils;
@@ -107,10 +107,10 @@
      * @hide
      */
     @Nullable
-    static SparseArray<VibrationEffect> loadVibrations(Resources res, Vibrator vibrator)
+    static SparseArray<VibrationEffect> loadVibrations(Resources res, VibratorInfo vibratorInfo)
             throws CustomizationParserException, IOException {
         try {
-            return loadVibrationsInternal(res, vibrator);
+            return loadVibrationsInternal(res, vibratorInfo);
         } catch (VibrationXmlParser.VibrationXmlParserException
                 | XmlParserException
                 | XmlPullParserException e) {
@@ -121,7 +121,7 @@
 
     @Nullable
     private static SparseArray<VibrationEffect> loadVibrationsInternal(
-            Resources res, Vibrator vibrator) throws
+            Resources res, VibratorInfo vibratorInfo) throws
                     CustomizationParserException,
                     IOException,
                     VibrationXmlParser.VibrationXmlParserException,
@@ -175,7 +175,7 @@
                 throw new CustomizationParserException(
                         "Unable to parse vibration element for effect " + effectId);
             }
-            VibrationEffect effect = parsedVibration.resolve(vibrator);
+            VibrationEffect effect = parsedVibration.resolve(vibratorInfo);
             if (effect != null) {
                 if (effect.getDuration() == Long.MAX_VALUE) {
                     throw new CustomizationParserException(String.format(
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 7c99543..3d89afa 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -21,6 +21,7 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.os.VibratorInfo;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.HapticFeedbackConstants;
@@ -29,8 +30,6 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.HashSet;
-import java.util.Set;
 
 /**
  * Provides the {@link VibrationEffect} and {@link VibrationAttributes} for haptic feedback.
@@ -47,7 +46,7 @@
     private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
 
-    private final Vibrator mVibrator;
+    private final VibratorInfo mVibratorInfo;
     private final boolean mHapticTextHandleEnabled;
     // Vibrator effect for haptic feedback during boot when safe mode is enabled.
     private final VibrationEffect mSafeModeEnabledVibrationEffect;
@@ -58,25 +57,25 @@
 
     /** @hide */
     public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) {
-        this(res, vibrator, loadHapticCustomizations(res, vibrator));
+        this(res, vibrator.getInfo());
+    }
+
+    /** @hide */
+    public HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo) {
+        this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo));
     }
 
     /** @hide */
     @VisibleForTesting HapticFeedbackVibrationProvider(
             Resources res,
-            Vibrator vibrator,
+            VibratorInfo vibratorInfo,
             @Nullable SparseArray<VibrationEffect> hapticCustomizations) {
-        mVibrator = vibrator;
+        mVibratorInfo = vibratorInfo;
         mHapticTextHandleEnabled = res.getBoolean(
                 com.android.internal.R.bool.config_enableHapticTextHandle);
 
-        if (hapticCustomizations != null) {
-            // Clean up the customizations to remove vibrations which may not ever be used due to
-            // Vibrator properties or other device configurations.
-            removeUnsupportedVibrations(hapticCustomizations, vibrator);
-            if (hapticCustomizations.size() == 0) {
-                hapticCustomizations = null;
-            }
+        if (hapticCustomizations != null && hapticCustomizations.size() == 0) {
+            hapticCustomizations = null;
         }
         mHapticCustomizations = hapticCustomizations;
 
@@ -257,7 +256,7 @@
         if (effectHasCustomization(hapticFeedbackId)) {
             return mHapticCustomizations.get(hapticFeedbackId);
         }
-        if (mVibrator.areAllPrimitivesSupported(primitiveId)) {
+        if (mVibratorInfo.isPrimitiveSupported(primitiveId)) {
             return VibrationEffect.startComposition()
                     .addPrimitive(primitiveId, primitiveScale)
                     .compose();
@@ -270,9 +269,8 @@
         if (effectHasCustomization(HapticFeedbackConstants.ASSISTANT_BUTTON)) {
             return mHapticCustomizations.get(HapticFeedbackConstants.ASSISTANT_BUTTON);
         }
-        if (mVibrator.areAllPrimitivesSupported(
-                VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
-                VibrationEffect.Composition.PRIMITIVE_TICK)) {
+        if (mVibratorInfo.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
+                && mVibratorInfo.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK)) {
             // quiet ramp, short pause, then sharp tick
             return VibrationEffect.startComposition()
                     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f)
@@ -289,27 +287,12 @@
 
     @Nullable
     private static SparseArray<VibrationEffect> loadHapticCustomizations(
-            Resources res, Vibrator vibrator) {
+            Resources res, VibratorInfo vibratorInfo) {
         try {
-            return HapticFeedbackCustomization.loadVibrations(res, vibrator);
+            return HapticFeedbackCustomization.loadVibrations(res, vibratorInfo);
         } catch (IOException | HapticFeedbackCustomization.CustomizationParserException e) {
             Slog.e(TAG, "Unable to load haptic customizations.", e);
             return null;
         }
     }
-
-    private static void removeUnsupportedVibrations(
-            SparseArray<VibrationEffect> customizations, Vibrator vibrator) {
-        Set<Integer> keysToRemove = new HashSet<>();
-        for (int i = 0; i < customizations.size(); i++) {
-            int key = customizations.keyAt(i);
-            if (!vibrator.areVibrationFeaturesSupported(customizations.get(key))) {
-                keysToRemove.add(key);
-            }
-        }
-
-        for (int key : keysToRemove) {
-            customizations.remove(key);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index e296c7b..ee3d697 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -28,6 +28,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.hardware.vibrator.IVibrator;
 import android.os.BatteryStats;
 import android.os.Binder;
@@ -131,6 +132,7 @@
 
     private final Object mLock = new Object();
     private final Context mContext;
+    private final Injector mInjector;
     private final PowerManager.WakeLock mWakeLock;
     private final IBatteryStats mBatteryStatsService;
     private final VibratorFrameworkStatsLogger mFrameworkStatsLogger;
@@ -162,6 +164,8 @@
 
     @GuardedBy("mLock")
     @Nullable private VibratorInfo mCombinedVibratorInfo;
+    @GuardedBy("mLock")
+    @Nullable private HapticFeedbackVibrationProvider mHapticFeedbackVibrationProvider;
 
     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
@@ -201,6 +205,7 @@
     @VisibleForTesting
     VibratorManagerService(Context context, Injector injector) {
         mContext = context;
+        mInjector = injector;
         mHandler = injector.createHandler(Looper.myLooper());
 
         mVibrationSettings = new VibrationSettings(mContext, mHandler);
@@ -393,7 +398,42 @@
     @Override // Binder call
     public void vibrate(int uid, int displayId, String opPkg, @NonNull CombinedVibration effect,
             @Nullable VibrationAttributes attrs, String reason, IBinder token) {
-        vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token);
+        vibrateWithPermissionCheck(uid, displayId, opPkg, effect, attrs, reason, token);
+    }
+
+    @Override // Binder call
+    public void performHapticFeedback(
+            int uid, int displayId, String opPkg, int constant, boolean always, String reason,
+            IBinder token) {
+        performHapticFeedbackInternal(uid, displayId, opPkg, constant, always, reason, token);
+    }
+
+    /**
+     * An internal-only version of performHapticFeedback that allows the caller access to the
+     * {@link HalVibration}.
+     * The Vibration is only returned if it is ongoing after this method returns.
+     */
+    @VisibleForTesting
+    @Nullable
+    HalVibration performHapticFeedbackInternal(
+            int uid, int displayId, String opPkg, int constant, boolean always, String reason,
+            IBinder token) {
+        HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
+        if (hapticVibrationProvider == null) {
+            Slog.w(TAG, "performHapticFeedback; haptic vibration provider not ready.");
+            return null;
+        }
+        VibrationEffect effect = hapticVibrationProvider.getVibrationForHapticFeedback(constant);
+        if (effect == null) {
+            Slog.w(TAG, "performHapticFeedback; vibration absent for effect " + constant);
+            return null;
+        }
+        CombinedVibration combinedVibration = CombinedVibration.createParallel(effect);
+        VibrationAttributes attrs =
+                hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
+                        constant, /* bypassVibrationIntensitySetting= */ always);
+        return vibrateWithoutPermissionCheck(uid, displayId, opPkg, combinedVibration, attrs,
+                "performHapticFeedback: " + reason, token);
     }
 
     /**
@@ -403,93 +443,110 @@
      */
     @VisibleForTesting
     @Nullable
-    HalVibration vibrateInternal(int uid, int displayId, String opPkg,
+    HalVibration vibrateWithPermissionCheck(int uid, int displayId, String opPkg,
             @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
             String reason, IBinder token) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
         try {
-            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, "vibrate");
-
-            if (token == null) {
-                Slog.e(TAG, "token must not be null");
-                return null;
-            }
-            enforceUpdateAppOpsStatsPermission(uid);
-            if (!isEffectValid(effect)) {
-                return null;
-            }
-            attrs = fixupVibrationAttributes(attrs, effect);
-            // Create Vibration.Stats as close to the received request as possible, for tracking.
-            HalVibration vib = new HalVibration(token, effect,
-                    new Vibration.CallerInfo(attrs, uid, displayId, opPkg, reason));
-            fillVibrationFallbacks(vib, effect);
-
-            if (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();
-            }
-
-            synchronized (mLock) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Starting vibrate for vibration " + vib.id);
-                }
-
-                // Check if user settings or DnD is set to ignore this vibration.
-                Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
-
-                // Check if ongoing vibration is more important than this vibration.
-                if (vibrationEndInfo == null) {
-                    vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vib);
-                }
-
-                // If not ignored so far then try to start this vibration.
-                if (vibrationEndInfo == null) {
-                    final long ident = Binder.clearCallingIdentity();
-                    try {
-                        if (mCurrentExternalVibration != null) {
-                            mCurrentExternalVibration.mute();
-                            vib.stats.reportInterruptedAnotherVibration(
-                                    mCurrentExternalVibration.callerInfo);
-                            endExternalVibrateLocked(
-                                    new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
-                                            vib.callerInfo),
-                                    /* continueExternalControl= */ false);
-                        } else if (mCurrentVibration != null) {
-                            if (mCurrentVibration.getVibration().canPipelineWith(vib)) {
-                                // Don't cancel the current vibration if it's pipeline-able.
-                                // Note that if there is a pending next vibration that can't be
-                                // pipelined, it will have already cancelled the current one, so we
-                                // don't need to consider it here as well.
-                                if (DEBUG) {
-                                    Slog.d(TAG, "Pipelining vibration " + vib.id);
-                                }
-                            } else {
-                                vib.stats.reportInterruptedAnotherVibration(
-                                        mCurrentVibration.getVibration().callerInfo);
-                                mCurrentVibration.notifyCancelled(
-                                        new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
-                                                vib.callerInfo),
-                                        /* immediate= */ false);
-                            }
-                        }
-                        vibrationEndInfo = startVibrationLocked(vib);
-                    } finally {
-                        Binder.restoreCallingIdentity(ident);
-                    }
-                }
-
-                // Ignored or failed to start the vibration, end it and report metrics right away.
-                if (vibrationEndInfo != null) {
-                    endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ true);
-                }
-                return vib;
-            }
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.VIBRATE, "vibrate");
+            return vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
+    HalVibration vibrateWithoutPermissionCheck(int uid, int displayId, String opPkg,
+            @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
+            String reason, IBinder token) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate no perm check, reason = " + reason);
+        try {
+            return vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    private HalVibration vibrateInternal(int uid, int displayId, String opPkg,
+            @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
+            String reason, IBinder token) {
+        if (token == null) {
+            Slog.e(TAG, "token must not be null");
+            return null;
+        }
+        enforceUpdateAppOpsStatsPermission(uid);
+        if (!isEffectValid(effect)) {
+            return null;
+        }
+        attrs = fixupVibrationAttributes(attrs, effect);
+        // Create Vibration.Stats as close to the received request as possible, for tracking.
+        HalVibration vib = new HalVibration(token, effect,
+                new Vibration.CallerInfo(attrs, uid, displayId, opPkg, reason));
+        fillVibrationFallbacks(vib, effect);
+
+        if (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();
+        }
+
+        synchronized (mLock) {
+            if (DEBUG) {
+                Slog.d(TAG, "Starting vibrate for vibration " + vib.id);
+            }
+
+            // Check if user settings or DnD is set to ignore this vibration.
+            Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
+
+            // Check if ongoing vibration is more important than this vibration.
+            if (vibrationEndInfo == null) {
+                vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vib);
+            }
+
+            // If not ignored so far then try to start this vibration.
+            if (vibrationEndInfo == null) {
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    if (mCurrentExternalVibration != null) {
+                        mCurrentExternalVibration.mute();
+                        vib.stats.reportInterruptedAnotherVibration(
+                                mCurrentExternalVibration.callerInfo);
+                        endExternalVibrateLocked(
+                                new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+                                        vib.callerInfo),
+                                /* continueExternalControl= */ false);
+                    } else if (mCurrentVibration != null) {
+                        if (mCurrentVibration.getVibration().canPipelineWith(vib)) {
+                            // Don't cancel the current vibration if it's pipeline-able.
+                            // Note that if there is a pending next vibration that can't be
+                            // pipelined, it will have already cancelled the current one, so we
+                            // don't need to consider it here as well.
+                            if (DEBUG) {
+                                Slog.d(TAG, "Pipelining vibration " + vib.id);
+                            }
+                        } else {
+                            vib.stats.reportInterruptedAnotherVibration(
+                                    mCurrentVibration.getVibration().callerInfo);
+                            mCurrentVibration.notifyCancelled(
+                                    new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+                                            vib.callerInfo),
+                                    /* immediate= */ false);
+                        }
+                    }
+                    vibrationEndInfo = startVibrationLocked(vib);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+
+            // Ignored or failed to start the vibration, end it and report metrics right away.
+            if (vibrationEndInfo != null) {
+                endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ true);
+            }
+            return vib;
+        }
+    }
+
     @Override // Binder call
     public void cancelVibrate(int usageFilter, IBinder token) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "cancelVibrate");
@@ -1315,6 +1372,11 @@
             return new VibratorController(vibratorId, listener);
         }
 
+        HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
+                Resources resources, VibratorInfo vibratorInfo) {
+            return new HapticFeedbackVibrationProvider(resources, vibratorInfo);
+        }
+
         void addService(String name, IBinder service) {
             ServiceManager.addService(name, service);
         }
@@ -1831,6 +1893,22 @@
         }
     }
 
+    private HapticFeedbackVibrationProvider getHapticVibrationProvider() {
+        synchronized (mLock) {
+            // Used a cached haptic vibration provider if one exists.
+            if (mHapticFeedbackVibrationProvider != null) {
+                return mHapticFeedbackVibrationProvider;
+            }
+            VibratorInfo combinedVibratorInfo = getCombinedVibratorInfo();
+            if (combinedVibratorInfo == null) {
+                return null;
+            }
+            return mHapticFeedbackVibrationProvider =
+                    mInjector.createHapticFeedbackVibrationProvider(
+                            mContext.getResources(), combinedVibratorInfo);
+        }
+    }
+
     private VibratorInfo getCombinedVibratorInfo() {
         synchronized (mLock) {
             // Used a cached resolving vibrator if one exists.
@@ -2068,6 +2146,9 @@
                 if ("cancel".equals(cmd)) {
                     return runCancel();
                 }
+                if ("feedback".equals(cmd)) {
+                    return runHapticFeedback();
+                }
                 return handleDefaultCommands(cmd);
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -2098,16 +2179,10 @@
             // only cancel background vibrations.
             IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
                     : mShellCallbacksToken;
-            HalVibration vib = vibrateInternal(Binder.getCallingUid(), Display.DEFAULT_DISPLAY,
-                    SHELL_PACKAGE_NAME, combined, attrs, commonOptions.description, deathBinder);
-            if (vib != null && !commonOptions.background) {
-                try {
-                    // Waits for the client vibration to finish, but the VibrationThread may still
-                    // do cleanup after this.
-                    vib.waitForEnd();
-                } catch (InterruptedException e) {
-                }
-            }
+            HalVibration vib = vibrateWithPermissionCheck(Binder.getCallingUid(),
+                    Display.DEFAULT_DISPLAY, SHELL_PACKAGE_NAME, combined, attrs,
+                    commonOptions.description, deathBinder);
+            maybeWaitOnVibration(vib, commonOptions);
         }
 
         private int runMono() {
@@ -2155,6 +2230,21 @@
             return 0;
         }
 
+        private int runHapticFeedback() {
+            CommonOptions commonOptions = new CommonOptions();
+            int constant = Integer.parseInt(getNextArgRequired());
+
+            IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
+                    : mShellCallbacksToken;
+            HalVibration vib = performHapticFeedbackInternal(Binder.getCallingUid(),
+                    Display.DEFAULT_DISPLAY, SHELL_PACKAGE_NAME, constant,
+                    /* always= */ commonOptions.force, /* reason= */ commonOptions.description,
+                    deathBinder);
+            maybeWaitOnVibration(vib, commonOptions);
+
+            return 0;
+        }
+
         private VibrationEffect nextEffect() {
             VibrationEffect.Composition composition = VibrationEffect.startComposition();
             String nextArg;
@@ -2364,6 +2454,17 @@
             }
         }
 
+        private void maybeWaitOnVibration(HalVibration vib, CommonOptions commonOptions) {
+            if (vib != null && !commonOptions.background) {
+                try {
+                    // Waits for the client vibration to finish, but the VibrationThread may still
+                    // do cleanup after this.
+                    vib.waitForEnd();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
         @Override
         public void onHelp() {
             try (PrintWriter pw = getOutPrintWriter();) {
@@ -2389,6 +2490,10 @@
                 pw.println("    XML containing a single effect it runs on all vibrators in sync.");
                 pw.println("  cancel");
                 pw.println("    Cancels any active vibration");
+                pw.println("  feedback [-f] [-d <description>] <constant>");
+                pw.println("    Performs a haptic feedback with the given constant.");
+                pw.println("    The force (-f) option enables the `always` configuration, which");
+                pw.println("    plays the haptic irrespective of the vibration intensity settings");
                 pw.println("");
                 pw.println("Effect commands:");
                 pw.println("  oneshot [-w delay] [-a] <duration> [<amplitude>]");
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 90e67df..582536b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2929,7 +2929,7 @@
         reparent(newTaskFrag, position);
     }
 
-    private boolean isHomeIntent(Intent intent) {
+    static boolean isHomeIntent(Intent intent) {
         return ACTION_MAIN.equals(intent.getAction())
                 && (intent.hasCategory(CATEGORY_HOME)
                 || intent.hasCategory(CATEGORY_SECONDARY_HOME))
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 1eb56f1..a5b1132 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -55,6 +55,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.Pair;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -77,7 +78,6 @@
 
     private final ActivityTaskManagerService mService;
     private final ActivityTaskSupervisor mSupervisor;
-    private final RootWindowContainer mRootWindowContainer;
     private final Context mServiceContext;
 
     // UserManager cannot be final as it's not ready when this class is instantiated during boot
@@ -110,17 +110,23 @@
     TaskFragment mInTaskFragment;
     ActivityOptions mActivityOptions;
 
+    /*
+     * Note that this is just a hint of what the launch display area will be as it is
+     * based only on the information at the early pre-interception stage of starting the
+     * intent. The real launch display area calculated later may be different from this one.
+     */
+    TaskDisplayArea mPresumableLaunchDisplayArea;
+
     ActivityStartInterceptor(
             ActivityTaskManagerService service, ActivityTaskSupervisor supervisor) {
-        this(service, supervisor, service.mRootWindowContainer, service.mContext);
+        this(service, supervisor, service.mContext);
     }
 
     @VisibleForTesting
     ActivityStartInterceptor(ActivityTaskManagerService service, ActivityTaskSupervisor supervisor,
-            RootWindowContainer root, Context context) {
+            Context context) {
         mService = service;
         mSupervisor = supervisor;
-        mRootWindowContainer = root;
         mServiceContext = context;
     }
 
@@ -162,7 +168,7 @@
     /**
      * A helper function to obtain the targeted {@link TaskFragment} during
      * {@link #intercept(Intent, ResolveInfo, ActivityInfo, String, Task, TaskFragment, int, int,
-     * ActivityOptions)} if any.
+     * ActivityOptions, TaskDisplayArea)} if any.
      */
     @Nullable
     private TaskFragment getLaunchTaskFragment() {
@@ -187,7 +193,7 @@
      */
     boolean intercept(Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType,
             Task inTask, TaskFragment inTaskFragment, int callingPid, int callingUid,
-            ActivityOptions activityOptions) {
+            ActivityOptions activityOptions, TaskDisplayArea presumableLaunchDisplayArea) {
         mUserManager = UserManager.get(mServiceContext);
 
         mIntent = intent;
@@ -199,6 +205,7 @@
         mInTask = inTask;
         mInTaskFragment = inTaskFragment;
         mActivityOptions = activityOptions;
+        mPresumableLaunchDisplayArea = presumableLaunchDisplayArea;
 
         if (interceptQuietProfileIfNeeded()) {
             // If work profile is turned off, skip the work challenge since the profile can only
@@ -221,6 +228,11 @@
         if (interceptLockedManagedProfileIfNeeded()) {
             return true;
         }
+        if (interceptHomeIfNeeded()) {
+            // Replace primary home intents directed at displays that do not support primary home
+            // but support secondary home with the relevant secondary home activity.
+            return true;
+        }
 
         final SparseArray<ActivityInterceptorCallback> callbacks =
                 mService.getActivityInterceptorCallbacks();
@@ -470,6 +482,47 @@
         return true;
     }
 
+    private boolean interceptHomeIfNeeded() {
+        if (mPresumableLaunchDisplayArea == null || mService.mRootWindowContainer == null) {
+            return false;
+        }
+        if (!ActivityRecord.isHomeIntent(mIntent)) {
+            return false;
+        }
+        if (!mIntent.hasCategory(Intent.CATEGORY_HOME)) {
+            // Already a secondary home intent, leave it alone.
+            return false;
+        }
+        if (mService.mRootWindowContainer.shouldPlacePrimaryHomeOnDisplay(
+                mPresumableLaunchDisplayArea.getDisplayId())) {
+            // Primary home can be launched to the display area.
+            return false;
+        }
+        if (!mService.mRootWindowContainer.shouldPlaceSecondaryHomeOnDisplayArea(
+                mPresumableLaunchDisplayArea)) {
+            // Secondary home cannot be launched on the display area.
+            return false;
+        }
+
+        // At this point we have a primary home intent for a display that does not support primary
+        // home activity but it supports secondary home one. So replace it with secondary home.
+        Pair<ActivityInfo, Intent> info = mService.mRootWindowContainer
+                .resolveSecondaryHomeActivity(mUserId, mPresumableLaunchDisplayArea);
+        mIntent = info.second;
+        // The new task flag is needed because the home activity should already be in the root task
+        // and should not be moved to the caller's task. Also, activities cannot change their type,
+        // e.g. a standard activity cannot become a home activity.
+        mIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+        mCallingPid = mRealCallingPid;
+        mCallingUid = mRealCallingUid;
+        mResolvedType = null;
+
+        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, /* flags= */ 0,
+                mRealCallingUid, mRealCallingPid);
+        mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, /*profilerInfo=*/ null);
+        return true;
+    }
+
     private boolean isPackageSuspended() {
         return mAInfo != null && mAInfo.applicationInfo != null
                 && (mAInfo.applicationInfo.flags & FLAG_SUSPENDED) != 0;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 1bc78d6..458d1e8 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1154,10 +1154,12 @@
             }
         }
 
+        final TaskDisplayArea suggestedLaunchDisplayArea =
+                computeSuggestedLaunchDisplayArea(inTask, sourceRecord, checkedOptions);
         mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags, callingPackage,
                 callingFeatureId);
         if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, inTaskFragment,
-                callingPid, callingUid, checkedOptions)) {
+                callingPid, callingUid, checkedOptions, suggestedLaunchDisplayArea)) {
             // activity start was intercepted, e.g. because the target user is currently in quiet
             // mode (turn off work) or the target application is suspended
             intent = mInterceptor.mIntent;
@@ -1890,6 +1892,15 @@
         mPreferredWindowingMode = mLaunchParams.mWindowingMode;
     }
 
+    private TaskDisplayArea computeSuggestedLaunchDisplayArea(
+            Task task, ActivityRecord source, ActivityOptions options) {
+        mSupervisor.getLaunchParamsController().calculate(task, /*layout=*/null,
+                /*activity=*/ null, source, options, mRequest, PHASE_DISPLAY, mLaunchParams);
+        return mLaunchParams.hasPreferredTaskDisplayArea()
+                ? mLaunchParams.mPreferredTaskDisplayArea
+                : mRootWindowContainer.getDefaultTaskDisplayArea();
+    }
+
     @VisibleForTesting
     int isAllowedToStart(ActivityRecord r, boolean newTask, Task targetTask) {
         if (r.packageName == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index bed52be..fd42077 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2648,9 +2648,15 @@
             mAmInternal.enforceCallingPermission(Manifest.permission.UPDATE_LOCK_TASK_PACKAGES,
                     "updateLockTaskPackages()");
         }
-        synchronized (mGlobalLock) {
-            ProtoLog.w(WM_DEBUG_LOCKTASK, "Allowlisting %d:%s", userId, Arrays.toString(packages));
-            getLockTaskController().updateLockTaskPackages(userId, packages);
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                ProtoLog.w(WM_DEBUG_LOCKTASK, "Allowlisting %d:%s", userId,
+                        Arrays.toString(packages));
+                getLockTaskController().updateLockTaskPackages(userId, packages);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 9e3a611..97b3e32 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -880,6 +880,58 @@
                         false /* forTabletopMode */);
     }
 
+    /**
+     * Overrides persistent horizontal position of the letterboxed app window when horizontal
+     * reachability is enabled.
+     */
+    void setPersistentLetterboxPositionForHorizontalReachability(boolean forBookMode,
+            @LetterboxHorizontalReachabilityPosition int position) {
+        mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+                forBookMode, position);
+    }
+
+    /**
+     * Overrides persistent vertical position of the letterboxed app window when vertical
+     * reachability is enabled.
+     */
+    void setPersistentLetterboxPositionForVerticalReachability(boolean forTabletopMode,
+            @LetterboxVerticalReachabilityPosition int position) {
+        mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+                forTabletopMode, position);
+    }
+
+    /**
+     * Resets persistent horizontal position of the letterboxed app window when horizontal
+     * reachability
+     * is enabled to default position.
+     */
+    void resetPersistentLetterboxPositionForHorizontalReachability() {
+        mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+                false /* forBookMode */,
+                readLetterboxHorizontalReachabilityPositionFromConfig(mContext,
+                        false /* forBookMode */));
+        mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+                true /* forBookMode */,
+                readLetterboxHorizontalReachabilityPositionFromConfig(mContext,
+                        true /* forBookMode */));
+    }
+
+    /**
+     * Resets persistent vertical position of the letterboxed app window when vertical reachability
+     * is
+     * enabled to default position.
+     */
+    void resetPersistentLetterboxPositionForVerticalReachability() {
+        mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+                false /* forTabletopMode */,
+                readLetterboxVerticalReachabilityPositionFromConfig(mContext,
+                        false /* forTabletopMode */));
+        mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+                true /* forTabletopMode */,
+                readLetterboxVerticalReachabilityPositionFromConfig(mContext,
+                        true /* forTabletopMode */));
+    }
+
     @LetterboxHorizontalReachabilityPosition
     private static int readLetterboxHorizontalReachabilityPositionFromConfig(Context context,
             boolean forBookMode) {
diff --git a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
index e646f14..106e142 100644
--- a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
+++ b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.util.Slog;
 import android.view.IDisplayChangeWindowCallback;
 import android.window.DisplayAreaInfo;
@@ -40,6 +41,7 @@
 public class RemoteDisplayChangeController {
 
     private static final String TAG = "RemoteDisplayChangeController";
+    private static final String REMOTE_DISPLAY_CHANGE_TRACE_TAG = "RemoteDisplayChange";
 
     private static final int REMOTE_DISPLAY_CHANGE_TIMEOUT_MS = 800;
 
@@ -82,6 +84,10 @@
         }
         mCallbacks.add(callback);
 
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+            Trace.beginAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+        }
+
         if (newDisplayAreaInfo != null) {
             ProtoLog.v(WM_DEBUG_CONFIGURATION,
                     "Starting remote display change: "
@@ -122,6 +128,10 @@
                     mCallbacks.clear();
                 }
                 callback.onContinueRemoteDisplayChange(null /* transaction */);
+
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+                    Trace.endAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+                }
             }
         }
     }
@@ -137,13 +147,23 @@
             for (int i = 0; i < idx; ++i) {
                 // Expect remote callbacks in order. If they don't come in order, then force
                 // ordering by continuing everything up until this one with empty transactions.
-                mCallbacks.get(i).onContinueRemoteDisplayChange(null /* transaction */);
+                ContinueRemoteDisplayChangeCallback currentCallback = mCallbacks.get(i);
+                currentCallback.onContinueRemoteDisplayChange(null /* transaction */);
+
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+                    Trace.endAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG,
+                            currentCallback.hashCode());
+                }
             }
             mCallbacks.subList(0, idx + 1).clear();
             if (mCallbacks.isEmpty()) {
                 mService.mH.removeCallbacks(mTimeoutRunnable);
             }
             callback.onContinueRemoteDisplayChange(transaction);
+
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+                Trace.endAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 57f8268..d56acaa 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1608,6 +1608,19 @@
     }
 
     /**
+     * Check if the display is valid for primary home activity.
+     *
+     * @param displayId The target display ID
+     * @return {@code true} if allowed to launch, {@code false} otherwise.
+     */
+    boolean shouldPlacePrimaryHomeOnDisplay(int displayId) {
+        // No restrictions to default display, vr 2d display or main display for visible users.
+        return displayId == DEFAULT_DISPLAY || (displayId != INVALID_DISPLAY
+                && (displayId == mService.mVr2dDisplayId
+                || mWmService.shouldPlacePrimaryHomeOnDisplay(displayId)));
+    }
+
+    /**
      * Check if the display area is valid for secondary home activity.
      *
      * @param taskDisplayArea The target display area.
@@ -1680,10 +1693,7 @@
 
         final int displayId = taskDisplayArea != null ? taskDisplayArea.getDisplayId()
                 : INVALID_DISPLAY;
-        if (displayId == DEFAULT_DISPLAY || (displayId != INVALID_DISPLAY
-                && (displayId == mService.mVr2dDisplayId
-                || mWmService.shouldPlacePrimaryHomeOnDisplay(displayId)))) {
-            // No restrictions to default display, vr 2d display or main display for visible users.
+        if (shouldPlacePrimaryHomeOnDisplay(displayId)) {
             return true;
         }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 387a876..21a4fe8 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2815,7 +2815,7 @@
         final WindowManager.LayoutParams attrs = win.mAttrs;
         visibleFrame.set(win.getFrame());
         visibleFrame.inset(win.getInsetsStateWithVisibilityOverride().calculateVisibleInsets(
-                visibleFrame, attrs.type, win.getWindowingMode(), attrs.softInputMode,
+                visibleFrame, attrs.type, win.getActivityType(), attrs.softInputMode,
                 attrs.flags));
         out.union(visibleFrame);
     }
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index ad46770..4e7a9bd 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -118,12 +118,14 @@
             root = activity;
         }
 
-        if (root == null) {
+        if (root == null && phase != PHASE_DISPLAY) {
             // There is a case that can lead us here. The caller is moving the top activity that is
             // in a task that has multiple activities to PIP mode. For that the caller is creating a
             // new task to host the activity so that we only move the top activity to PIP mode and
             // keep other activities in the previous task. There is no point to apply the launch
             // logic in this case.
+            // However, for PHASE_DISPLAY the root may be null, but we still want to get a hint of
+            // what the suggested launch display area would be.
             return RESULT_SKIP;
         }
 
@@ -395,8 +397,9 @@
     }
 
     private TaskDisplayArea getPreferredLaunchTaskDisplayArea(@Nullable Task task,
-            @Nullable ActivityOptions options, ActivityRecord source, LaunchParams currentParams,
-            @NonNull ActivityRecord activityRecord, @Nullable Request request) {
+            @Nullable ActivityOptions options, @Nullable ActivityRecord source,
+            @Nullable LaunchParams currentParams, @Nullable ActivityRecord activityRecord,
+            @Nullable Request request) {
         TaskDisplayArea taskDisplayArea = null;
 
         final WindowContainerToken optionLaunchTaskDisplayAreaToken = options != null
@@ -438,8 +441,7 @@
 
         // If the source activity is a no-display activity, pass on the launch display area token
         // from source activity as currently preferred.
-        if (taskDisplayArea == null && source != null
-                && source.noDisplay) {
+        if (taskDisplayArea == null && source != null && source.noDisplay) {
             taskDisplayArea = source.mHandoverTaskDisplayArea;
             if (taskDisplayArea != null) {
                 if (DEBUG) appendLog("display-area-from-no-display-source=" + taskDisplayArea);
@@ -478,21 +480,24 @@
             }
         }
 
-        if (taskDisplayArea == null) {
+        if (taskDisplayArea == null && currentParams != null) {
             taskDisplayArea = currentParams.mPreferredTaskDisplayArea;
+            if (DEBUG) appendLog("display-area-from-current-params=" + taskDisplayArea);
         }
 
         // Re-route to default display if the device didn't declare support for multi-display
         if (taskDisplayArea != null && !mSupervisor.mService.mSupportsMultiDisplay
                 && taskDisplayArea.getDisplayId() != DEFAULT_DISPLAY) {
             taskDisplayArea = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
+            if (DEBUG) appendLog("display-area-from-no-multidisplay=" + taskDisplayArea);
         }
 
         // Re-route to default display if the home activity doesn't support multi-display
-        if (taskDisplayArea != null && activityRecord.isActivityTypeHome()
+        if (taskDisplayArea != null && activityRecord != null && activityRecord.isActivityTypeHome()
                 && !mSupervisor.mRootWindowContainer.canStartHomeOnDisplayArea(activityRecord.info,
                         taskDisplayArea, false /* allowInstrumenting */)) {
             taskDisplayArea = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
+            if (DEBUG) appendLog("display-area-from-home=" + taskDisplayArea);
         }
 
         return (taskDisplayArea != null)
@@ -516,34 +521,56 @@
      * @return {@link TaskDisplayArea} to house the task
      */
     private TaskDisplayArea getFallbackDisplayAreaForActivity(
-            @NonNull ActivityRecord activityRecord, @Nullable Request request) {
+            @Nullable ActivityRecord activityRecord, @Nullable Request request) {
+        if (activityRecord != null) {
+            WindowProcessController controllerFromLaunchingRecord =
+                    mSupervisor.mService.getProcessController(
+                            activityRecord.launchedFromPid, activityRecord.launchedFromUid);
+            if (controllerFromLaunchingRecord != null) {
+                final TaskDisplayArea taskDisplayAreaForLaunchingRecord =
+                        controllerFromLaunchingRecord.getTopActivityDisplayArea();
+                if (taskDisplayAreaForLaunchingRecord != null) {
+                    if (DEBUG) {
+                        appendLog("display-area-for-launching-record="
+                                + taskDisplayAreaForLaunchingRecord);
+                    }
+                    return taskDisplayAreaForLaunchingRecord;
+                }
+            }
 
-        WindowProcessController controllerFromLaunchingRecord = mSupervisor.mService
-                .getProcessController(activityRecord.launchedFromPid,
-                        activityRecord.launchedFromUid);
-        final TaskDisplayArea displayAreaForLaunchingRecord = controllerFromLaunchingRecord == null
-                ? null : controllerFromLaunchingRecord.getTopActivityDisplayArea();
-        if (displayAreaForLaunchingRecord != null) {
-            return displayAreaForLaunchingRecord;
+            WindowProcessController controllerFromProcess =
+                    mSupervisor.mService.getProcessController(
+                            activityRecord.getProcessName(), activityRecord.getUid());
+            if (controllerFromProcess != null) {
+                final TaskDisplayArea displayAreaForRecord =
+                        controllerFromProcess.getTopActivityDisplayArea();
+                if (displayAreaForRecord != null) {
+                    if (DEBUG) appendLog("display-area-for-record=" + displayAreaForRecord);
+                    return displayAreaForRecord;
+                }
+            }
         }
 
-        WindowProcessController controllerFromProcess = mSupervisor.mService.getProcessController(
-                activityRecord.getProcessName(), activityRecord.getUid());
-        final TaskDisplayArea displayAreaForRecord = controllerFromProcess == null ? null
-                : controllerFromProcess.getTopActivityDisplayArea();
-        if (displayAreaForRecord != null) {
-            return displayAreaForRecord;
+        if (request != null) {
+            WindowProcessController controllerFromRequest =
+                    mSupervisor.mService.getProcessController(
+                            request.realCallingPid, request.realCallingUid);
+            if (controllerFromRequest != null) {
+                final TaskDisplayArea displayAreaFromSourceProcess =
+                            controllerFromRequest.getTopActivityDisplayArea();
+                if (displayAreaFromSourceProcess != null) {
+                    if (DEBUG) {
+                        appendLog("display-area-source-process=" + displayAreaFromSourceProcess);
+                    }
+                    return displayAreaFromSourceProcess;
+                }
+            }
         }
 
-        WindowProcessController controllerFromRequest = request == null ? null : mSupervisor
-                .mService.getProcessController(request.realCallingPid, request.realCallingUid);
-        final TaskDisplayArea displayAreaFromSourceProcess = controllerFromRequest == null ? null
-                : controllerFromRequest.getTopActivityDisplayArea();
-        if (displayAreaFromSourceProcess != null) {
-            return displayAreaFromSourceProcess;
-        }
-
-        return mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
+        final TaskDisplayArea defaultTaskDisplayArea =
+                mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
+        if (DEBUG) appendLog("display-area-from-default-fallback=" + defaultTaskDisplayArea);
+        return defaultTaskDisplayArea;
     }
 
     private boolean canInheritWindowingModeFromSource(@NonNull DisplayContent display,
@@ -559,7 +586,7 @@
             return false;
         }
 
-        final int sourceWindowingMode = source.getWindowingMode();
+        final int sourceWindowingMode = source.getTask().getWindowingMode();
         if (sourceWindowingMode != WINDOWING_MODE_FULLSCREEN
                 && sourceWindowingMode != WINDOWING_MODE_FREEFORM) {
             return false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index ceebb27..bfe0553 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -911,6 +911,70 @@
         return 0;
     }
 
+    private int runSetPersistentLetterboxPositionForHorizontalReachability(PrintWriter pw)
+            throws RemoteException {
+        @LetterboxHorizontalReachabilityPosition final int position;
+        try {
+            String arg = getNextArgRequired();
+            switch (arg) {
+                case "left":
+                    position = LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+                    break;
+                case "center":
+                    position = LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+                    break;
+                case "right":
+                    position = LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+                    break;
+                default:
+                    getErrPrintWriter().println(
+                            "Error: 'left', 'center' or 'right' are expected as an argument");
+                    return -1;
+            }
+        } catch (IllegalArgumentException e) {
+            getErrPrintWriter().println(
+                    "Error: 'left', 'center' or 'right' are expected as an argument" + e);
+            return -1;
+        }
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setPersistentLetterboxPositionForHorizontalReachability(
+                    false /* IsInBookMode */, position);
+        }
+        return 0;
+    }
+
+    private int runSetPersistentLetterboxPositionForVerticalReachability(PrintWriter pw)
+            throws RemoteException {
+        @LetterboxVerticalReachabilityPosition final int position;
+        try {
+            String arg = getNextArgRequired();
+            switch (arg) {
+                case "top":
+                    position = LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+                    break;
+                case "center":
+                    position = LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+                    break;
+                case "bottom":
+                    position = LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
+                    break;
+                default:
+                    getErrPrintWriter().println(
+                            "Error: 'top', 'center' or 'bottom' are expected as an argument");
+                    return -1;
+            }
+        } catch (IllegalArgumentException e) {
+            getErrPrintWriter().println(
+                    "Error: 'top', 'center' or 'bottom' are expected as an argument" + e);
+            return -1;
+        }
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setPersistentLetterboxPositionForVerticalReachability(
+                    false /* forTabletopMode */, position);
+        }
+        return 0;
+    }
+
     private int runSetBooleanFlag(PrintWriter pw, Consumer<Boolean> setter)
             throws RemoteException {
         String arg = getNextArg();
@@ -994,6 +1058,12 @@
                 case "--defaultPositionForVerticalReachability":
                     runSetLetterboxDefaultPositionForVerticalReachability(pw);
                     break;
+                case "--persistentPositionForHorizontalReachability":
+                    runSetPersistentLetterboxPositionForHorizontalReachability(pw);
+                    break;
+                case "--persistentPositionForVerticalReachability":
+                    runSetPersistentLetterboxPositionForVerticalReachability(pw);
+                    break;
                 case "--isEducationEnabled":
                     runSetBooleanFlag(pw, mLetterboxConfiguration::setIsEducationEnabled);
                     break;
@@ -1080,6 +1150,14 @@
                     case "defaultPositionForVerticalReachability":
                         mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
                         break;
+                    case "persistentPositionForHorizontalReachability":
+                        mLetterboxConfiguration
+                                .resetPersistentLetterboxPositionForHorizontalReachability();
+                        break;
+                    case "persistentPositionForVerticalReachability":
+                        mLetterboxConfiguration
+                                .resetPersistentLetterboxPositionForVerticalReachability();
+                        break;
                     case "isEducationEnabled":
                         mLetterboxConfiguration.resetIsEducationEnabled();
                         break;
@@ -1206,6 +1284,8 @@
             mLetterboxConfiguration.resetEnabledAutomaticReachabilityInBookMode();
             mLetterboxConfiguration.resetDefaultPositionForHorizontalReachability();
             mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
+            mLetterboxConfiguration.resetPersistentLetterboxPositionForHorizontalReachability();
+            mLetterboxConfiguration.resetPersistentLetterboxPositionForVerticalReachability();
             mLetterboxConfiguration.resetIsEducationEnabled();
             mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
             mLetterboxConfiguration.resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
@@ -1233,6 +1313,12 @@
             pw.println("Vertical position multiplier (tabletop mode): "
                     + mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier(
                             true /* isInTabletopMode */));
+            pw.println("Horizontal position multiplier for reachability: "
+                    + mLetterboxConfiguration.getHorizontalMultiplierForReachability(
+                            false /* isInBookMode */));
+            pw.println("Vertical position multiplier for reachability: "
+                    + mLetterboxConfiguration.getVerticalMultiplierForReachability(
+                            false /* isInTabletopMode */));
             pw.println("Aspect ratio: "
                     + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
             pw.println("Default min aspect ratio for unresizable apps: "
@@ -1472,6 +1558,12 @@
         pw.println("      --defaultPositionForVerticalReachability [top|center|bottom]");
         pw.println("        Default position of app window when vertical reachability is.");
         pw.println("        enabled.");
+        pw.println("      --persistentPositionForHorizontalReachability [left|center|right]");
+        pw.println("        Persistent position of app window when horizontal reachability is.");
+        pw.println("        enabled.");
+        pw.println("      --persistentPositionForVerticalReachability [top|center|bottom]");
+        pw.println("        Persistent position of app window when vertical reachability is.");
+        pw.println("        enabled.");
         pw.println("      --isEducationEnabled [true|1|false|0]");
         pw.println("        Whether education is allowed for letterboxed fullscreen apps.");
         pw.println("      --isSplitScreenAspectRatioForUnresizableAppsEnabled [true|1|false|0]");
@@ -1493,8 +1585,10 @@
         pw.println("      |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
         pw.println("      |horizontalPositionMultiplier|verticalPositionMultiplier");
         pw.println("      |isHorizontalReachabilityEnabled|isVerticalReachabilityEnabled");
-        pw.println("      |isEducationEnabled||defaultPositionMultiplierForHorizontalReachability");
+        pw.println("      |isEducationEnabled|defaultPositionMultiplierForHorizontalReachability");
         pw.println("      |isTranslucentLetterboxingEnabled|isUserAppAspectRatioSettingsEnabled");
+        pw.println("      |persistentPositionMultiplierForHorizontalReachability");
+        pw.println("      |persistentPositionMultiplierForVerticalReachability");
         pw.println("      |defaultPositionMultiplierForVerticalReachability]");
         pw.println("    Resets overrides to default values for specified properties separated");
         pw.println("    by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a172d99..9c04e0a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1759,7 +1759,7 @@
 
         bounds.set(mWindowFrames.mFrame);
         bounds.inset(getInsetsStateWithVisibilityOverride().calculateVisibleInsets(
-                bounds, mAttrs.type, getWindowingMode(), mAttrs.softInputMode, mAttrs.flags));
+                bounds, mAttrs.type, getActivityType(), mAttrs.softInputMode, mAttrs.flags));
         if (intersectWithRootTaskBounds) {
             bounds.intersect(mTmpRect);
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2be2bb9..438a9d6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -680,7 +680,7 @@
     // to decide whether an existing policy in the {@link #DEVICE_POLICIES_XML} needs to
     // be upgraded. See {@link PolicyVersionUpgrader} on instructions how to add an upgrade
     // step.
-    static final int DPMS_VERSION = 5;
+    static final int DPMS_VERSION = 6;
 
     static {
         SECURE_SETTINGS_ALLOWLIST = new ArraySet<>();
@@ -876,8 +876,7 @@
     private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = true;
 
     // TODO(b/265683382) remove the flag after rollout.
-    private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
-    public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = true;
+    public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false;
 
     // TODO(b/261999445) remove the flag after rollout.
     private static final String HEADLESS_FLAG = "headless";
@@ -23011,10 +23010,7 @@
     }
 
     private static boolean isKeepProfilesRunningFlagEnabled() {
-        return DeviceConfig.getBoolean(
-                NAMESPACE_DEVICE_POLICY_MANAGER,
-                KEEP_PROFILES_RUNNING_FLAG,
-                DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
+        return DEFAULT_KEEP_PROFILES_RUNNING_FLAG;
     }
 
     private boolean isUnicornFlagEnabled() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
index 733b1d9..f060426 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
@@ -117,6 +117,19 @@
             currentVersion = 5;
         }
 
+        if (currentVersion == 5) {
+            Slog.i(LOG_TAG, String.format("Upgrading from version %d", currentVersion));
+            // No-op upgrade here:
+            // DevicePolicyData.mEffectiveKeepProfilesRunning is only stored in XML file when it is
+            // different from its default value, otherwise the tag is not written. When loading, if
+            // the tag is missing, the field retains the value previously assigned in the
+            // constructor, which is the default value.
+            // In version 5 the default value was 'true', in version 6 it is 'false', so when
+            // loading XML version 5 we need to initialize the field to 'true' for it to be restored
+            // correctly in case the tag is missing. This is done in loadDataForUser().
+            currentVersion = 6;
+        }
+
         writePoliciesAndVersion(allUsers, allUsersData, ownersData, currentVersion);
     }
 
@@ -282,6 +295,10 @@
     private DevicePolicyData loadDataForUser(
             int userId, int loadVersion, ComponentName ownerComponent) {
         DevicePolicyData policy = new DevicePolicyData(userId);
+        // See version 5 -> 6 step in upgradePolicy()
+        if (loadVersion == 5 && userId == UserHandle.USER_SYSTEM) {
+            policy.mEffectiveKeepProfilesRunning = true;
+        }
         DevicePolicyData.load(policy,
                 mProvider.makeDevicePoliciesJournaledFile(userId),
                 mProvider.getAdminInfoSupplier(userId),
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index d099693..40d7a77 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -28,6 +28,7 @@
 import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
 
 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;
@@ -53,6 +54,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.PropertyInvalidatedCache;
 import android.companion.virtual.IVirtualDevice;
 import android.companion.virtual.IVirtualDeviceManager;
@@ -627,7 +630,7 @@
      * Tests that the virtual display is created along-side the default display.
      */
     @Test
-    public void testStartVirtualDisplayWithDefaultDisplay_Succeeds() throws Exception {
+    public void testStartVirtualDisplayWithDefaultDisplay_Succeeds() {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
         registerDefaultDisplays(displayManager);
@@ -663,7 +666,7 @@
      * internal state for things like display cutout when nonOverrideDisplayInfo is changed.
      */
     @Test
-    public void testShouldNotifyChangeWhenNonOverrideDisplayInfoChanged() throws Exception {
+    public void testShouldNotifyChangeWhenNonOverrideDisplayInfoChanged() {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
         registerDefaultDisplays(displayManager);
@@ -1486,7 +1489,7 @@
      * a virtual device, even if ADD_TRUSTED_DISPLAY is not granted.
      */
     @Test
-    public void testOwnDisplayGroup_allowCreationWithVirtualDevice()  throws Exception {
+    public void testOwnDisplayGroup_allowCreationWithVirtualDevice() throws Exception {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerInternal localService = displayManager.new LocalService();
@@ -1650,7 +1653,7 @@
      */
     @Test
     @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public  void testDisplayInfoFrameRateOverrideModeCompat() throws Exception {
+    public  void testDisplayInfoFrameRateOverrideModeCompat() {
         testDisplayInfoFrameRateOverrideModeCompat(/*compatChangeEnabled*/ false);
     }
 
@@ -1659,7 +1662,7 @@
      */
     @Test
     @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public  void testDisplayInfoFrameRateOverrideMode() throws Exception {
+    public  void testDisplayInfoFrameRateOverrideMode() {
         testDisplayInfoFrameRateOverrideModeCompat(/*compatChangeEnabled*/ true);
     }
 
@@ -1742,7 +1745,7 @@
      */
     @Test
     @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public  void testDisplayInfoRenderFrameRateModeCompat() throws Exception {
+    public  void testDisplayInfoRenderFrameRateModeCompat() {
         testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ false);
     }
 
@@ -1751,7 +1754,7 @@
      */
     @Test
     @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public  void testDisplayInfoRenderFrameRateMode() throws Exception {
+    public  void testDisplayInfoRenderFrameRateMode() {
         testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ true);
     }
 
@@ -2104,10 +2107,11 @@
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
         bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+        callback.expectsEvent(EVENT_DISPLAY_ADDED);
 
         FakeDisplayDevice displayDevice =
                 createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
 
         LogicalDisplay display =
                 logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
@@ -2124,16 +2128,19 @@
         DisplayManagerInternal localService = displayManager.new LocalService();
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
-        // Create default display device
-        createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
-
         bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
         localService.registerDisplayGroupListener(callback);
+        callback.expectsEvent(EVENT_DISPLAY_ADDED);
+        // Create default display device
+        createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+        callback.waitForExpectedEvent();
+        callback.clear();
 
+        callback.expectsEvent(EVENT_DISPLAY_CONNECTED);
         FakeDisplayDevice displayDevice =
                 createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
 
         LogicalDisplay display =
                 logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
@@ -2151,8 +2158,9 @@
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
         bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
 
+        callback.expectsEvent(EVENT_DISPLAY_ADDED);
         createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
 
         assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_CONNECTED,
                 EVENT_DISPLAY_ADDED).inOrder();
@@ -2166,18 +2174,22 @@
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+        callback.expectsEvent(EVENT_DISPLAY_ADDED);
         // Create default display device
         createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
-        bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+        callback.waitForExpectedEvent();
+        callback.expectsEvent(EVENT_DISPLAY_CONNECTED);
         FakeDisplayDevice displayDevice =
                 createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
         callback.clear();
 
+        callback.expectsEvent(EVENT_DISPLAY_ADDED);
         LogicalDisplay display =
                 logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
         displayManager.enableConnectedDisplay(display.getDisplayIdLocked(), /* enabled= */ true);
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
 
         assertThat(display.isEnabledLocked()).isTrue();
         assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_ADDED).inOrder();
@@ -2190,11 +2202,15 @@
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+        callback.expectsEvent(EVENT_DISPLAY_ADDED);
         // Create default display device
         createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+        callback.waitForExpectedEvent();
         bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
         FakeDisplayDevice displayDevice =
                 createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+        // Withouts permission, we cannot get the CONNECTED event.
         waitForIdleHandler(displayManager.getDisplayHandler());
         callback.clear();
         LogicalDisplay display =
@@ -2212,19 +2228,22 @@
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
         bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+        callback.expectsEvent(EVENT_DISPLAY_ADDED);
         FakeDisplayDevice displayDevice =
                 createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
         LogicalDisplay display =
                 logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+        callback.expectsEvent(EVENT_DISPLAY_REMOVED);
         logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ false);
         logicalDisplayMapper.updateLogicalDisplays();
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
         callback.clear();
 
+        callback.expectsEvent(EVENT_DISPLAY_ADDED);
         logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ true);
         logicalDisplayMapper.updateLogicalDisplays();
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
 
         assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_ADDED);
     }
@@ -2237,16 +2256,18 @@
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
         bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+        callback.expectsEvent(EVENT_DISPLAY_ADDED);
         FakeDisplayDevice displayDevice =
                 createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
         callback.clear();
         LogicalDisplay display =
                 logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
 
+        callback.expectsEvent(EVENT_DISPLAY_REMOVED);
         logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ false);
         logicalDisplayMapper.updateLogicalDisplays();
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
 
         assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_REMOVED);
     }
@@ -2259,23 +2280,26 @@
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
         DisplayManagerInternal localService = displayManager.new LocalService();
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
-        // Create default display device
-        createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
         bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
         localService.registerDisplayGroupListener(callback);
+        callback.expectsEvent(EVENT_DISPLAY_ADDED);
+        // Create default display device
+        createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+        callback.waitForExpectedEvent();
         FakeDisplayDevice displayDevice =
                 createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
-        waitForIdleHandler(displayManager.getDisplayHandler());
         LogicalDisplay display =
                 logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+        callback.expectsEvent(EVENT_DISPLAY_ADDED);
         logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ true);
         logicalDisplayMapper.updateLogicalDisplays();
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
         callback.clear();
 
+        callback.expectsEvent(EVENT_DISPLAY_REMOVED);
         logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ false);
         logicalDisplayMapper.updateLogicalDisplays();
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
 
         assertThat(display.isEnabledLocked()).isFalse();
         assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_REMOVED);
@@ -2288,18 +2312,20 @@
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+        callback.expectsEvent(EVENT_DISPLAY_ADDED);
         // Create default display device
         createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
-        bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+        callback.waitForExpectedEvent();
         FakeDisplayDevice displayDevice =
                 createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.expectsEvent(EVENT_DISPLAY_ADDED);
         LogicalDisplay display =
                 logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
         int displayId = display.getDisplayIdLocked();
         logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ true);
         logicalDisplayMapper.updateLogicalDisplays();
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
         callback.clear();
 
         assertThrows(SecurityException.class, () -> bs.disableConnectedDisplay(displayId));
@@ -2314,23 +2340,27 @@
         DisplayManagerInternal localService = displayManager.new LocalService();
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
-        // Create default display device
-        createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
         bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
         localService.registerDisplayGroupListener(callback);
+        callback.expectsEvent(EVENT_DISPLAY_ADDED);
+        // Create default display device'
+        createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+        callback.waitForExpectedEvent();
+        callback.expectsEvent(EVENT_DISPLAY_CONNECTED);
         FakeDisplayDevice displayDevice =
                 createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
         callback.clear();
         LogicalDisplay display = logicalDisplayMapper.getDisplayLocked(displayDevice);
         int groupId = display.getDisplayInfoLocked().displayGroupId;
         DisplayGroup group = logicalDisplayMapper.getDisplayGroupLocked(groupId);
         assertThat(group.getSizeLocked()).isEqualTo(1);
 
+        callback.expectsEvent(DISPLAY_GROUP_EVENT_REMOVED);
         display.setPrimaryDisplayDeviceLocked(null);
         displayManager.getDisplayDeviceRepository()
                 .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
 
         assertThat(group.getSizeLocked()).isEqualTo(0);
         assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_DISCONNECTED,
@@ -2357,20 +2387,23 @@
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
         bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+        callback.expectsEvent(EVENT_DISPLAY_CONNECTED);
         FakeDisplayDevice displayDevice =
                 createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
+        callback.expectsEvent(EVENT_DISPLAY_ADDED);
         LogicalDisplay display =
                 logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
         int displayId = display.getDisplayIdLocked();
         displayManager.enableConnectedDisplay(displayId, /* enabled= */ true);
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
         callback.clear();
 
+        callback.expectsEvent(EVENT_DISPLAY_DISCONNECTED);
         display.setPrimaryDisplayDeviceLocked(null);
         displayManager.getDisplayDeviceRepository()
                 .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
 
         assertThat(logicalDisplayMapper.getDisplayLocked(displayId, true)).isNull();
         assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_REMOVED,
@@ -2386,17 +2419,19 @@
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
         bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+        callback.expectsEvent(EVENT_DISPLAY_ADDED);
         FakeDisplayDevice displayDevice =
                 createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
         LogicalDisplay display =
                 logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
         callback.clear();
 
+        callback.expectsEvent(EVENT_DISPLAY_DISCONNECTED);
         display.setPrimaryDisplayDeviceLocked(null);
         displayManager.getDisplayDeviceRepository()
                 .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
-        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.waitForExpectedEvent();
 
         assertThat(logicalDisplayMapper.getDisplayLocked(displayDevice,
                 /* includeDisabled= */ true)).isNull();
@@ -2675,6 +2710,12 @@
         int mDisplayId;
         List<String> mReceivedEvents = new ArrayList<>();
 
+        @Nullable
+        private String mExpectedEvent;
+
+        @NonNull
+        private volatile CountDownLatch mLatch = new CountDownLatch(0);
+
         FakeDisplayManagerCallback(int displayId) {
             mDisplayId = displayId;
         }
@@ -2683,6 +2724,30 @@
             mDisplayId = -1;
         }
 
+        void expectsEvent(@NonNull String event) {
+            mExpectedEvent = event;
+            mLatch = new CountDownLatch(1);
+        }
+
+        void waitForExpectedEvent() {
+            waitForExpectedEvent(Duration.ofSeconds(1));
+        }
+
+        void waitForExpectedEvent(Duration timeout) {
+            try {
+                assertWithMessage("Event '" + mExpectedEvent + "' is received.")
+                        .that(mLatch.await(timeout.toMillis(), TimeUnit.MILLISECONDS)).isTrue();
+            } catch (InterruptedException ex) {
+                throw new AssertionError("Waiting for expected event interrupted", ex);
+            }
+        }
+
+        private void eventSeen(String event) {
+            if (event.equals(mExpectedEvent)) {
+                mLatch.countDown();
+            }
+        }
+
         @Override
         public void onDisplayEvent(int displayId, int event) {
             if (mDisplayId != -1 && displayId != mDisplayId) {
@@ -2693,22 +2758,27 @@
             // 1 - The error produced is a lot easier to read
             // 2 - The values used for display and group events are the same, strings are used to
             // differentiate them easily.
-            mReceivedEvents.add(eventTypeToString(event));
+            String eventName = eventTypeToString(event);
+            mReceivedEvents.add(eventName);
+            eventSeen(eventName);
         }
 
         @Override
         public void onDisplayGroupAdded(int groupId) {
             mReceivedEvents.add(DISPLAY_GROUP_EVENT_ADDED);
+            eventSeen(DISPLAY_GROUP_EVENT_ADDED);
         }
 
         @Override
         public void onDisplayGroupRemoved(int groupId) {
             mReceivedEvents.add(DISPLAY_GROUP_EVENT_REMOVED);
+            eventSeen(DISPLAY_GROUP_EVENT_REMOVED);
         }
 
         @Override
         public void onDisplayGroupChanged(int groupId) {
             mReceivedEvents.add(DISPLAY_GROUP_EVENT_CHANGED);
+            eventSeen(DISPLAY_GROUP_EVENT_CHANGED);
         }
 
         public void clear() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 0195ff7..e7dc48e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -1160,6 +1160,21 @@
                 eq(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE_IDLE));
     }
 
+    @Test
+    public void testPowerStateStopsOnDpcStop() {
+        // Set up
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1);
+
+        // Stop dpc
+        mHolder.dpc.stop();
+        advanceTime(1);
+
+        // Ensure dps has stopped
+        verify(mHolder.displayPowerState, times(1)).stop();
+    }
+
     /**
      * Creates a mock and registers it to {@link LocalServices}.
      */
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index f499ac5..2640390 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1167,6 +1167,21 @@
                 eq(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE_IDLE));
     }
 
+    @Test
+    public void testPowerStateStopsOnDpcStop() {
+        // Set up
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1);
+
+        // Stop dpc
+        mHolder.dpc.stop();
+        advanceTime(1);
+
+        // Ensure dps has stopped
+        verify(mHolder.displayPowerState, times(1)).stop();
+    }
+
     private void advanceTime(long timeMs) {
         mClock.fastForward(timeMs);
         mTestLooper.dispatchAll();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.java
new file mode 100644
index 0000000..167a412
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.Mockito.times;
+
+import android.os.Handler;
+import android.os.test.TestLooper;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+
+@SmallTest
+public class DisplayPowerStateTest {
+    private static final int DISPLAY_ID = 123;
+
+    private DisplayPowerState mDisplayPowerState;
+    private TestLooper mTestLooper;
+    @Mock
+    private DisplayBlanker mDisplayBlankerMock;
+    @Mock
+    private ColorFade mColorFadeMock;
+
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Before
+    public void setUp() {
+        mTestLooper = new TestLooper();
+        mDisplayPowerState = new DisplayPowerState(
+                mDisplayBlankerMock, mColorFadeMock, DISPLAY_ID, Display.STATE_ON,
+                new Handler(mTestLooper.getLooper()));
+    }
+
+    @Test
+    public void testColorFadeStopsOnDpsStop() {
+        mDisplayPowerState.stop();
+        verify(mColorFadeMock, times(1)).stop();
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
index c7e1bda..e58a234 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
@@ -16,41 +16,51 @@
 
 package com.android.server.pm;
 
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.AppOpsManager;
 import android.content.Context;
+import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
+import android.content.pm.PackageArchiver;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.os.Binder;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.ParcelableException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.text.TextUtils;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.pm.pkg.ArchiveState;
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateImpl;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -64,7 +74,8 @@
 public class PackageArchiverServiceTest {
 
     private static final String PACKAGE = "com.example";
-    private static final String CALLER_PACKAGE = "com.vending";
+    private static final String CALLER_PACKAGE = "com.caller";
+    private static final String INSTALLER_PACKAGE = "com.installer";
 
     @Rule
     public final MockSystemRule mMockSystem = new MockSystemRule();
@@ -84,11 +95,11 @@
 
     private final InstallSource mInstallSource =
             InstallSource.create(
-                    CALLER_PACKAGE,
-                    CALLER_PACKAGE,
-                    CALLER_PACKAGE,
+                    INSTALLER_PACKAGE,
+                    INSTALLER_PACKAGE,
+                    INSTALLER_PACKAGE,
                     Binder.getCallingUid(),
-                    CALLER_PACKAGE,
+                    INSTALLER_PACKAGE,
                     /* installerAttributionTag= */ null,
                     /* packageSource= */ 0);
 
@@ -96,6 +107,8 @@
 
     private final int mUserId = UserHandle.CURRENT.getIdentifier();
 
+    private PackageUserStateImpl mUserState;
+
     private PackageSetting mPackageSetting;
 
     private PackageArchiverService mArchiveService;
@@ -116,11 +129,16 @@
 
         when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
                 mPackageState);
+        when(mComputer.getPackageStateFiltered(eq(INSTALLER_PACKAGE), anyInt(),
+                anyInt())).thenReturn(mock(PackageStateInternal.class));
         when(mPackageState.getPackageName()).thenReturn(PACKAGE);
         when(mPackageState.getInstallSource()).thenReturn(mInstallSource);
         mPackageSetting = createBasicPackageSetting();
         when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn(
                 mPackageSetting);
+        mUserState = new PackageUserStateImpl().setInstalled(true);
+        mPackageSetting.setUserState(mUserId, mUserState);
+        when(mPackageState.getUserStateOrDefault(eq(mUserId))).thenReturn(mUserState);
         when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
         when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
                 mLauncherActivityInfos);
@@ -135,9 +153,7 @@
         Exception e = assertThrows(
                 SecurityException.class,
                 () -> mArchiveService.requestArchive(PACKAGE, "different", mIntentSender,
-                        UserHandle.CURRENT
-                )
-        );
+                        UserHandle.CURRENT));
         assertThat(e).hasMessageThat().isEqualTo(
                 String.format(
                         "The UID %s of callerPackageName set by the caller doesn't match the "
@@ -154,9 +170,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT
-                )
-        );
+                        UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("Package %s not found.", PACKAGE));
@@ -169,9 +183,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT
-                )
-        );
+                        UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("Package %s not found.", PACKAGE));
@@ -193,25 +205,28 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT
-                )
-        );
+                        UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("No installer found to archive app %s.", PACKAGE));
     }
 
     @Test
-    public void archiveApp_success() {
-        List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
-        for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
-            // TODO(b/278553670) Extract and store launcher icons
-            ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
-                    mainActivity.getLabel().toString(),
-                    Path.of("/TODO"), null);
-            activityInfos.add(activityInfo);
-        }
+    public void archiveApp_noMainActivities() {
+        when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
+                List.of());
 
+        Exception e = assertThrows(
+                ParcelableException.class,
+                () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+                        UserHandle.CURRENT));
+        assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+        assertThat(e.getCause()).hasMessageThat().isEqualTo(
+                TextUtils.formatSimple("The app %s does not have a main activity.", PACKAGE));
+    }
+
+    @Test
+    public void archiveApp_success() {
         mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
 
         verify(mInstallerService).uninstall(
@@ -220,7 +235,112 @@
                 eq(UserHandle.CURRENT.getIdentifier()));
         assertThat(mPackageSetting.readUserState(
                 UserHandle.CURRENT.getIdentifier()).getArchiveState()).isEqualTo(
-                new ArchiveState(activityInfos, CALLER_PACKAGE));
+                createArchiveState());
+    }
+
+    @Test
+    public void unarchiveApp_callerPackageNameIncorrect() {
+        mUserState.setArchiveState(createArchiveState()).setInstalled(false);
+
+        Exception e = assertThrows(
+                SecurityException.class,
+                () -> mArchiveService.requestUnarchive(PACKAGE, "different",
+                        UserHandle.CURRENT));
+        assertThat(e).hasMessageThat().isEqualTo(
+                String.format(
+                        "The UID %s of callerPackageName set by the caller doesn't match the "
+                                + "caller's actual UID %s.",
+                        0,
+                        Binder.getCallingUid()));
+    }
+
+    @Test
+    public void unarchiveApp_packageNotInstalled() {
+        mUserState.setArchiveState(createArchiveState()).setInstalled(false);
+        when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
+                null);
+
+        Exception e = assertThrows(
+                ParcelableException.class,
+                () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE,
+                        UserHandle.CURRENT));
+        assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+        assertThat(e.getCause()).hasMessageThat().isEqualTo(
+                String.format("Package %s not found.", PACKAGE));
+    }
+
+    @Test
+    public void unarchiveApp_notArchived() {
+        Exception e = assertThrows(
+                ParcelableException.class,
+                () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE,
+                        UserHandle.CURRENT));
+        assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+        assertThat(e.getCause()).hasMessageThat().isEqualTo(
+                String.format("Package %s is not currently archived.", PACKAGE));
+    }
+
+    @Test
+    public void unarchiveApp_noInstallerFound() {
+        mUserState.setArchiveState(createArchiveState());
+        InstallSource otherInstallSource =
+                InstallSource.create(
+                        CALLER_PACKAGE,
+                        CALLER_PACKAGE,
+                        /* installerPackageName= */ null,
+                        Binder.getCallingUid(),
+                        /* updateOwnerPackageName= */ null,
+                        /* installerAttributionTag= */ null,
+                        /* packageSource= */ 0);
+        when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
+
+        Exception e = assertThrows(
+                ParcelableException.class,
+                () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE,
+                        UserHandle.CURRENT));
+        assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+        assertThat(e.getCause()).hasMessageThat().isEqualTo(
+                String.format("No installer found to unarchive app %s.", PACKAGE));
+    }
+
+    @Test
+    public void unarchiveApp_success() {
+        mUserState.setArchiveState(createArchiveState()).setInstalled(false);
+
+        mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT);
+        mMockSystem.mocks().getHandler().flush();
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendOrderedBroadcastAsUser(
+                intentCaptor.capture(),
+                eq(UserHandle.CURRENT),
+                /* receiverPermission = */ isNull(),
+                eq(AppOpsManager.OP_NONE),
+                any(Bundle.class),
+                /* resultReceiver= */ isNull(),
+                /* scheduler= */ isNull(),
+                /* initialCode= */ eq(0),
+                /* initialData= */ isNull(),
+                /* initialExtras= */ isNull());
+        Intent intent = intentCaptor.getValue();
+        assertThat(intent.getFlags() & FLAG_RECEIVER_FOREGROUND).isNotEqualTo(0);
+        assertThat(intent.getStringExtra(PackageArchiver.EXTRA_UNARCHIVE_PACKAGE_NAME)).isEqualTo(
+                PACKAGE);
+        assertThat(
+                intent.getBooleanExtra(PackageArchiver.EXTRA_UNARCHIVE_ALL_USERS, true)).isFalse();
+        assertThat(intent.getPackage()).isEqualTo(INSTALLER_PACKAGE);
+    }
+
+    private static ArchiveState createArchiveState() {
+        List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
+        for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
+            // TODO(b/278553670) Extract and store launcher icons
+            ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
+                    mainActivity.getLabel().toString(),
+                    Path.of("/TODO"), null);
+            activityInfos.add(activityInfo);
+        }
+        return new ArchiveState(activityInfos, INSTALLER_PACKAGE);
     }
 
     private static List<LauncherActivityInfo> createLauncherActivities() {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 534aa89..93cbea6 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -243,8 +243,10 @@
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
         final boolean includeProcessStateData = (query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0;
+        final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
         BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
-                customPowerComponentNames, includePowerModels, includeProcessStateData);
+                customPowerComponentNames, includePowerModels, includeProcessStateData,
+                minConsumedPowerThreshold);
         SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats();
         for (int i = 0; i < uidStats.size(); i++) {
             builder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i));
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index 266a226..07c486c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -181,7 +181,7 @@
         final BatteryUsageStats stats2 = buildBatteryUsageStats2(new String[]{"FOO"}, true).build();
 
         final BatteryUsageStats sum =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true)
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0)
                         .add(stats1)
                         .add(stats2)
                         .build();
@@ -222,7 +222,7 @@
     @Test
     public void testAdd_customComponentMismatch() {
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true);
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0);
         final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"BAR"}, false).build();
 
         assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
@@ -231,7 +231,7 @@
     @Test
     public void testAdd_processStateDataMismatch() {
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true);
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0);
         final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"FOO"}, false).build();
 
         assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
@@ -260,7 +260,7 @@
         final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks);
 
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true)
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0)
                         .setBatteryCapacity(4000)
                         .setDischargePercentage(20)
                         .setDischargedPowerRange(1000, 2000)
@@ -305,7 +305,7 @@
 
         final BatteryUsageStats.Builder builder =
                 new BatteryUsageStats.Builder(customPowerComponentNames, true,
-                        includeProcessStateData);
+                        includeProcessStateData, 0);
         builder.setDischargePercentage(30)
                 .setDischargedPowerRange(1234, 2345)
                 .setStatsStartTimestamp(2000)
diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_false.xml b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_false.xml
new file mode 100644
index 0000000..4785a88
--- /dev/null
+++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_false.xml
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+    <keep-profiles-running value="false" />
+    <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+        <policies flags="991" />
+        <strong-auth-unlock-timeout value="0" />
+        <organization-color value="-16738680" />
+        <active-password value="0" />
+    </admin>
+</policies>
diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_true.xml b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_true.xml
new file mode 100644
index 0000000..07ec229
--- /dev/null
+++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_true.xml
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+    <keep-profiles-running value="true" />
+    <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+        <policies flags="991" />
+        <strong-auth-unlock-timeout value="0" />
+        <organization-color value="-16738680" />
+        <active-password value="0" />
+    </admin>
+</policies>
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
index eb2ee35..d2b921d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
@@ -76,7 +76,7 @@
 public class PolicyVersionUpgraderTest extends DpmTestBase {
     // NOTE: Only change this value if the corresponding CL also adds a test to test the upgrade
     // to the new version.
-    private static final int LATEST_TESTED_VERSION = 5;
+    private static final int LATEST_TESTED_VERSION = 6;
     public static final String PERMISSIONS_TAG = "admin-can-grant-sensors-permissions";
     public static final String DEVICE_OWNER_XML = "device_owner_2.xml";
     private ComponentName mFakeAdmin;
@@ -313,7 +313,7 @@
     }
 
     @Test
-    public void testEffectiveKeepProfilesRunningSet() throws Exception {
+    public void testEffectiveKeepProfilesRunningSetToFalse4To5() throws Exception {
         writeVersionToXml(4);
 
         final int userId = UserHandle.USER_SYSTEM;
@@ -327,8 +327,109 @@
         Document policies = readPolicies(userId);
         Element keepProfilesRunning = (Element) policies.getDocumentElement()
                 .getElementsByTagName("keep-profiles-running").item(0);
-        assertThat(keepProfilesRunning.getAttribute("value")).isEqualTo("false");
+
+        // Default value (false) is not serialized.
+        assertThat(keepProfilesRunning).isNull();
     }
+    @Test
+    public void testEffectiveKeepProfilesRunningIsToFalse4To6() throws Exception {
+        writeVersionToXml(4);
+
+        final int userId = UserHandle.USER_SYSTEM;
+        mProvider.mUsers = new int[]{userId};
+        preparePoliciesFile(userId, "device_policies.xml");
+
+        mUpgrader.upgradePolicy(6);
+
+        assertThat(readVersionFromXml()).isAtLeast(6);
+
+        Document policies = readPolicies(userId);
+        Element keepProfilesRunning = (Element) policies.getDocumentElement()
+                .getElementsByTagName("keep-profiles-running").item(0);
+
+        // Default value (false) is not serialized.
+        assertThat(keepProfilesRunning).isNull();
+    }
+
+    /**
+     * Verify correct behaviour when upgrading from Android 13
+     */
+    @Test
+    public void testEffectiveKeepProfilesRunningIsToFalse3To6() throws Exception {
+        writeVersionToXml(3);
+
+        final int userId = UserHandle.USER_SYSTEM;
+        mProvider.mUsers = new int[]{userId};
+        preparePoliciesFile(userId, "device_policies.xml");
+
+        mUpgrader.upgradePolicy(6);
+
+        assertThat(readVersionFromXml()).isAtLeast(6);
+
+        Document policies = readPolicies(userId);
+        Element keepProfilesRunning = (Element) policies.getDocumentElement()
+                .getElementsByTagName("keep-profiles-running").item(0);
+
+        // Default value (false) is not serialized.
+        assertThat(keepProfilesRunning).isNull();
+    }
+
+    @Test
+    public void testEffectiveKeepProfilesRunningMissingInV5() throws Exception {
+        writeVersionToXml(5);
+
+        final int userId = UserHandle.USER_SYSTEM;
+        mProvider.mUsers = new int[]{userId};
+        preparePoliciesFile(userId, "device_policies.xml");
+
+        mUpgrader.upgradePolicy(6);
+
+        assertThat(readVersionFromXml()).isAtLeast(6);
+
+        Document policies = readPolicies(userId);
+        Element keepProfilesRunning = (Element) policies.getDocumentElement()
+                .getElementsByTagName("keep-profiles-running").item(0);
+        assertThat(keepProfilesRunning.getAttribute("value")).isEqualTo("true");
+    }
+
+    @Test
+    public void testEffectiveKeepProfilesRunningTrueInV5() throws Exception {
+        writeVersionToXml(5);
+
+        final int userId = UserHandle.USER_SYSTEM;
+        mProvider.mUsers = new int[]{userId};
+        preparePoliciesFile(userId, "device_policies_keep_profiles_running_true.xml");
+
+        mUpgrader.upgradePolicy(6);
+
+        assertThat(readVersionFromXml()).isAtLeast(6);
+
+        Document policies = readPolicies(userId);
+        Element keepProfilesRunning = (Element) policies.getDocumentElement()
+                .getElementsByTagName("keep-profiles-running").item(0);
+        assertThat(keepProfilesRunning.getAttribute("value")).isEqualTo("true");
+    }
+
+    @Test
+    public void testEffectiveKeepProfilesRunningFalseInV5() throws Exception {
+        writeVersionToXml(5);
+
+        final int userId = UserHandle.USER_SYSTEM;
+        mProvider.mUsers = new int[]{userId};
+        preparePoliciesFile(userId, "device_policies_keep_profiles_running_false.xml");
+
+        mUpgrader.upgradePolicy(6);
+
+        assertThat(readVersionFromXml()).isAtLeast(6);
+
+        Document policies = readPolicies(userId);
+        Element keepProfilesRunning = (Element) policies.getDocumentElement()
+                .getElementsByTagName("keep-profiles-running").item(0);
+
+        // Default value (false) is not serialized.
+        assertThat(keepProfilesRunning).isNull();
+    }
+
 
     @Test
     public void isLatestVersionTested() {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
index 10b49c6..bc826a3 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
@@ -30,7 +30,6 @@
 
 import android.content.res.Resources;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
 import android.os.VibratorInfo;
 import android.util.AtomicFile;
 import android.util.SparseArray;
@@ -73,12 +72,10 @@
             VibrationEffect.createWaveform(new long[] {123}, new int[] {254}, -1);
 
     @Mock private Resources mResourcesMock;
-    @Mock private Vibrator mVibratorMock;
     @Mock private VibratorInfo mVibratorInfoMock;
 
     @Before
     public void setUp() {
-        when(mVibratorMock.getInfo()).thenReturn(mVibratorInfoMock);
         when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true);
     }
 
@@ -220,17 +217,17 @@
     public void testParseCustomizations_noCustomizationFile_returnsNull() throws Exception {
         setCustomizationFilePath("");
 
-        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock))
+        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
                 .isNull();
 
         setCustomizationFilePath(null);
 
-        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock))
+        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
                 .isNull();
 
         setCustomizationFilePath("non_existent_file.xml");
 
-        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock))
+        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
                 .isNull();
     }
 
@@ -387,7 +384,7 @@
             String xml, SparseArray<VibrationEffect> expectedCustomizations) throws Exception {
         setupCustomizationFile(xml);
         assertThat(expectedCustomizations.contentEquals(
-                HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock)))
+                HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock)))
                         .isTrue();
     }
 
@@ -395,13 +392,15 @@
         setupCustomizationFile(xml);
         assertThrows("Expected haptic feedback customization to fail for " + xml,
                 CustomizationParserException.class,
-                () ->  HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock));
+                () ->  HapticFeedbackCustomization.loadVibrations(
+                        mResourcesMock, mVibratorInfoMock));
     }
 
     private void assertParseCustomizationsFails() throws Exception {
         assertThrows("Expected haptic feedback customization to fail",
                 CustomizationParserException.class,
-                () ->  HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock));
+                () ->  HapticFeedbackCustomization.loadVibrations(
+                        mResourcesMock, mVibratorInfoMock));
     }
 
     private void setupCustomizationFile(String xml) throws Exception {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index cae811e..a91bd2b 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -31,8 +31,10 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.hardware.vibrator.IVibrator;
+import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
-import android.os.test.FakeVibrator;
+import android.os.VibratorInfo;
 import android.util.AtomicFile;
 import android.util.SparseArray;
 
@@ -58,7 +60,7 @@
             VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK, 0.3497f).compose();
 
     private Context mContext = InstrumentationRegistry.getContext();
-    private FakeVibrator mVibrator = new FakeVibrator(mContext);
+    private VibratorInfo mVibratorInfo = VibratorInfo.EMPTY_VIBRATOR_INFO;
 
     @Mock private Resources mResourcesMock;
 
@@ -66,14 +68,14 @@
     public void testNonExistentCustomization_useDefault() throws Exception {
         // No customization file is set.
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator);
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
                 .isEqualTo(VibrationEffect.get(EFFECT_TICK));
 
         // The customization file specifies no customization.
         setupCustomizationFile("<haptic-feedback-constants></haptic-feedback-constants>");
-        hapticProvider = new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator);
+        hapticProvider = new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
                 .isEqualTo(VibrationEffect.get(EFFECT_TICK));
@@ -83,7 +85,7 @@
     public void testExceptionParsingCustomizations_useDefault() throws Exception {
         setupCustomizationFile("<bad-xml></bad-xml>");
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator);
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
                 .isEqualTo(VibrationEffect.get(EFFECT_TICK));
@@ -96,7 +98,7 @@
         customizations.put(CONTEXT_CLICK, PRIMITIVE_CLICK_EFFECT);
 
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
 
         // The override for `CONTEXT_CLICK` is used.
         assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
@@ -109,11 +111,15 @@
     @Test
     public void testDoNotUseInvalidCustomizedVibration() throws Exception {
         mockVibratorPrimitiveSupport(new int[] {});
-        SparseArray<VibrationEffect> customizations = new SparseArray<>();
-        customizations.put(CONTEXT_CLICK, PRIMITIVE_CLICK_EFFECT);
+        String xml = "<haptic-feedback-constants>"
+                + "<constant id=\"" + CONTEXT_CLICK + "\">"
+                + PRIMITIVE_CLICK_EFFECT
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        setupCustomizationFile(xml);
 
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
 
         // The override for `CONTEXT_CLICK` is not used because the vibration is not supported.
         assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
@@ -132,14 +138,14 @@
 
         // Test with a customization available for `TEXT_HANDLE_MOVE`.
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
 
         // Test with no customization available for `TEXT_HANDLE_MOVE`.
         hapticProvider =
                 new HapticFeedbackVibrationProvider(
-                        mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
+                        mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
     }
@@ -153,7 +159,7 @@
 
         // Test with a customization available for `TEXT_HANDLE_MOVE`.
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
                 .isEqualTo(PRIMITIVE_CLICK_EFFECT);
@@ -161,7 +167,7 @@
         // Test with no customization available for `TEXT_HANDLE_MOVE`.
         hapticProvider =
                 new HapticFeedbackVibrationProvider(
-                        mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
+                        mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
                 .isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
@@ -176,14 +182,14 @@
         customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
 
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
                 .isEqualTo(PRIMITIVE_CLICK_EFFECT);
 
         mockSafeModeEnabledVibration(null);
         hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
                 .isEqualTo(PRIMITIVE_CLICK_EFFECT);
@@ -193,20 +199,9 @@
     public void testNoValidCustomizationPresentForSafeModeEnabled_resourceBasedVibrationUsed()
                 throws Exception {
         mockSafeModeEnabledVibration(10, 20, 30, 40);
-        SparseArray<VibrationEffect> customizations = new SparseArray<>();
-        customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
-
-        // Test with a customization that is not supported by the vibrator.
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
-
-        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
-                .isEqualTo(VibrationEffect.createWaveform(new long[] {10, 20, 30, 40}, -1));
-
-        // Test with no customizations.
-        hapticProvider =
                 new HapticFeedbackVibrationProvider(
-                        mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
+                        mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
                 .isEqualTo(VibrationEffect.createWaveform(new long[] {10, 20, 30, 40}, -1));
@@ -216,25 +211,44 @@
     public void testNoValidCustomizationAndResourcePresentForSafeModeEnabled_noVibrationUsed()
                 throws Exception {
         mockSafeModeEnabledVibration(null);
-        SparseArray<VibrationEffect> customizations = new SparseArray<>();
-        customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
-
-        // Test with a customization that is not supported by the vibrator.
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
-
-        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)).isNull();
-
-        // Test with no customizations.
-        hapticProvider =
                 new HapticFeedbackVibrationProvider(
-                        mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
+                        mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)).isNull();
     }
 
+    @Test
+    public void testVibrationAttribute_forNotBypassingIntensitySettings() {
+        HapticFeedbackVibrationProvider hapticProvider =
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
+
+        VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+                SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ false);
+
+        assertThat(attrs.getFlags() & VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
+                .isEqualTo(0);
+    }
+
+    @Test
+    public void testVibrationAttribute_forByassingIntensitySettings() {
+        HapticFeedbackVibrationProvider hapticProvider =
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
+
+        VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+                SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ true);
+
+        assertThat(attrs.getFlags() & VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
+                .isNotEqualTo(0);
+    }
+
     private void mockVibratorPrimitiveSupport(int... supportedPrimitives) {
-        mVibrator = new FakeVibrator(mContext, supportedPrimitives);
+        VibratorInfo.Builder builder = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        for (int primitive : supportedPrimitives) {
+            builder.setSupportedPrimitive(primitive, 10);
+        }
+        mVibratorInfo = builder.build();
     }
 
     private void mockHapticTextSupport(boolean supported) {
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 4e3a893..c25f0cb 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -30,6 +31,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -45,6 +47,7 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.pm.PackageManagerInternal;
+import android.content.res.Resources;
 import android.hardware.input.IInputManager;
 import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerGlobal;
@@ -79,8 +82,10 @@
 import android.os.vibrator.VibrationEffectSegment;
 import android.provider.Settings;
 import android.util.ArraySet;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.view.Display;
+import android.view.HapticFeedbackConstants;
 import android.view.InputDevice;
 
 import androidx.test.InstrumentationRegistry;
@@ -169,6 +174,8 @@
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
 
+    private SparseArray<VibrationEffect>  mHapticFeedbackVibrationMap = new SparseArray<>();
+
     private VibratorManagerService mService;
     private Context mContextSpy;
     private TestLooper mTestLooper;
@@ -309,6 +316,12 @@
                         mExternalVibratorService =
                                 (VibratorManagerService.ExternalVibratorService) serviceInstance;
                     }
+
+                    HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
+                            Resources resources, VibratorInfo vibratorInfo) {
+                        return new HapticFeedbackVibrationProvider(
+                                resources, vibratorInfo, mHapticFeedbackVibrationMap);
+                    }
                 });
         return mService;
     }
@@ -623,6 +636,18 @@
     }
 
     @Test
+    public void vibrate_withoutVibratePermission_throwsSecurityException() {
+        denyPermission(android.Manifest.permission.VIBRATE);
+        VibratorManagerService service = createSystemReadyService();
+
+        assertThrows("Expected vibrating without permission to fail!",
+                SecurityException.class,
+                () -> vibrate(service,
+                        VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
+                        VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH)));
+    }
+
+    @Test
     public void vibrate_withRingtone_usesRingerModeSettings() throws Exception {
         mockVibrators(1);
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
@@ -1274,6 +1299,60 @@
     }
 
     @Test
+    public void performHapticFeedback_doesNotRequirePermission() throws Exception {
+        denyPermission(android.Manifest.permission.VIBRATE);
+        mHapticFeedbackVibrationMap.put(
+                HapticFeedbackConstants.KEYBOARD_TAP,
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+
+        HalVibration vibration =
+                performHapticFeedbackAndWaitUntilFinished(
+                        service, HapticFeedbackConstants.KEYBOARD_TAP, /* always= */ true);
+
+        List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments();
+        assertEquals(1, playedSegments.size());
+        PrebakedSegment segment = (PrebakedSegment) playedSegments.get(0);
+        assertEquals(VibrationEffect.EFFECT_CLICK, segment.getEffectId());
+        assertEquals(VibrationAttributes.USAGE_TOUCH, vibration.callerInfo.attrs.getUsage());
+    }
+
+    @Test
+    public void performHapticFeedback_doesNotVibrateWhenVibratorInfoNotReady() throws Exception {
+        denyPermission(android.Manifest.permission.VIBRATE);
+        mHapticFeedbackVibrationMap.put(
+                HapticFeedbackConstants.KEYBOARD_TAP,
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setVibratorInfoLoadSuccessful(false);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createService();
+
+        performHapticFeedbackAndWaitUntilFinished(
+                service, HapticFeedbackConstants.KEYBOARD_TAP, /* always= */ true);
+
+        assertTrue(fakeVibrator.getAllEffectSegments().isEmpty());
+    }
+
+    @Test
+    public void performHapticFeedback_doesNotVibrateForInvalidConstant() throws Exception {
+        denyPermission(android.Manifest.permission.VIBRATE);
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        // These are bad haptic feedback IDs, so expect no vibration played.
+        performHapticFeedbackAndWaitUntilFinished(service, /* constant= */ -1, /* always= */ false);
+        performHapticFeedbackAndWaitUntilFinished(
+                service, HapticFeedbackConstants.NO_HAPTICS, /* always= */ true);
+
+        assertTrue(mVibratorProviders.get(1).getAllEffectSegments().isEmpty());
+    }
+
+    @Test
     public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception {
         int defaultNotificationIntensity =
                 mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION);
@@ -2231,6 +2310,18 @@
                 mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
     }
 
+    private HalVibration performHapticFeedbackAndWaitUntilFinished(VibratorManagerService service,
+                int constant, boolean always) throws InterruptedException {
+        HalVibration vib =
+                service.performHapticFeedbackInternal(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME,
+                        constant, always, "some reason", service);
+        if (vib != null) {
+            vib.waitForEnd();
+        }
+
+        return vib;
+    }
+
     private void vibrateAndWaitUntilFinished(VibratorManagerService service, VibrationEffect effect,
             VibrationAttributes attrs) throws InterruptedException {
         vibrateAndWaitUntilFinished(service, CombinedVibration.createParallel(effect), attrs);
@@ -2239,8 +2330,8 @@
     private void vibrateAndWaitUntilFinished(VibratorManagerService service,
             CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException {
         HalVibration vib =
-                service.vibrateInternal(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME, effect, attrs,
-                        "some reason", service);
+                service.vibrateWithPermissionCheck(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME,
+                        effect, attrs, "some reason", service);
         if (vib != null) {
             vib.waitForEnd();
         }
@@ -2271,4 +2362,9 @@
         }
         return predicateResult;
     }
+
+    private void denyPermission(String permission) {
+        doThrow(new SecurityException()).when(mContextSpy)
+                .enforceCallingOrSelfPermission(eq(permission), anyString());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index 8fadecd..e13dc3e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -65,6 +65,12 @@
                         KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, ALT_ON},
                 {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK}, KeyboardLogEvent.BACK,
                         KeyEvent.KEYCODE_BACK, 0},
+                {"Meta + `(grave) -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_GRAVE},
+                        KeyboardLogEvent.BACK, KeyEvent.KEYCODE_GRAVE, META_ON},
+                {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
+                        KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DPAD_LEFT, META_ON},
+                {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
+                        KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DEL, META_ON},
                 {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
                         KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_APP_SWITCH, 0},
                 {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST},
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index bcb0c6b..0989db4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -59,6 +59,7 @@
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 import android.testing.DexmakerShareClassLoaderRule;
+import android.util.Pair;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
@@ -128,6 +129,8 @@
     private ActivityManagerInternal mAmInternal;
     @Mock
     private LockTaskController mLockTaskController;
+    @Mock
+    private TaskDisplayArea mTaskDisplayArea;
 
     private ActivityStartInterceptor mInterceptor;
     private ActivityInfo mAInfo = new ActivityInfo();
@@ -139,8 +142,8 @@
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
         mService.mAmInternal = mAmInternal;
-        mInterceptor = new ActivityStartInterceptor(
-                mService, mSupervisor, mRootWindowContainer, mContext);
+        mService.mRootWindowContainer = mRootWindowContainer;
+        mInterceptor = new ActivityStartInterceptor(mService, mSupervisor, mContext);
         mInterceptor.setStates(TEST_USER_ID, TEST_REAL_CALLING_PID, TEST_REAL_CALLING_UID,
                 TEST_START_FLAGS, TEST_CALLING_PACKAGE, null);
 
@@ -201,7 +204,7 @@
                 .thenReturn(PLATFORM_PACKAGE_NAME);
 
         // THEN calling intercept returns true
-        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
 
         // THEN the returned intent is the admin support intent
         assertEquals(ADMIN_SUPPORT_INTENT, mInterceptor.mIntent);
@@ -212,7 +215,7 @@
         final String suspendingPackage = "com.test.suspending.package";
         final SuspendDialogInfo dialogInfo = suspendPackage(suspendingPackage);
         // THEN calling intercept returns true
-        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
 
         // Check intent parameters
         assertEquals(dialogInfo,
@@ -243,7 +246,7 @@
                 TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
                 .thenReturn(false);
 
-        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
 
         assertTrue(BlockedAppActivity.createIntent(TEST_USER_ID, TEST_PACKAGE_NAME)
                 .filterEquals(mInterceptor.mIntent));
@@ -257,7 +260,8 @@
         when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(true);
 
         // THEN calling intercept returns false because package also has to be suspended.
-        assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null,  null, 0, 0, null));
+        assertFalse(
+                mInterceptor.intercept(null, null, mAInfo, null, null,  null, 0, 0, null, null));
     }
 
     @Test
@@ -268,7 +272,7 @@
         when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(false);
 
         // THEN calling intercept returns true
-        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null,  null, 0, 0, null));
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null,  null, 0, 0, null, null));
 
         // THEN the returned intent is the quiet mode intent
         assertTrue(UnlaunchableAppActivity.createInQuietModeDialogIntent(TEST_USER_ID)
@@ -284,7 +288,7 @@
         when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(true);
 
         // THEN calling intercept returns true
-        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
 
         // THEN the returned intent is the quiet mode intent
         assertTrue(UnlaunchableAppActivity.createInQuietModeDialogIntent(TEST_USER_ID)
@@ -300,7 +304,7 @@
         when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(false);
 
         // THEN calling intercept returns true
-        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
 
         // THEN the returned intent is the quiet mode intent
         assertTrue(UnlaunchableAppActivity.createInQuietModeDialogIntent(TEST_USER_ID)
@@ -313,7 +317,7 @@
         when(mAmInternal.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true);
 
         // THEN calling intercept returns true
-        mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null);
+        mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null);
 
         // THEN the returned intent is the confirm credentials intent
         assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent));
@@ -329,7 +333,7 @@
         mAInfo.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
 
         // THEN calling intercept returns true
-        mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0, null);
+        mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0, null, null);
 
         // THEN the returned intent is original intent
         assertSame(originalIntent, mInterceptor.mIntent);
@@ -345,7 +349,7 @@
         mAInfo.directBootAware = false;
 
         // THEN calling intercept returns true
-        mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null);
+        mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null);
 
         // THEN the returned intent is the confirm credentials intent
         assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent));
@@ -362,7 +366,7 @@
         mAInfo.directBootAware = true;
 
         // THEN calling intercept returns true
-        mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0, null);
+        mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0, null, null);
 
         // THEN the returned intent is original intent
         assertSame(originalIntent, mInterceptor.mIntent);
@@ -375,7 +379,7 @@
                 .thenReturn("This app is bad");
 
         // THEN calling intercept returns true
-        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
 
         // THEN the returned intent is the harmful app warning intent
         assertEquals(HarmfulAppWarningActivity.class.getName(),
@@ -383,11 +387,40 @@
     }
 
     @Test
+    public void testHomeIntentInterception() {
+        // GIVEN a primary home intent and a display area that doesn't support it but supports
+        // secondary home activities
+        Intent originalIntent = new Intent(Intent.ACTION_MAIN);
+        originalIntent.addCategory(Intent.CATEGORY_HOME);
+
+        Intent expectedIntent = new Intent(Intent.ACTION_MAIN);
+        expectedIntent.addCategory(Intent.CATEGORY_SECONDARY_HOME);
+        expectedIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final int secondaryDisplayId = 7;
+        when(mTaskDisplayArea.getDisplayId()).thenReturn(secondaryDisplayId);
+        when(mRootWindowContainer.shouldPlacePrimaryHomeOnDisplay(eq(secondaryDisplayId)))
+                .thenReturn(false);
+        when(mRootWindowContainer.shouldPlaceSecondaryHomeOnDisplayArea(eq(mTaskDisplayArea)))
+                .thenReturn(true);
+        when(mRootWindowContainer.resolveSecondaryHomeActivity(
+                eq(TEST_USER_ID), eq(mTaskDisplayArea)))
+                .thenReturn(Pair.create(null, expectedIntent));
+
+        // THEN calling intercept returns true
+        assertTrue(mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0,
+                null, mTaskDisplayArea));
+
+        // THEN the returned intent is the secondary home intent
+        assertSame(expectedIntent, mInterceptor.mIntent);
+    }
+
+    @Test
     public void testNoInterception() {
         // GIVEN that none of the interception conditions are met
 
         // THEN calling intercept returns false
-        assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+        assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
     }
 
     public void addMockInterceptorCallback(
@@ -420,7 +453,7 @@
                 new Intent("android.test.foo"),
                 ActivityOptions.makeBasic().setLaunchDisplayId(3));
 
-        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
         assertEquals("android.test.foo", mInterceptor.mIntent.getAction());
         assertEquals(3, mInterceptor.mActivityOptions.getLaunchDisplayId());
     }
@@ -429,7 +462,7 @@
     public void testInterceptionCallback_singleCallbackReturnsNull() {
         addMockInterceptorCallback(null, null);
 
-        assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+        assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
     }
 
     @Test
@@ -437,7 +470,7 @@
         addMockInterceptorCallback(null, null);
         addMockInterceptorCallback(new Intent("android.test.second"), null);
 
-        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
         assertEquals("android.test.second", mInterceptor.mIntent.getAction());
     }
 
@@ -447,7 +480,7 @@
                 new Intent("android.test.foo"),
                 ActivityOptions.makeBasic().setLaunchDisplayId(3), true);
         ActivityInfo aInfo = mAInfo;
-        assertTrue(mInterceptor.intercept(null, null, aInfo, null, null, null, 0, 0, null));
+        assertTrue(mInterceptor.intercept(null, null, aInfo, null, null, null, 0, 0, null, null));
         assertEquals("android.test.foo", mInterceptor.mIntent.getAction());
         assertEquals(3, mInterceptor.mActivityOptions.getLaunchDisplayId());
         assertEquals(aInfo, mInterceptor.mAInfo); // mAInfo should not be resolved
@@ -459,7 +492,7 @@
                 new Intent("android.test.foo"),
                 ActivityOptions.makeBasic().setLaunchDisplayId(3));
         ActivityInfo aInfo = mAInfo;
-        assertTrue(mInterceptor.intercept(null, null, aInfo, null, null, null, 0, 0, null));
+        assertTrue(mInterceptor.intercept(null, null, aInfo, null, null, null, 0, 0, null, null));
         assertEquals("android.test.foo", mInterceptor.mIntent.getAction());
         assertEquals(3, mInterceptor.mActivityOptions.getLaunchDisplayId());
         assertNotEquals(aInfo, mInterceptor.mAInfo); // mAInfo should be resolved after intercept
@@ -488,7 +521,7 @@
         when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
 
         Intent intent = new Intent().setAction(ACTION_START_SANDBOXED_ACTIVITY);
-        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null);
+        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
 
         verify(spyCallback, times(1)).onInterceptActivityLaunch(
                 any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
@@ -505,7 +538,7 @@
         when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
 
         Intent intent = new Intent().setPackage(sandboxPackageNameMock);
-        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null);
+        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
 
         verify(spyCallback, times(1)).onInterceptActivityLaunch(
                 any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
@@ -522,7 +555,7 @@
         when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
 
         Intent intent = new Intent().setComponent(new ComponentName(sandboxPackageNameMock, ""));
-        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null);
+        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
 
         verify(spyCallback, times(1)).onInterceptActivityLaunch(
                 any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
@@ -539,23 +572,23 @@
         when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
 
         // Intent: null
-        mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null);
+        mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null);
 
         // Action: null, Package: null, ComponentName: null
         Intent intent = new Intent();
-        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null);
+        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
 
         // Wrong Action
         intent = new Intent().setAction(Intent.ACTION_VIEW);
-        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null);
+        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
 
         // Wrong Package
         intent = new Intent().setPackage("Random");
-        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null);
+        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
 
         // Wrong ComponentName's package
         intent = new Intent().setComponent(new ComponentName("Random", ""));
-        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null);
+        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
 
         verify(spyCallback, never()).onInterceptActivityLaunch(
                 any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index cb92cc5..ae87e38 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1662,9 +1662,10 @@
 
     @Test
     public void testResultCanceledWhenNotAllowedStartingActivity() {
+        final Task task = new TaskBuilder(mSupervisor).build();
         final ActivityStarter starter = prepareStarter(0, false);
         final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build();
-        final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).build();
+        final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setTask(task).build();
         targetRecord.resultTo = sourceRecord;
 
         // Abort the activity start and ensure the sourceRecord gets the result (RESULT_CANCELED).
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index e1fc0cf..80e169d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -250,4 +250,42 @@
                 times(expectedTime)).setLetterboxPositionForVerticalReachability(halfFoldPose,
                 expected);
     }
+
+    @Test
+    public void test_letterboxPositionWhenReachabilityEnabledIsReset() {
+        // Check that horizontal reachability is set with correct arguments
+        mLetterboxConfiguration.resetPersistentLetterboxPositionForHorizontalReachability();
+        verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
+                false /* forBookMode */,
+                LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER);
+        verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
+                true /* forBookMode */,
+                LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+
+        // Check that vertical reachability is set with correct arguments
+        mLetterboxConfiguration.resetPersistentLetterboxPositionForVerticalReachability();
+        verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
+                false /* forTabletopMode */,
+                LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER);
+        verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
+                true /* forTabletopMode */,
+                LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+    }
+
+    @Test
+    public void test_lettterboxPositionWhenReachabilityEnabledIsSet() {
+        // Check that horizontal reachability is set with correct arguments
+        mLetterboxConfiguration.setPersistentLetterboxPositionForHorizontalReachability(
+                false /* forBookMode */, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+        verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
+                false /* forBookMode */,
+                LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+
+        // Check that vertical reachability is set with correct arguments
+        mLetterboxConfiguration.setPersistentLetterboxPositionForVerticalReachability(
+                false /* forTabletopMode */, LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+        verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
+                false /* forTabletopMode */,
+                LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 739737e..07cfbf0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -678,6 +678,39 @@
                 WINDOWING_MODE_FULLSCREEN);
     }
 
+    @Test
+    public void testInheritsSourceTaskWindowingModeWhenActivityIsInDifferentWindowingMode() {
+        final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
+                WINDOWING_MODE_FULLSCREEN);
+        final ActivityRecord source = createSourceActivity(fullscreenDisplay);
+        source.setWindowingMode(WINDOWING_MODE_PINNED);
+        source.getTask().setWindowingMode(WINDOWING_MODE_FREEFORM);
+
+        assertEquals(RESULT_CONTINUE,
+                new CalculateRequestBuilder().setSource(source).calculate());
+
+        assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+                WINDOWING_MODE_FULLSCREEN);
+    }
+
+    @Test
+    public void testDoesNotInheritsSourceTaskWindowingModeWhenActivityIsInFreeformWindowingMode() {
+        // The activity could end up in different windowing mode state after calling finish()
+        // while the task would still hold the WINDOWING_MODE_PINNED state, or in other words
+        // be still in the Picture in Picture mode.
+        final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
+                WINDOWING_MODE_FULLSCREEN);
+        final ActivityRecord source = createSourceActivity(fullscreenDisplay);
+        source.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        source.getTask().setWindowingMode(WINDOWING_MODE_PINNED);
+
+        assertEquals(RESULT_CONTINUE,
+                new CalculateRequestBuilder().setSource(source).calculate());
+
+        assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
+                WINDOWING_MODE_FULLSCREEN);
+    }
+
 
     @Test
     public void testKeepsPictureInPictureLaunchModeInOptions() {
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index e51696e..7d9b379 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -31,6 +32,7 @@
 import android.os.ParcelFileDescriptor;
 
 import com.android.internal.telecom.IVideoProvider;
+import com.android.server.telecom.flags.Flags;
 
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -720,6 +722,7 @@
          * The underlying connection was added as a transactional call via the
          * {@link TelecomManager#addCall} API.
          */
+        @FlaggedApi(Flags.FLAG_VOIP_APP_ACTIONS_SUPPORT)
         public static final int PROPERTY_IS_TRANSACTIONAL = 0x00008000;
 
         //******************************************************************************************
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 0bb75d8..ac8200a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9456,6 +9456,19 @@
             "carrier_supported_satellite_services_per_provider_bundle";
 
     /**
+     * This config enables modem to scan satellite PLMNs specified as per
+     * {@link #KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE} and attach to same
+     * in case cellular networks are not enabled. This will need specific agreement between
+     * satellite provider and the carrier before enabling this flag.
+     *
+     * The default value is false.
+     *
+     * @hide
+     */
+    public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL =
+            "satellite_attach_supported_bool";
+
+    /**
      * Indicating whether DUN APN should be disabled when the device is roaming. In that case,
      * the default APN (i.e. internet) will be used for tethering.
      *
@@ -10465,6 +10478,7 @@
         sDefaults.putPersistableBundle(
                 KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
                 PersistableBundle.EMPTY);
+        sDefaults.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, false);
         sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
         sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
         sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 75b5f55..5f6c14a 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -44,10 +44,14 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.time.Duration;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * Manages satellite operations such as provisioning, pointing, messaging, location sharing, etc.
@@ -79,6 +83,23 @@
     @Nullable private final Context mContext;
 
     /**
+     * Create a new SatelliteManager object pinned to the given subscription ID.
+     * This is needed only to handle carrier specific satellite features.
+     * For eg: requestSatelliteAttachEnabledForCarrier and
+     *         requestIsSatelliteAttachEnabledForCarrier
+     *
+     * @return a SatelliteManager that uses the given subId for all satellite activities.
+     * @throws IllegalArgumentException if the subscription is invalid.
+     * @hide
+     */
+    public SatelliteManager createForSubscriptionId(int subId) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid subscription ID");
+        }
+        return new SatelliteManager(mContext, subId);
+    }
+
+    /**
      * Create an instance of the SatelliteManager.
      *
      * @param context The context the SatelliteManager belongs to.
@@ -791,6 +812,27 @@
     public @interface DatagramType {}
 
     /**
+     * Satellite communication restricted by user.
+     * @hide
+     */
+    public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER = 0;
+
+    /**
+     * Satellite communication restricted by geolocation. This can be
+     * triggered based upon geofence input provided by carrier to enable or disable satellite.
+     * @hide
+     */
+    public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1;
+
+    /** @hide */
+    @IntDef(prefix = "SATELLITE_COMMUNICATION_RESTRICTION_REASON_", value = {
+            SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER,
+            SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SatelliteCommunicationRestrictionReason {}
+
+    /**
      * Start receiving satellite transmission updates.
      * This can be called by the pointing UI when the user starts pointing to the satellite.
      * Modem should continue to report the pointing input as the device or satellite moves.
@@ -1559,6 +1601,182 @@
         }
     }
 
+    /**
+     * User request to enable or disable carrier supported satellite plmn scan and attach by modem.
+     * <p>
+     * This API should be called by only settings app to pass down the user input for
+     * enabling/disabling satellite. This user input will be persisted across device reboots.
+     * <p>
+     * Satellite will be enabled only when the following conditions are met:
+     * <ul>
+     * <li>Users want to enable it.</li>
+     * <li>There is no satellite communication restriction, which is added by
+     * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}</li>
+     * <li>The carrier config {@link
+     * android.telephony.CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} is set to
+     * {@code true}.</li>
+     * </ul>
+     *
+     * @param enableSatellite {@code true} to enable the satellite and {@code false} to disable.
+     * @param executor The executor on which the error code listener will be called.
+     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    public void requestSatelliteAttachEnabledForCarrier(boolean enableSatellite,
+            @NonNull @CallbackExecutor Executor executor,
+            @SatelliteResult @NonNull Consumer<Integer> resultListener) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(resultListener);
+
+        if (enableSatellite) {
+            removeSatelliteAttachRestrictionForCarrier(
+                    SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, executor, resultListener);
+        } else {
+            addSatelliteAttachRestrictionForCarrier(
+                    SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, executor, resultListener);
+        }
+    }
+
+    /**
+     * Request to get whether the carrier supported satellite plmn scan and attach by modem is
+     * enabled by user.
+     *
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback object to which the result will be delivered.
+     *                 If the request is successful, {@link OutcomeReceiver#onResult(Object)}
+     *                 will return a {@code boolean} with value {@code true} if the satellite
+     *                 is enabled and {@code false} otherwise.
+     *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
+     *                 will return a {@link SatelliteException} with the {@link SatelliteError}.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    public void requestIsSatelliteAttachEnabledForCarrier(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        Set<Integer> restrictionReason = getSatelliteAttachRestrictionReasonsForCarrier();
+        executor.execute(() -> callback.onResult(
+                !restrictionReason.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER)));
+    }
+
+    /**
+     * Add a restriction reason for disallowing carrier supported satellite plmn scan and attach
+     * by modem.
+     *
+     * @param reason Reason for disallowing satellite communication.
+     * @param executor The executor on which the error code listener will be called.
+     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    public void addSatelliteAttachRestrictionForCarrier(
+            @SatelliteCommunicationRestrictionReason int reason,
+            @NonNull @CallbackExecutor Executor executor,
+            @SatelliteResult @NonNull Consumer<Integer> resultListener) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        executor.execute(() -> Binder.withCleanCallingIdentity(
+                                () -> resultListener.accept(result)));
+                    }
+                };
+                telephony.addSatelliteAttachRestrictionForCarrier(mSubId, reason, errorCallback);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("addSatelliteAttachRestrictionForCarrier() RemoteException:" + ex);
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove a restriction reason for disallowing carrier supported satellite plmn scan and attach
+     * by modem.
+     *
+     * @param reason Reason for disallowing satellite communication.
+     * @param executor The executor on which the error code listener will be called.
+     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    public void removeSatelliteAttachRestrictionForCarrier(
+            @SatelliteCommunicationRestrictionReason int reason,
+            @NonNull @CallbackExecutor Executor executor,
+            @SatelliteResult @NonNull Consumer<Integer> resultListener) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        executor.execute(() -> Binder.withCleanCallingIdentity(
+                                () -> resultListener.accept(result)));
+                    }
+                };
+                telephony.removeSatelliteAttachRestrictionForCarrier(mSubId, reason, errorCallback);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("removeSatelliteAttachRestrictionForCarrier() RemoteException:" + ex);
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get reasons for disallowing satellite attach, as requested by
+     * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}
+     *
+     * @return Set of reasons for disallowing satellite communication.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @SatelliteCommunicationRestrictionReason
+    public @NonNull Set<Integer> getSatelliteAttachRestrictionReasonsForCarrier() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                int[] receivedArray =
+                        telephony.getSatelliteAttachRestrictionReasonsForCarrier(mSubId);
+                if (receivedArray.length == 0) {
+                    logd("received set is empty, create empty set");
+                    return new HashSet<>();
+                } else {
+                    return Arrays.stream(receivedArray).boxed().collect(Collectors.toSet());
+                }
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("getSatelliteAttachRestrictionReasonsForCarrier() RemoteException: " + ex);
+            ex.rethrowFromSystemServer();
+        }
+        return null;
+    }
+
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
                 .getTelephonyServiceManager()
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index ea4e2e2..02661de 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -382,4 +382,64 @@
      */
     void requestTimeForNextSatelliteVisibility(in IIntegerConsumer resultCallback,
             in IIntegerConsumer callback);
+
+    /**
+     * Set the non-terrestrial PLMN with lower priority than terrestrial networks.
+     * MCC/MNC broadcast by the non-terrestrial networks may not be included in OPLMNwACT file on
+     * SIM profile. Acquisition of satellite based system is lower priority to terrestrial
+     * networks. UE shall make all attempts to acquire terrestrial service prior to camping on
+     * satellite LTE service.
+     *
+     * @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
+     *                this information to determine the relevant carrier.
+     * @param plmnList The list of roaming PLMN used for connecting to satellite networks.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *
+     * Valid error codes returned:
+     *   SatelliteError:NONE
+     *   SatelliteError:INVALID_ARGUMENTS
+     *   SatelliteError:INVALID_MODEM_STATE
+     *   SatelliteError:MODEM_ERR
+     *   SatelliteError:NO_RESOURCES
+     *   SatelliteError:RADIO_NOT_AVAILABLE
+     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     */
+    void setSatellitePlmn(int simSlot, in List<String> plmnList,
+            in IIntegerConsumer resultCallback);
+
+    /**
+     * Enable or disable satellite in the cellular modem associated with a carrier.
+     * Refer setSatellitePlmn for the details of satellite PLMN scanning process.
+     *
+     * @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
+     *                this information to determine the relevant carrier.
+     * @param serial Serial number of request.
+     * @param enable {@code true} to enable satellite, {@code false} to disable satellite.
+     *
+     * Valid errors returned:
+     *   SatelliteError:NONE
+     *   SatelliteError:INVALID_MODEM_STATE
+     *   SatelliteError:MODEM_ERR
+     *   SatelliteError:RADIO_NOT_AVAILABLE
+     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     */
+    void setSatelliteEnabledForCarrier(int simSlot, boolean satelliteEnabled,
+         in IIntegerConsumer callback);
+
+    /**
+     * Check whether satellite is enabled in the cellular modem associated with a carrier.
+     *
+     * @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
+     *                this information to determine the relevant carrier.
+     * @param serial Serial number of request.
+     *
+     * Valid errors returned:
+     *   SatelliteError:NONE
+     *   SatelliteError:INVALID_MODEM_STATE
+     *   SatelliteError:MODEM_ERR
+     *   SatelliteError:RADIO_NOT_AVAILABLE
+     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     */
+    void requestIsSatelliteEnabledForCarrier(int simSlot, in IIntegerConsumer resultCallback,
+            in IBooleanConsumer callback);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index 17d026c..6451daf 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -25,6 +25,7 @@
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.util.TelephonyUtils;
 
+import java.util.List;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
@@ -212,6 +213,34 @@
                     "requestTimeForNextSatelliteVisibility");
         }
 
+        @Override
+        public void setSatellitePlmn(int simSlot, List<String> plmnList,
+                IIntegerConsumer errorCallback)
+                throws RemoteException {
+            executeMethodAsync(
+                    () -> SatelliteImplBase.this
+                            .setSatellitePlmn(simSlot, plmnList, errorCallback),
+                    "setSatellitePlmn");
+        }
+
+        @Override
+        public void setSatelliteEnabledForCarrier(int simSlot, boolean enableSatellite,
+                IIntegerConsumer errorCallback) throws RemoteException {
+            executeMethodAsync(
+                    () -> SatelliteImplBase.this
+                            .setSatelliteEnabledForCarrier(simSlot, enableSatellite, errorCallback),
+                    "setSatelliteEnabledForCarrier");
+        }
+
+        @Override
+        public void requestIsSatelliteEnabledForCarrier(int simSlot, IIntegerConsumer errorCallback,
+                IBooleanConsumer callback) throws RemoteException {
+            executeMethodAsync(
+                    () -> SatelliteImplBase.this
+                            .requestIsSatelliteEnabledForCarrier(simSlot, errorCallback, callback),
+                    "requestIsSatelliteEnabledForCarrier");
+        }
+
         // Call the methods with a clean calling identity on the executor and wait indefinitely for
         // the future to return.
         private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
@@ -618,4 +647,75 @@
             @NonNull IIntegerConsumer callback) {
         // stub implementation
     }
+
+
+    /**
+     * Set the non-terrestrial PLMN with lower priority than terrestrial networks.
+     * MCC/MNC broadcast by the non-terrestrial networks may not be included in OPLMNwACT file on
+     * SIM profile. Acquisition of satellite based system is lower priority to terrestrial
+     * networks. UE shall make all attempts to acquire terrestrial service prior to camping on
+     * satellite LTE service.
+     * This method must only take effect if {@link #setSatelliteEnabledForCarrier} is {@code true},
+     * and return an error otherwise.
+     *
+     * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be
+     * applied. The modem will use this information to determine the relevant carrier.
+     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param plmnList The list of roaming PLMN used for connecting to satellite networks.
+     *
+     * Valid error codes returned:
+     *   SatelliteError:NONE
+     *   SatelliteError:INVALID_ARGUMENTS
+     *   SatelliteError:INVALID_MODEM_STATE
+     *   SatelliteError:MODEM_ERR
+     *   SatelliteError:NO_RESOURCES
+     *   SatelliteError:RADIO_NOT_AVAILABLE
+     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     */
+    public void setSatellitePlmn(@NonNull int simLogicalSlotIndex, @NonNull List<String> plmnList,
+            @NonNull IIntegerConsumer errorCallback) {
+        // stub implementation
+    }
+
+    /**
+     * Request to enable or disable carrier supported satellite plmn scan and attach by modem.
+     * Refer {@link #setSatellitePlmn} for the details of satellite PLMN scanning process.
+     *
+     * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be
+     * applied. The modem will use this information to determine the relevant carrier.
+     * @param satelliteEnabled {@code true} to enable satellite, {@code false} to disable satellite.
+     * @param callback {@code true} to enable satellite, {@code false} to disable satellite.
+     *
+     * Valid errors returned:
+     *   SatelliteError:NONE
+     *   SatelliteError:INVALID_MODEM_STATE
+     *   SatelliteError:MODEM_ERR
+     *   SatelliteError:RADIO_NOT_AVAILABLE
+     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     */
+    public void setSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex,
+            @NonNull boolean satelliteEnabled, @NonNull IIntegerConsumer callback) {
+        // stub implementation
+    }
+
+    /**
+     * Request to get whether the satellite is enabled in the cellular modem associated with a
+     * carrier.
+     *
+     * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be
+     * applied. The modem will use this information to determine the relevant carrier.
+     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param callback {@code true} to satellite enabled, {@code false} to satellite disabled.
+     *
+     * Valid errors returned:
+     *   SatelliteError:NONE
+     *   SatelliteError:INVALID_MODEM_STATE
+     *   SatelliteError:MODEM_ERR
+     *   SatelliteError:RADIO_NOT_AVAILABLE
+     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     */
+    public void requestIsSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex,
+            @NonNull IIntegerConsumer errorCallback, @NonNull IBooleanConsumer callback) {
+        // stub implementation
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 06071fe..3aa5a5a 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3033,4 +3033,42 @@
      @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
                  + "android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)")
      List<String> getShaIdFromAllowList(String pkgName, int carrierId);
+
+    /**
+     * Add a restriction reason for disallowing satellite communication.
+     *
+     * @param subId The subId of the subscription to request for.
+     * @param reason Reason for disallowing satellite communication for carrier.
+     * @param callback Listener for the {@link SatelliteManager.SatelliteError} result of the
+     * operation.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void addSatelliteAttachRestrictionForCarrier(int subId, int reason,
+            in IIntegerConsumer callback);
+
+    /**
+     * Remove a restriction reason for disallowing satellite communication.
+     *
+     * @param subId The subId of the subscription to request for.
+     * @param reason Reason for disallowing satellite communication.
+     * @param callback Listener for the {@link SatelliteManager.SatelliteError} result of the
+     * operation.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void removeSatelliteAttachRestrictionForCarrier(int subId, int reason,
+            in IIntegerConsumer callback);
+
+    /**
+     * Get reasons for disallowing satellite communication, as requested by
+     * {@link #addSatelliteAttachRestrictionForCarrier(int, int)}.
+     *
+     * @param subId The subId of the subscription to request for.
+     *
+     * @return Set of reasons for disallowing satellite communication.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    int[] getSatelliteAttachRestrictionReasonsForCarrier(int subId);
 }
diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
index fe2fe0b..08430f2 100644
--- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
+++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
@@ -159,7 +159,7 @@
 
     private static BatteryUsageStats buildBatteryUsageStats() {
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false)
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false, 0)
                         .setBatteryCapacity(4000)
                         .setDischargePercentage(20)
                         .setDischargedPowerRange(1000, 2000)
diff --git a/tests/ApkVerityTest/Android.bp b/tests/FsVerityTest/Android.bp
similarity index 100%
rename from tests/ApkVerityTest/Android.bp
rename to tests/FsVerityTest/Android.bp
diff --git a/tests/ApkVerityTest/AndroidTest.xml b/tests/FsVerityTest/AndroidTest.xml
similarity index 100%
rename from tests/ApkVerityTest/AndroidTest.xml
rename to tests/FsVerityTest/AndroidTest.xml
diff --git a/tests/ApkVerityTest/FsVerityTestApp/Android.bp b/tests/FsVerityTest/FsVerityTestApp/Android.bp
similarity index 100%
rename from tests/ApkVerityTest/FsVerityTestApp/Android.bp
rename to tests/FsVerityTest/FsVerityTestApp/Android.bp
diff --git a/tests/ApkVerityTest/FsVerityTestApp/AndroidManifest.xml b/tests/FsVerityTest/FsVerityTestApp/AndroidManifest.xml
similarity index 100%
rename from tests/ApkVerityTest/FsVerityTestApp/AndroidManifest.xml
rename to tests/FsVerityTest/FsVerityTestApp/AndroidManifest.xml
diff --git a/tests/ApkVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java b/tests/FsVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java
similarity index 100%
rename from tests/ApkVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java
rename to tests/FsVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java
diff --git a/tests/ApkVerityTest/OWNERS b/tests/FsVerityTest/OWNERS
similarity index 100%
rename from tests/ApkVerityTest/OWNERS
rename to tests/FsVerityTest/OWNERS
diff --git a/tests/ApkVerityTest/TEST_MAPPING b/tests/FsVerityTest/TEST_MAPPING
similarity index 100%
rename from tests/ApkVerityTest/TEST_MAPPING
rename to tests/FsVerityTest/TEST_MAPPING
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/FsVerityTest/block_device_writer/Android.bp
similarity index 100%
rename from tests/ApkVerityTest/block_device_writer/Android.bp
rename to tests/FsVerityTest/block_device_writer/Android.bp
diff --git a/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp b/tests/FsVerityTest/block_device_writer/block_device_writer.cpp
similarity index 100%
rename from tests/ApkVerityTest/block_device_writer/block_device_writer.cpp
rename to tests/FsVerityTest/block_device_writer/block_device_writer.cpp
diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/FsVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
similarity index 100%
rename from tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
rename to tests/FsVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
diff --git a/tests/ApkVerityTest/src/com/android/fsverity/FsVerityHostTest.java b/tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java
similarity index 100%
rename from tests/ApkVerityTest/src/com/android/fsverity/FsVerityHostTest.java
rename to tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java
diff --git a/tests/ApkVerityTest/testdata/Android.bp b/tests/FsVerityTest/testdata/Android.bp
similarity index 100%
rename from tests/ApkVerityTest/testdata/Android.bp
rename to tests/FsVerityTest/testdata/Android.bp
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestCert.der b/tests/FsVerityTest/testdata/ApkVerityTestCert.der
similarity index 100%
rename from tests/ApkVerityTest/testdata/ApkVerityTestCert.der
rename to tests/FsVerityTest/testdata/ApkVerityTestCert.der
Binary files differ
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestCert.pem b/tests/FsVerityTest/testdata/ApkVerityTestCert.pem
similarity index 100%
rename from tests/ApkVerityTest/testdata/ApkVerityTestCert.pem
rename to tests/FsVerityTest/testdata/ApkVerityTestCert.pem
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestKey.pem b/tests/FsVerityTest/testdata/ApkVerityTestKey.pem
similarity index 100%
rename from tests/ApkVerityTest/testdata/ApkVerityTestKey.pem
rename to tests/FsVerityTest/testdata/ApkVerityTestKey.pem
diff --git a/tools/hoststubgen/OWNERS b/tools/hoststubgen/OWNERS
new file mode 100644
index 0000000..a8c5321
--- /dev/null
+++ b/tools/hoststubgen/OWNERS
@@ -0,0 +1,3 @@
+omakoto@google.com
+jsharkey@google.com
+jaggies@google.com