Merge "Unify IMMS#onSecureSettingsChangedLocked() behaviors among users" into main
diff --git a/Android.bp b/Android.bp
index eabd9c7..cf73451 100644
--- a/Android.bp
+++ b/Android.bp
@@ -255,7 +255,7 @@
         "android.hardware.vibrator-V1.1-java",
         "android.hardware.vibrator-V1.2-java",
         "android.hardware.vibrator-V1.3-java",
-        "android.hardware.vibrator-V2-java",
+        "android.hardware.vibrator-V3-java",
         "android.se.omapi-V1-java",
         "android.system.suspend.control.internal-java",
         "devicepolicyprotosnano",
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 36a335e..fd0262e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -409,6 +409,7 @@
     field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String USE_ON_DEVICE_INTELLIGENCE = "android.permission.USE_ON_DEVICE_INTELLIGENCE";
     field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
     field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED";
+    field @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public static final String VIBRATE_VENDOR_EFFECTS = "android.permission.VIBRATE_VENDOR_EFFECTS";
     field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
     field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
     field public static final String WIFI_ACCESS_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS";
@@ -11354,6 +11355,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.os.UserManager.EnforcingUser> CREATOR;
   }
 
+  public abstract class VibrationEffect implements android.os.Parcelable {
+    method @FlaggedApi("android.os.vibrator.vendor_vibration_effects") @NonNull @RequiresPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS) public static android.os.VibrationEffect createVendorEffect(@NonNull android.os.PersistableBundle);
+  }
+
   public abstract class Vibrator {
     method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
     method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.Vibrator.OnVibratorStateChangedListener);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 88b5275..90af259 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2577,6 +2577,16 @@
   public static final class VibrationEffect.Composition.UnreachableAfterRepeatingIndefinitelyException extends java.lang.IllegalStateException {
   }
 
+  @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public static final class VibrationEffect.VendorEffect extends android.os.VibrationEffect {
+    method @Nullable public long[] computeCreateWaveformOffOnTimingsOrNull();
+    method public long getDuration();
+    method public int getEffectStrength();
+    method public float getLinearScale();
+    method @NonNull public android.os.PersistableBundle getVendorData();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.VendorEffect> CREATOR;
+  }
+
   public static class VibrationEffect.VibrationParameter {
     method @NonNull public static android.os.VibrationEffect.VibrationParameter targetAmplitude(@FloatRange(from=0, to=1) float);
     method @NonNull public static android.os.VibrationEffect.VibrationParameter targetFrequency(@FloatRange(from=1) float);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index cbabb02..90de7ab 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2214,6 +2214,9 @@
         notifyVoiceInteractionManagerServiceActivityEvent(
                 VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_RESUME);
 
+        // Notify autofill
+        getAutofillClientController().onActivityPostResumed();
+
         mCalled = true;
     }
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4952af3..75aab7d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3818,8 +3818,7 @@
         final ArrayList<ResultInfo> list = new ArrayList<>();
         list.add(new ResultInfo(id, requestCode, resultCode, data, activityToken));
         final ClientTransaction clientTransaction = new ClientTransaction(mAppThread);
-        final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
-                activityToken, list);
+        final ActivityResultItem activityResultItem = new ActivityResultItem(activityToken, list);
         clientTransaction.addTransactionItem(activityResultItem);
         try {
             mAppThread.scheduleTransaction(clientTransaction);
@@ -4614,7 +4613,7 @@
 
     private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) {
         final ClientTransaction transaction = new ClientTransaction(mAppThread);
-        final PauseActivityItem pauseActivityItem = PauseActivityItem.obtain(r.token,
+        final PauseActivityItem pauseActivityItem = new PauseActivityItem(r.token,
                 r.activity.isFinishing(), /* userLeaving */ true,
                 /* dontReport */ false, /* autoEnteringPip */ false);
         transaction.addTransactionItem(pauseActivityItem);
@@ -4623,7 +4622,7 @@
 
     private void scheduleResume(ActivityClientRecord r) {
         final ClientTransaction transaction = new ClientTransaction(mAppThread);
-        final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(r.token,
+        final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(r.token,
                 /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
         transaction.addTransactionItem(resumeActivityItem);
         executeTransaction(transaction);
@@ -6239,7 +6238,7 @@
                 r.createdConfig != null
                         ? r.createdConfig : mConfigurationController.getConfiguration(),
                 r.overrideConfig);
-        final ActivityRelaunchItem activityRelaunchItem = ActivityRelaunchItem.obtain(
+        final ActivityRelaunchItem activityRelaunchItem = new ActivityRelaunchItem(
                 r.token, null /* pendingResults */, null /* pendingIntents */,
                 0 /* configChanges */, mergedConfiguration, r.mPreserveWindow,
                 r.getActivityWindowInfo());
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index 11d7ff8..2b52681 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -19,6 +19,8 @@
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread.ActivityClientRecord;
@@ -34,12 +36,23 @@
 
 /**
  * Activity configuration changed callback.
+ *
  * @hide
  */
 public class ActivityConfigurationChangeItem extends ActivityTransactionItem {
 
-    private Configuration mConfiguration;
-    private ActivityWindowInfo mActivityWindowInfo;
+    @NonNull
+    private final Configuration mConfiguration;
+
+    @NonNull
+    private final ActivityWindowInfo mActivityWindowInfo;
+
+    public ActivityConfigurationChangeItem(@NonNull IBinder activityToken,
+            @NonNull Configuration config, @NonNull ActivityWindowInfo activityWindowInfo) {
+        super(activityToken);
+        mConfiguration = new Configuration(config);
+        mActivityWindowInfo = new ActivityWindowInfo(activityWindowInfo);
+    }
 
     @Override
     public void preExecute(@NonNull ClientTransactionHandler client) {
@@ -59,38 +72,9 @@
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
-    // ObjectPoolItem implementation
-
-    private ActivityConfigurationChangeItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    @NonNull
-    public static ActivityConfigurationChangeItem obtain(@NonNull IBinder activityToken,
-            @NonNull Configuration config, @NonNull ActivityWindowInfo activityWindowInfo) {
-        ActivityConfigurationChangeItem instance =
-                ObjectPool.obtain(ActivityConfigurationChangeItem.class);
-        if (instance == null) {
-            instance = new ActivityConfigurationChangeItem();
-        }
-        instance.setActivityToken(activityToken);
-        instance.mConfiguration = new Configuration(config);
-        instance.mActivityWindowInfo = new ActivityWindowInfo(activityWindowInfo);
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        mConfiguration = null;
-        mActivityWindowInfo = null;
-        ObjectPool.recycle(this);
-    }
-
-
     // Parcelable implementation
 
-    /** Write to Parcel. */
+    /** Writes to Parcel. */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
@@ -98,11 +82,11 @@
         dest.writeTypedObject(mActivityWindowInfo, flags);
     }
 
-    /** Read from Parcel. */
+    /** Reads from Parcel. */
     private ActivityConfigurationChangeItem(@NonNull Parcel in) {
         super(in);
-        mConfiguration = in.readTypedObject(Configuration.CREATOR);
-        mActivityWindowInfo = in.readTypedObject(ActivityWindowInfo.CREATOR);
+        mConfiguration = requireNonNull(in.readTypedObject(Configuration.CREATOR));
+        mActivityWindowInfo = requireNonNull(in.readTypedObject(ActivityWindowInfo.CREATOR));
     }
 
     public static final @NonNull Creator<ActivityConfigurationChangeItem> CREATOR =
diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
index 48db18f..778f134 100644
--- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java
+++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.os.IBinder;
 import android.os.Parcel;
 
 import java.lang.annotation.Retention;
@@ -25,6 +26,7 @@
 
 /**
  * Request for lifecycle state that an activity should reach.
+ *
  * @hide
  */
 public abstract class ActivityLifecycleItem extends ActivityTransactionItem {
@@ -52,8 +54,13 @@
     public static final int ON_DESTROY = 6;
     public static final int ON_RESTART = 7;
 
-    ActivityLifecycleItem() {}
+    ActivityLifecycleItem(@NonNull IBinder activityToken) {
+        super(activityToken);
+    }
 
+    // Parcelable implementation
+
+    /** Reads from Parcel. */
     ActivityLifecycleItem(@NonNull Parcel in) {
         super(in);
     }
diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
index 45bf235..cecf701 100644
--- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -18,6 +18,8 @@
 
 import static android.app.ActivityThread.DEBUG_ORDER;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread.ActivityClientRecord;
@@ -39,25 +41,50 @@
 
 /**
  * Activity relaunch callback.
+ *
  * @hide
  */
 public class ActivityRelaunchItem extends ActivityTransactionItem {
 
     private static final String TAG = "ActivityRelaunchItem";
 
-    private List<ResultInfo> mPendingResults;
-    private List<ReferrerIntent> mPendingNewIntents;
-    private int mConfigChanges;
-    private MergedConfiguration mConfig;
-    private boolean mPreserveWindow;
-    private ActivityWindowInfo mActivityWindowInfo;
+    @Nullable
+    private final List<ResultInfo> mPendingResults;
+
+    @Nullable
+    private final List<ReferrerIntent> mPendingNewIntents;
+
+    @NonNull
+    private final MergedConfiguration mConfig;
+
+    @NonNull
+    private final ActivityWindowInfo mActivityWindowInfo;
+
+    private final int mConfigChanges;
+    private final boolean mPreserveWindow;
 
     /**
      * A record that was properly configured for relaunch. Execution will be cancelled if not
      * initialized after {@link #preExecute(ClientTransactionHandler)}.
      */
+    @Nullable
     private ActivityClientRecord mActivityClientRecord;
 
+    public ActivityRelaunchItem(@NonNull IBinder activityToken,
+            @Nullable List<ResultInfo> pendingResults,
+            @Nullable List<ReferrerIntent> pendingNewIntents, int configChanges,
+            @NonNull MergedConfiguration config, boolean preserveWindow,
+            @NonNull ActivityWindowInfo activityWindowInfo) {
+        super(activityToken);
+        mPendingResults = pendingResults != null ? new ArrayList<>(pendingResults) : null;
+        mPendingNewIntents =
+                pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null;
+        mConfig = new MergedConfiguration(config);
+        mActivityWindowInfo = new ActivityWindowInfo(activityWindowInfo);
+        mConfigChanges = configChanges;
+        mPreserveWindow = preserveWindow;
+    }
+
     @Override
     public void preExecute(@NonNull ClientTransactionHandler client) {
         // The local config is already scaled so only apply if this item is from server side.
@@ -87,70 +114,29 @@
         client.reportRelaunch(r);
     }
 
-    // ObjectPoolItem implementation
-
-    private ActivityRelaunchItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    @NonNull
-    public static ActivityRelaunchItem obtain(@NonNull IBinder activityToken,
-            @Nullable List<ResultInfo> pendingResults,
-            @Nullable List<ReferrerIntent> pendingNewIntents, int configChanges,
-            @NonNull MergedConfiguration config, boolean preserveWindow,
-            @NonNull ActivityWindowInfo activityWindowInfo) {
-        ActivityRelaunchItem instance = ObjectPool.obtain(ActivityRelaunchItem.class);
-        if (instance == null) {
-            instance = new ActivityRelaunchItem();
-        }
-        instance.setActivityToken(activityToken);
-        instance.mPendingResults = pendingResults != null ? new ArrayList<>(pendingResults) : null;
-        instance.mPendingNewIntents =
-                pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null;
-        instance.mConfigChanges = configChanges;
-        instance.mConfig = new MergedConfiguration(config);
-        instance.mPreserveWindow = preserveWindow;
-        instance.mActivityWindowInfo = new ActivityWindowInfo(activityWindowInfo);
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        mPendingResults = null;
-        mPendingNewIntents = null;
-        mConfigChanges = 0;
-        mConfig = null;
-        mPreserveWindow = false;
-        mActivityClientRecord = null;
-        mActivityWindowInfo = null;
-        ObjectPool.recycle(this);
-    }
-
-
     // Parcelable implementation
 
-    /** Write to Parcel. */
+    /** Writes to Parcel. */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         dest.writeTypedList(mPendingResults, flags);
         dest.writeTypedList(mPendingNewIntents, flags);
-        dest.writeInt(mConfigChanges);
         dest.writeTypedObject(mConfig, flags);
-        dest.writeBoolean(mPreserveWindow);
         dest.writeTypedObject(mActivityWindowInfo, flags);
+        dest.writeInt(mConfigChanges);
+        dest.writeBoolean(mPreserveWindow);
     }
 
-    /** Read from Parcel. */
+    /** Reads from Parcel. */
     private ActivityRelaunchItem(@NonNull Parcel in) {
         super(in);
         mPendingResults = in.createTypedArrayList(ResultInfo.CREATOR);
         mPendingNewIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
+        mConfig = requireNonNull(in.readTypedObject(MergedConfiguration.CREATOR));
+        mActivityWindowInfo = requireNonNull(in.readTypedObject(ActivityWindowInfo.CREATOR));
         mConfigChanges = in.readInt();
-        mConfig = in.readTypedObject(MergedConfiguration.CREATOR);
         mPreserveWindow = in.readBoolean();
-        mActivityWindowInfo = in.readTypedObject(ActivityWindowInfo.CREATOR);
     }
 
     public static final @NonNull Creator<ActivityRelaunchItem> CREATOR =
@@ -175,9 +161,10 @@
         final ActivityRelaunchItem other = (ActivityRelaunchItem) o;
         return Objects.equals(mPendingResults, other.mPendingResults)
                 && Objects.equals(mPendingNewIntents, other.mPendingNewIntents)
-                && mConfigChanges == other.mConfigChanges && Objects.equals(mConfig, other.mConfig)
-                && mPreserveWindow == other.mPreserveWindow
-                && Objects.equals(mActivityWindowInfo, other.mActivityWindowInfo);
+                && Objects.equals(mConfig, other.mConfig)
+                && Objects.equals(mActivityWindowInfo, other.mActivityWindowInfo)
+                && mConfigChanges == other.mConfigChanges
+                && mPreserveWindow == other.mPreserveWindow;
     }
 
     @Override
@@ -186,10 +173,10 @@
         result = 31 * result + super.hashCode();
         result = 31 * result + Objects.hashCode(mPendingResults);
         result = 31 * result + Objects.hashCode(mPendingNewIntents);
-        result = 31 * result + mConfigChanges;
         result = 31 * result + Objects.hashCode(mConfig);
-        result = 31 * result + (mPreserveWindow ? 1 : 0);
         result = 31 * result + Objects.hashCode(mActivityWindowInfo);
+        result = 31 * result + mConfigChanges;
+        result = 31 * result + (mPreserveWindow ? 1 : 0);
         return result;
     }
 
@@ -198,9 +185,9 @@
         return "ActivityRelaunchItem{" + super.toString()
                 + ",pendingResults=" + mPendingResults
                 + ",pendingNewIntents=" + mPendingNewIntents
-                + ",configChanges="  + mConfigChanges
                 + ",config=" + mConfig
-                + ",preserveWindow=" + mPreserveWindow
-                + ",activityWindowInfo=" + mActivityWindowInfo + "}";
+                + ",activityWindowInfo=" + mActivityWindowInfo
+                + ",configChanges=" + mConfigChanges
+                + ",preserveWindow=" + mPreserveWindow + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java
index 51a09fb..761c519 100644
--- a/core/java/android/app/servertransaction/ActivityResultItem.java
+++ b/core/java/android/app/servertransaction/ActivityResultItem.java
@@ -41,10 +41,13 @@
 
 /**
  * Activity result delivery callback.
+ *
  * @hide
  */
 public class ActivityResultItem extends ActivityTransactionItem {
 
+    // TODO(b/170729553): Mark this with @NonNull and final once @UnsupportedAppUsage removed.
+    //  We cannot do it now to avoid app compatibility regression.
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private List<ResultInfo> mResultInfoList;
 
@@ -56,6 +59,12 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
     public static final long CALL_ACTIVITY_RESULT_BEFORE_RESUME = 78294732L;
 
+    public ActivityResultItem(@NonNull IBinder activityToken,
+            @NonNull List<ResultInfo> resultInfoList) {
+        super(activityToken);
+        mResultInfoList = new ArrayList<>(resultInfoList);
+    }
+
     @Override
     public int getPostExecutionState() {
         return CompatChanges.isChangeEnabled(CALL_ACTIVITY_RESULT_BEFORE_RESUME)
@@ -70,43 +79,19 @@
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
-    // ObjectPoolItem implementation
-
-    private ActivityResultItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    @NonNull
-    public static ActivityResultItem obtain(@NonNull IBinder activityToken,
-            @NonNull List<ResultInfo> resultInfoList) {
-        ActivityResultItem instance = ObjectPool.obtain(ActivityResultItem.class);
-        if (instance == null) {
-            instance = new ActivityResultItem();
-        }
-        instance.setActivityToken(activityToken);
-        instance.mResultInfoList = new ArrayList<>(resultInfoList);
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        mResultInfoList = null;
-        ObjectPool.recycle(this);
-    }
-
     // Parcelable implementation
 
-    /** Write to Parcel. */
+    /** Writes to Parcel. */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         dest.writeTypedList(mResultInfoList, flags);
     }
 
-    /** Read from Parcel. */
+    /** Reads from Parcel. */
     private ActivityResultItem(@NonNull Parcel in) {
         super(in);
+        // TODO(b/170729553): Wrap with requireNonNull once @UnsupportedAppUsage removed.
         mResultInfoList = in.createTypedArrayList(ResultInfo.CREATOR);
     }
 
diff --git a/core/java/android/app/servertransaction/ActivityTransactionItem.java b/core/java/android/app/servertransaction/ActivityTransactionItem.java
index b4ff476f..506a158 100644
--- a/core/java/android/app/servertransaction/ActivityTransactionItem.java
+++ b/core/java/android/app/servertransaction/ActivityTransactionItem.java
@@ -49,9 +49,12 @@
 public abstract class ActivityTransactionItem extends ClientTransactionItem {
 
     /** Target client activity. */
-    private IBinder mActivityToken;
+    @NonNull
+    private final IBinder mActivityToken;
 
-    ActivityTransactionItem() {}
+    public ActivityTransactionItem(@NonNull IBinder activityToken) {
+        mActivityToken = requireNonNull(activityToken);
+    }
 
     @Override
     public final void execute(@NonNull ClientTransactionHandler client,
@@ -94,26 +97,18 @@
         return mActivityToken;
     }
 
-    void setActivityToken(@NonNull IBinder activityToken) {
-        mActivityToken = requireNonNull(activityToken);
-    }
+    // Parcelable implementation
 
-    // To be overridden
-
-    ActivityTransactionItem(@NonNull Parcel in) {
-        mActivityToken = in.readStrongBinder();
-    }
-
+    /** Writes to Parcel. */
     @CallSuper
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeStrongBinder(mActivityToken);
     }
 
-    @CallSuper
-    @Override
-    public void recycle() {
-        mActivityToken = null;
+    /** Reads from Parcel. */
+    ActivityTransactionItem(@NonNull Parcel in) {
+        this(in.readStrongBinder());
     }
 
     @Override
diff --git a/core/java/android/app/servertransaction/BaseClientRequest.java b/core/java/android/app/servertransaction/BaseClientRequest.java
index f275175..bcbb514 100644
--- a/core/java/android/app/servertransaction/BaseClientRequest.java
+++ b/core/java/android/app/servertransaction/BaseClientRequest.java
@@ -22,31 +22,34 @@
 /**
  * Base interface for individual requests from server to client.
  * Each of them can be prepared before scheduling and, eventually, executed.
+ *
  * @hide
  */
-public interface BaseClientRequest extends ObjectPoolItem {
+public interface BaseClientRequest {
 
     /**
      * Prepares the client request before scheduling.
      * An example of this might be informing about pending updates for some values.
      *
-     * @param client Target client handler.
+     * @param client target client handler.
      */
     default void preExecute(@NonNull ClientTransactionHandler client) {
     }
 
     /**
      * Executes the request.
-     * @param client Target client handler.
-     * @param pendingActions Container that may have data pending to be used.
+     *
+     * @param client         target client handler.
+     * @param pendingActions container that may have data pending to be used.
      */
     void execute(@NonNull ClientTransactionHandler client,
             @NonNull PendingTransactionActions pendingActions);
 
     /**
      * Performs all actions that need to happen after execution, e.g. report the result to server.
-     * @param client Target client handler.
-     * @param pendingActions Container that may have data pending to be used.
+     *
+     * @param client         target client handler.
+     * @param pendingActions container that may have data pending to be used.
      */
     default void postExecute(@NonNull ClientTransactionHandler client,
             @NonNull PendingTransactionActions pendingActions) {
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
index 2d35bf1..99ebe1b 100644
--- a/core/java/android/app/servertransaction/ClientTransactionItem.java
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -75,17 +75,6 @@
         pw.append(prefix).println(this);
     }
 
-    /**
-     * Provides a default empty implementation for progressive cleanup.
-     *
-     * @deprecated This method is deprecated. The object pool is no longer used, so there's
-     * no need to recycle objects.
-     * TODO(b/311089192): Remove once ObjectPoolItem inheritance is removed.
-     */
-    @Override
-    @Deprecated
-    public void recycle() {}
-
     // Parcelable
 
     @Override
diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
index 22da706..123d792 100644
--- a/core/java/android/app/servertransaction/ConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
@@ -16,6 +16,8 @@
 
 package android.app.servertransaction;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ClientTransactionHandler;
@@ -27,12 +29,20 @@
 
 /**
  * App configuration change message.
+ *
  * @hide
  */
 public class ConfigurationChangeItem extends ClientTransactionItem {
 
-    private Configuration mConfiguration;
-    private int mDeviceId;
+    @NonNull
+    private final Configuration mConfiguration;
+
+    private final int mDeviceId;
+
+    public ConfigurationChangeItem(@NonNull Configuration config, int deviceId) {
+        mConfiguration = new Configuration(config);
+        mDeviceId = deviceId;
+    }
 
     @Override
     public void preExecute(@NonNull ClientTransactionHandler client) {
@@ -46,55 +56,31 @@
         client.handleConfigurationChanged(mConfiguration, mDeviceId);
     }
 
-    // ObjectPoolItem implementation
-
-    private ConfigurationChangeItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    public static ConfigurationChangeItem obtain(@NonNull Configuration config, int deviceId) {
-        ConfigurationChangeItem instance = ObjectPool.obtain(ConfigurationChangeItem.class);
-        if (instance == null) {
-            instance = new ConfigurationChangeItem();
-        }
-        instance.mConfiguration = new Configuration(config);
-        instance.mDeviceId = deviceId;
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        mConfiguration = null;
-        mDeviceId = 0;
-        ObjectPool.recycle(this);
-    }
-
-
     // Parcelable implementation
 
-    /** Write to Parcel. */
+    /** Writes to Parcel. */
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeTypedObject(mConfiguration, flags);
         dest.writeInt(mDeviceId);
     }
 
-    /** Read from Parcel. */
+    /** Reads from Parcel. */
     private ConfigurationChangeItem(Parcel in) {
-        mConfiguration = in.readTypedObject(Configuration.CREATOR);
+        mConfiguration = requireNonNull(in.readTypedObject(Configuration.CREATOR));
         mDeviceId = in.readInt();
     }
 
     public static final @android.annotation.NonNull Creator<ConfigurationChangeItem> CREATOR =
-            new Creator<ConfigurationChangeItem>() {
-        public ConfigurationChangeItem createFromParcel(Parcel in) {
-            return new ConfigurationChangeItem(in);
-        }
+            new Creator<>() {
+                public ConfigurationChangeItem createFromParcel(Parcel in) {
+                    return new ConfigurationChangeItem(in);
+                }
 
-        public ConfigurationChangeItem[] newArray(int size) {
-            return new ConfigurationChangeItem[size];
-        }
-    };
+                public ConfigurationChangeItem[] newArray(int size) {
+                    return new ConfigurationChangeItem[size];
+                }
+            };
 
     @Override
     public boolean equals(@Nullable Object o) {
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index b0213d7..8a81143 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -28,11 +28,17 @@
 
 /**
  * Request to destroy an activity.
+ *
  * @hide
  */
 public class DestroyActivityItem extends ActivityLifecycleItem {
 
-    private boolean mFinished;
+    private final boolean mFinished;
+
+    public DestroyActivityItem(@NonNull IBinder activityToken, boolean finished) {
+        super(activityToken);
+        mFinished = finished;
+    }
 
     @Override
     public void preExecute(@NonNull ClientTransactionHandler client) {
@@ -60,40 +66,16 @@
         return ON_DESTROY;
     }
 
-    // ObjectPoolItem implementation
-
-    private DestroyActivityItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    @NonNull
-    public static DestroyActivityItem obtain(@NonNull IBinder activityToken, boolean finished) {
-        DestroyActivityItem instance = ObjectPool.obtain(DestroyActivityItem.class);
-        if (instance == null) {
-            instance = new DestroyActivityItem();
-        }
-        instance.setActivityToken(activityToken);
-        instance.mFinished = finished;
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        mFinished = false;
-        ObjectPool.recycle(this);
-    }
-
     // Parcelable implementation
 
-    /** Write to Parcel. */
+    /** Writes to Parcel. */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         dest.writeBoolean(mFinished);
     }
 
-    /** Read from Parcel. */
+    /** Reads from Parcel. */
     private DestroyActivityItem(@NonNull Parcel in) {
         super(in);
         mFinished = in.readBoolean();
diff --git a/core/java/android/app/servertransaction/EnterPipRequestedItem.java b/core/java/android/app/servertransaction/EnterPipRequestedItem.java
index 743653f..b6f8655 100644
--- a/core/java/android/app/servertransaction/EnterPipRequestedItem.java
+++ b/core/java/android/app/servertransaction/EnterPipRequestedItem.java
@@ -24,39 +24,24 @@
 
 /**
  * Request an activity to enter picture-in-picture mode.
+ *
  * @hide
  */
 public final class EnterPipRequestedItem extends ActivityTransactionItem {
 
+    public EnterPipRequestedItem(@NonNull IBinder activityToken) {
+        super(activityToken);
+    }
+
     @Override
     public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
             @NonNull PendingTransactionActions pendingActions) {
         client.handlePictureInPictureRequested(r);
     }
 
-    // ObjectPoolItem implementation
-
-    private EnterPipRequestedItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    @NonNull
-    public static EnterPipRequestedItem obtain(@NonNull IBinder activityToken) {
-        EnterPipRequestedItem instance = ObjectPool.obtain(EnterPipRequestedItem.class);
-        if (instance == null) {
-            instance = new EnterPipRequestedItem();
-        }
-        instance.setActivityToken(activityToken);
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        ObjectPool.recycle(this);
-    }
-
     // Parcelable implementation
 
+    /** Reads from Parcel. */
     private EnterPipRequestedItem(@NonNull Parcel in) {
         super(in);
     }
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 7819e1e..235a9f7 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -20,9 +20,12 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityClient;
+import android.app.ActivityManager.ProcessState;
 import android.app.ActivityOptions.SceneTransitionInfo;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
@@ -52,41 +55,148 @@
 
 /**
  * Request to launch an activity.
+ *
  * @hide
  */
 public class LaunchActivityItem extends ClientTransactionItem {
 
-    private IBinder mActivityToken;
+    @NonNull
+    private final IBinder mActivityToken;
+
+    // TODO(b/170729553): Mark this with @NonNull and final once @UnsupportedAppUsage removed.
+    //  We cannot do it now to avoid app compatibility regression.
     @UnsupportedAppUsage
     private Intent mIntent;
-    private int mIdent;
+
+    // TODO(b/170729553): Mark this with @NonNull and final once @UnsupportedAppUsage removed.
+    //  We cannot do it now to avoid app compatibility regression.
     @UnsupportedAppUsage
     private ActivityInfo mInfo;
-    private Configuration mCurConfig;
-    private Configuration mOverrideConfig;
-    private int mDeviceId;
-    private String mReferrer;
-    private IVoiceInteractor mVoiceInteractor;
-    private int mProcState;
-    private Bundle mState;
-    private PersistableBundle mPersistentState;
-    private List<ResultInfo> mPendingResults;
-    private List<ReferrerIntent> mPendingNewIntents;
-    private SceneTransitionInfo mSceneTransitionInfo;
-    private boolean mIsForward;
-    private ProfilerInfo mProfilerInfo;
-    private IBinder mAssistToken;
-    private IBinder mShareableActivityToken;
-    private boolean mLaunchedFromBubble;
-    private IBinder mTaskFragmentToken;
-    private IBinder mInitialCallerInfoAccessToken;
-    private ActivityWindowInfo mActivityWindowInfo;
+
+    @NonNull
+    private final Configuration mCurConfig;
+
+    @NonNull
+    private final Configuration mOverrideConfig;
+
+    @Nullable
+    private final String mReferrer;
+
+    @Nullable
+    private final IVoiceInteractor mVoiceInteractor;
+
+    @Nullable
+    private final Bundle mState;
+
+    @Nullable
+    private final PersistableBundle mPersistentState;
+
+    @Nullable
+    private final List<ResultInfo> mPendingResults;
+
+    @Nullable
+    private final List<ReferrerIntent> mPendingNewIntents;
+
+    @Nullable
+    private final SceneTransitionInfo mSceneTransitionInfo;
+
+    @Nullable
+    private final ProfilerInfo mProfilerInfo;
+
+    @NonNull
+    private final IBinder mAssistToken;
+
+    @NonNull
+    private final IBinder mShareableActivityToken;
+
+    @Nullable
+    private final IBinder mTaskFragmentToken;
+
+    @NonNull
+    private final IBinder mInitialCallerInfoAccessToken;
+
+    @NonNull
+    private final ActivityWindowInfo mActivityWindowInfo;
 
     /**
      * It is only non-null if the process is the first time to launch activity. It is only an
      * optimization for quick look up of the interface so the field is ignored for comparison.
      */
-    private IActivityClientController mActivityClientController;
+    @Nullable
+    private final IActivityClientController mActivityClientController;
+
+    private final int mIdent;
+    private final int mDeviceId;
+    private final int mProcState;
+    private final boolean mIsForward;
+    private final boolean mLaunchedFromBubble;
+
+    public LaunchActivityItem(@NonNull IBinder activityToken, @NonNull Intent intent,
+            int ident, @NonNull ActivityInfo info, @NonNull Configuration curConfig,
+            @NonNull Configuration overrideConfig, int deviceId, @Nullable String referrer,
+            @Nullable IVoiceInteractor voiceInteractor, @ProcessState int procState,
+            @Nullable Bundle state, @Nullable PersistableBundle persistentState,
+            @Nullable List<ResultInfo> pendingResults,
+            @Nullable List<ReferrerIntent> pendingNewIntents,
+            @Nullable SceneTransitionInfo sceneTransitionInfo,
+            boolean isForward, @Nullable ProfilerInfo profilerInfo, @NonNull IBinder assistToken,
+            @Nullable IActivityClientController activityClientController,
+            @NonNull IBinder shareableActivityToken, boolean launchedFromBubble,
+            @Nullable IBinder taskFragmentToken, @NonNull IBinder initialCallerInfoAccessToken,
+            @NonNull ActivityWindowInfo activityWindowInfo) {
+        this(activityToken, ident, new Configuration(curConfig), new Configuration(overrideConfig),
+                deviceId, referrer, voiceInteractor, procState,
+                state != null ? new Bundle(state) : null,
+                persistentState != null ? new PersistableBundle(persistentState) : null,
+                pendingResults != null ? new ArrayList<>(pendingResults) : null,
+                pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null,
+                sceneTransitionInfo, isForward,
+                profilerInfo != null ? new ProfilerInfo(profilerInfo) : null,
+                assistToken, activityClientController, shareableActivityToken, launchedFromBubble,
+                taskFragmentToken, initialCallerInfoAccessToken,
+                new ActivityWindowInfo(activityWindowInfo));
+        mIntent = new Intent(intent);
+        mInfo = new ActivityInfo(info);
+    }
+
+    // TODO(b/170729553): Merge this constructor with previous one if no @UnsupportedAppUsage filed.
+    //  We cannot do it now to avoid app compatibility regression.
+    private LaunchActivityItem(@NonNull IBinder activityToken, int ident,
+            @NonNull Configuration curConfig,
+            @NonNull Configuration overrideConfig, int deviceId, @Nullable String referrer,
+            @Nullable IVoiceInteractor voiceInteractor, @ProcessState int procState,
+            @Nullable Bundle state, @Nullable PersistableBundle persistentState,
+            @Nullable List<ResultInfo> pendingResults,
+            @Nullable List<ReferrerIntent> pendingNewIntents,
+            @Nullable SceneTransitionInfo sceneTransitionInfo,
+            boolean isForward, @Nullable ProfilerInfo profilerInfo, @NonNull IBinder assistToken,
+            @Nullable IActivityClientController activityClientController,
+            @NonNull IBinder shareableActivityToken, boolean launchedFromBubble,
+            @Nullable IBinder taskFragmentToken, @NonNull IBinder initialCallerInfoAccessToken,
+            @NonNull ActivityWindowInfo activityWindowInfo) {
+        mActivityToken = activityToken;
+        mIdent = ident;
+        mCurConfig = curConfig;
+        mOverrideConfig = overrideConfig;
+        mDeviceId = deviceId;
+        mReferrer = referrer;
+        mVoiceInteractor = voiceInteractor;
+        mProcState = procState;
+        mState = state;
+        mPersistentState = persistentState;
+        mPendingResults = pendingResults;
+        mPendingNewIntents = pendingNewIntents;
+        mSceneTransitionInfo = sceneTransitionInfo;
+        mIsForward = isForward;
+        mProfilerInfo = profilerInfo;
+        mAssistToken = assistToken;
+        mActivityClientController = activityClientController;
+        mShareableActivityToken = shareableActivityToken;
+        mLaunchedFromBubble = launchedFromBubble;
+        mTaskFragmentToken = taskFragmentToken;
+        mInitialCallerInfoAccessToken = initialCallerInfoAccessToken;
+        mActivityWindowInfo = activityWindowInfo;
+    }
 
     @Override
     public void preExecute(@NonNull ClientTransactionHandler client) {
@@ -119,44 +229,6 @@
         client.countLaunchingActivities(-1);
     }
 
-    // ObjectPoolItem implementation
-
-    private LaunchActivityItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    @NonNull
-    public static LaunchActivityItem obtain(@NonNull IBinder activityToken, @NonNull Intent intent,
-            int ident, @NonNull ActivityInfo info, @NonNull Configuration curConfig,
-            @NonNull Configuration overrideConfig, int deviceId, @Nullable String referrer,
-            @Nullable IVoiceInteractor voiceInteractor, int procState, @Nullable Bundle state,
-            @Nullable PersistableBundle persistentState, @Nullable List<ResultInfo> pendingResults,
-            @Nullable List<ReferrerIntent> pendingNewIntents,
-            @Nullable SceneTransitionInfo sceneTransitionInfo,
-            boolean isForward, @Nullable ProfilerInfo profilerInfo, @NonNull IBinder assistToken,
-            @Nullable IActivityClientController activityClientController,
-            @NonNull IBinder shareableActivityToken, boolean launchedFromBubble,
-            @Nullable IBinder taskFragmentToken, @NonNull IBinder initialCallerInfoAccessToken,
-            @NonNull ActivityWindowInfo activityWindowInfo) {
-        LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class);
-        if (instance == null) {
-            instance = new LaunchActivityItem();
-        }
-        setValues(instance, activityToken, new Intent(intent), ident,  new ActivityInfo(info),
-                new Configuration(curConfig), new Configuration(overrideConfig), deviceId,
-                referrer, voiceInteractor, procState,
-                state != null ? new Bundle(state) : null,
-                persistentState != null ? new PersistableBundle(persistentState) : null,
-                pendingResults != null ? new ArrayList<>(pendingResults) : null,
-                pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null,
-                sceneTransitionInfo, isForward,
-                profilerInfo != null ? new ProfilerInfo(profilerInfo) : null,
-                assistToken, activityClientController, shareableActivityToken,
-                launchedFromBubble, taskFragmentToken, initialCallerInfoAccessToken,
-                new ActivityWindowInfo(activityWindowInfo));
-
-        return instance;
-    }
-
     @VisibleForTesting(visibility = PACKAGE)
     @NonNull
     @Override
@@ -164,22 +236,13 @@
         return mActivityToken;
     }
 
-    @Override
-    public void recycle() {
-        setValues(this, null, null, 0, null, null, null, 0, null, null, 0, null, null, null, null,
-                null, false, null, null, null, null, false, null, null, null);
-        ObjectPool.recycle(this);
-    }
-
     // Parcelable implementation
 
-    /** Write from Parcel. */
+    /** Writes to Parcel. */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeStrongBinder(mActivityToken);
-        dest.writeTypedObject(mIntent, flags);
         dest.writeInt(mIdent);
-        dest.writeTypedObject(mInfo, flags);
         dest.writeTypedObject(mCurConfig, flags);
         dest.writeTypedObject(mOverrideConfig, flags);
         dest.writeInt(mDeviceId);
@@ -200,28 +263,40 @@
         dest.writeStrongBinder(mTaskFragmentToken);
         dest.writeStrongBinder(mInitialCallerInfoAccessToken);
         dest.writeTypedObject(mActivityWindowInfo, flags);
+
+        dest.writeTypedObject(mIntent, flags);
+        dest.writeTypedObject(mInfo, flags);
     }
 
-    /** Read from Parcel. */
+    /** Reads from Parcel. */
     private LaunchActivityItem(@NonNull Parcel in) {
-        setValues(this, in.readStrongBinder(), in.readTypedObject(Intent.CREATOR), in.readInt(),
-                in.readTypedObject(ActivityInfo.CREATOR), in.readTypedObject(Configuration.CREATOR),
-                in.readTypedObject(Configuration.CREATOR), in.readInt(), in.readString(),
-                IVoiceInteractor.Stub.asInterface(in.readStrongBinder()), in.readInt(),
-                in.readBundle(getClass().getClassLoader()),
-                in.readPersistableBundle(getClass().getClassLoader()),
-                in.createTypedArrayList(ResultInfo.CREATOR),
-                in.createTypedArrayList(ReferrerIntent.CREATOR),
-                in.readTypedObject(SceneTransitionInfo.CREATOR),
-                in.readBoolean(),
-                in.readTypedObject(ProfilerInfo.CREATOR),
-                in.readStrongBinder(),
-                IActivityClientController.Stub.asInterface(in.readStrongBinder()),
-                in.readStrongBinder(),
-                in.readBoolean(),
-                in.readStrongBinder(),
-                in.readStrongBinder(),
-                in.readTypedObject(ActivityWindowInfo.CREATOR));
+        this(in.readStrongBinder() /* activityToken */,
+                in.readInt() /* ident */,
+                requireNonNull(in.readTypedObject(Configuration.CREATOR)) /* curConfig */,
+                requireNonNull(in.readTypedObject(Configuration.CREATOR)) /* overrideConfig */,
+                in.readInt() /* deviceId */,
+                in.readString() /* referrer */,
+                IVoiceInteractor.Stub.asInterface(in.readStrongBinder()) /* voiceInteractor */,
+                in.readInt() /* procState */,
+                in.readBundle(in.getClass().getClassLoader()) /* state */,
+                in.readPersistableBundle(in.getClass().getClassLoader()) /* persistentState */,
+                in.createTypedArrayList(ResultInfo.CREATOR) /* pendingResults */,
+                in.createTypedArrayList(ReferrerIntent.CREATOR) /* pendingNewIntents */,
+                in.readTypedObject(SceneTransitionInfo.CREATOR) /* sceneTransitionInfo */,
+                in.readBoolean() /* isForward */,
+                in.readTypedObject(ProfilerInfo.CREATOR) /* profilerInfo */,
+                in.readStrongBinder() /* assistToken */,
+                IActivityClientController.Stub.asInterface(
+                        in.readStrongBinder()) /* activityClientController */,
+                in.readStrongBinder() /* shareableActivityToken */,
+                in.readBoolean() /* launchedFromBubble */,
+                in.readStrongBinder() /* taskFragmentToken */,
+                in.readStrongBinder() /* initialCallerInfoAccessToken */,
+                requireNonNull(in.readTypedObject(ActivityWindowInfo.CREATOR))
+                /* activityWindowInfo */
+        );
+        mIntent = in.readTypedObject(Intent.CREATOR);
+        mInfo = in.readTypedObject(ActivityInfo.CREATOR);
     }
 
     public static final @NonNull Creator<LaunchActivityItem> CREATOR = new Creator<>() {
@@ -339,45 +414,4 @@
                 + ",activityWindowInfo=" + mActivityWindowInfo
                 + "}";
     }
-
-    // Using the same method to set and clear values to make sure we don't forget anything
-    private static void setValues(@Nullable LaunchActivityItem instance,
-            @Nullable IBinder activityToken, @Nullable Intent intent, int ident,
-            @Nullable ActivityInfo info, @Nullable Configuration curConfig,
-            @Nullable Configuration overrideConfig, int deviceId,
-            @Nullable String referrer, @Nullable IVoiceInteractor voiceInteractor,
-            int procState, @Nullable Bundle state, @Nullable PersistableBundle persistentState,
-            @Nullable List<ResultInfo> pendingResults,
-            @Nullable List<ReferrerIntent> pendingNewIntents,
-            @Nullable SceneTransitionInfo sceneTransitionInfo, boolean isForward,
-            @Nullable ProfilerInfo profilerInfo, @Nullable IBinder assistToken,
-            @Nullable IActivityClientController activityClientController,
-            @Nullable IBinder shareableActivityToken, boolean launchedFromBubble,
-            @Nullable IBinder taskFragmentToken, @Nullable IBinder initialCallerInfoAccessToken,
-            @Nullable ActivityWindowInfo activityWindowInfo) {
-        instance.mActivityToken = activityToken;
-        instance.mIntent = intent;
-        instance.mIdent = ident;
-        instance.mInfo = info;
-        instance.mCurConfig = curConfig;
-        instance.mOverrideConfig = overrideConfig;
-        instance.mDeviceId = deviceId;
-        instance.mReferrer = referrer;
-        instance.mVoiceInteractor = voiceInteractor;
-        instance.mProcState = procState;
-        instance.mState = state;
-        instance.mPersistentState = persistentState;
-        instance.mPendingResults = pendingResults;
-        instance.mPendingNewIntents = pendingNewIntents;
-        instance.mSceneTransitionInfo = sceneTransitionInfo;
-        instance.mIsForward = isForward;
-        instance.mProfilerInfo = profilerInfo;
-        instance.mAssistToken = assistToken;
-        instance.mActivityClientController = activityClientController;
-        instance.mShareableActivityToken = shareableActivityToken;
-        instance.mLaunchedFromBubble = launchedFromBubble;
-        instance.mTaskFragmentToken = taskFragmentToken;
-        instance.mInitialCallerInfoAccessToken = initialCallerInfoAccessToken;
-        instance.mActivityWindowInfo = activityWindowInfo;
-    }
 }
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index 8706edd..1aa563a 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -18,6 +18,8 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread.ActivityClientRecord;
@@ -33,13 +35,26 @@
 
 /**
  * Activity move to a different display message.
+ *
  * @hide
  */
 public class MoveToDisplayItem extends ActivityTransactionItem {
 
-    private int mTargetDisplayId;
-    private Configuration mConfiguration;
-    private ActivityWindowInfo mActivityWindowInfo;
+    private final int mTargetDisplayId;
+
+    @NonNull
+    private final Configuration mConfiguration;
+
+    @NonNull
+    private final ActivityWindowInfo mActivityWindowInfo;
+
+    public MoveToDisplayItem(@NonNull IBinder activityToken, int targetDisplayId,
+            @NonNull Configuration configuration, @NonNull ActivityWindowInfo activityWindowInfo) {
+        super(activityToken);
+        mTargetDisplayId = targetDisplayId;
+        mConfiguration = new Configuration(configuration);
+        mActivityWindowInfo = new ActivityWindowInfo(activityWindowInfo);
+    }
 
     @Override
     public void preExecute(@NonNull ClientTransactionHandler client) {
@@ -58,38 +73,9 @@
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
-    // ObjectPoolItem implementation
-
-    private MoveToDisplayItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    @NonNull
-    public static MoveToDisplayItem obtain(@NonNull IBinder activityToken, int targetDisplayId,
-            @NonNull Configuration configuration, @NonNull ActivityWindowInfo activityWindowInfo) {
-        MoveToDisplayItem instance = ObjectPool.obtain(MoveToDisplayItem.class);
-        if (instance == null) {
-            instance = new MoveToDisplayItem();
-        }
-        instance.setActivityToken(activityToken);
-        instance.mTargetDisplayId = targetDisplayId;
-        instance.mConfiguration = new Configuration(configuration);
-        instance.mActivityWindowInfo = new ActivityWindowInfo(activityWindowInfo);
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        mTargetDisplayId = 0;
-        mConfiguration = null;
-        mActivityWindowInfo = null;
-        ObjectPool.recycle(this);
-    }
-
     // Parcelable implementation
 
-    /** Write to Parcel. */
+    /** Writes to Parcel. */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
@@ -98,12 +84,12 @@
         dest.writeTypedObject(mActivityWindowInfo, flags);
     }
 
-    /** Read from Parcel. */
+    /** Reads from Parcel. */
     private MoveToDisplayItem(@NonNull Parcel in) {
         super(in);
         mTargetDisplayId = in.readInt();
-        mConfiguration = in.readTypedObject(Configuration.CREATOR);
-        mActivityWindowInfo = in.readTypedObject(ActivityWindowInfo.CREATOR);
+        mConfiguration = requireNonNull(in.readTypedObject(Configuration.CREATOR));
+        mActivityWindowInfo = requireNonNull(in.readTypedObject(ActivityWindowInfo.CREATOR));
     }
 
     public static final @NonNull Creator<MoveToDisplayItem> CREATOR = new Creator<>() {
diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java
index acf2ea4..b5e9d66 100644
--- a/core/java/android/app/servertransaction/NewIntentItem.java
+++ b/core/java/android/app/servertransaction/NewIntentItem.java
@@ -38,13 +38,24 @@
 
 /**
  * New intent message.
+ *
  * @hide
  */
 public class NewIntentItem extends ActivityTransactionItem {
 
+    // TODO(b/170729553): Mark this with @NonNull and final once @UnsupportedAppUsage removed.
+    //  We cannot do it now to avoid app compatibility regression.
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private List<ReferrerIntent> mIntents;
-    private boolean mResume;
+
+    private final boolean mResume;
+
+    public NewIntentItem(@NonNull IBinder activityToken,
+            @NonNull List<ReferrerIntent> intents, boolean resume) {
+        super(activityToken);
+        mIntents = new ArrayList<>(intents);
+        mResume = resume;
+    }
 
     @Override
     public int getPostExecutionState() {
@@ -59,36 +70,9 @@
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
-    // ObjectPoolItem implementation
-
-    private NewIntentItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    @NonNull
-    public static NewIntentItem obtain(@NonNull IBinder activityToken,
-            @NonNull List<ReferrerIntent> intents, boolean resume) {
-        NewIntentItem instance = ObjectPool.obtain(NewIntentItem.class);
-        if (instance == null) {
-            instance = new NewIntentItem();
-        }
-        instance.setActivityToken(activityToken);
-        instance.mIntents = new ArrayList<>(intents);
-        instance.mResume = resume;
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        mIntents = null;
-        mResume = false;
-        ObjectPool.recycle(this);
-    }
-
     // Parcelable implementation
 
-    /** Write to Parcel. */
+    /** Writes to Parcel. */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
@@ -96,10 +80,11 @@
         dest.writeTypedList(mIntents, flags);
     }
 
-    /** Read from Parcel. */
+    /** Reads from Parcel. */
     private NewIntentItem(@NonNull Parcel in) {
         super(in);
         mResume = in.readBoolean();
+        // TODO(b/170729553): Wrap with requireNonNull once @UnsupportedAppUsage removed.
         mIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
     }
 
diff --git a/core/java/android/app/servertransaction/ObjectPool.java b/core/java/android/app/servertransaction/ObjectPool.java
deleted file mode 100644
index e86ca37..0000000
--- a/core/java/android/app/servertransaction/ObjectPool.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.servertransaction;
-
-/**
- * An object pool that can provide reused objects if available.
- *
- * @hide
- * @deprecated This class is deprecated. Directly create new instances of objects instead of
- * obtaining them from this pool.
- * TODO(b/311089192): Clean up usages of the pool.
- */
-@Deprecated
-class ObjectPool {
-
-    /**
-     * Obtain an instance of a specific class from the pool
-     *
-     * @param ignoredItemClass The class of the object we're looking for.
-     * @return An instance or null if there is none.
-     * @deprecated This method is deprecated. Directly create new instances of objects instead of
-     * obtaining them from this pool.
-     */
-    @Deprecated
-    public static <T extends ObjectPoolItem> T obtain(Class<T> ignoredItemClass) {
-        return null;
-    }
-
-    /**
-     * Recycle the object to the pool. The object should be properly cleared before this.
-     *
-     * @param ignoredItem The object to recycle.
-     * @see ObjectPoolItem#recycle()
-     * @deprecated This method is deprecated. The object pool is no longer used, so there's
-     * no need to recycle objects.
-     */
-    @Deprecated
-    public static <T extends ObjectPoolItem> void recycle(T ignoredItem) {
-    }
-}
diff --git a/core/java/android/app/servertransaction/ObjectPoolItem.java b/core/java/android/app/servertransaction/ObjectPoolItem.java
deleted file mode 100644
index 0141f6e..0000000
--- a/core/java/android/app/servertransaction/ObjectPoolItem.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.servertransaction;
-
-/**
- * Base interface for all lifecycle items that can be put in object pool.
- *
- * @hide
- * @deprecated This interface is deprecated. Objects should no longer be pooled.
- * TODO(b/311089192): Clean up usages of this interface.
- */
-@Deprecated
-public interface ObjectPoolItem {
-    /**
-     * Clear the contents of the item and putting it to a pool. The implementation should call
-     * {@link ObjectPool#recycle(ObjectPoolItem)} passing itself.
-     *
-     * @deprecated This method is deprecated. The object pool is no longer used, so there's
-     * no need to recycle objects.
-     */
-    @Deprecated
-    void recycle();
-}
diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java
index d230284..09fc51b 100644
--- a/core/java/android/app/servertransaction/PauseActivityItem.java
+++ b/core/java/android/app/servertransaction/PauseActivityItem.java
@@ -29,16 +29,29 @@
 
 /**
  * Request to move an activity to paused state.
+ *
  * @hide
  */
 public class PauseActivityItem extends ActivityLifecycleItem {
 
-    private static final String TAG = "PauseActivityItem";
+    private final boolean mFinished;
+    private final boolean mUserLeaving;
+    private final boolean mDontReport;
+    private final boolean mAutoEnteringPip;
 
-    private boolean mFinished;
-    private boolean mUserLeaving;
-    private boolean mDontReport;
-    private boolean mAutoEnteringPip;
+    public PauseActivityItem(@NonNull IBinder activityToken) {
+        this(activityToken, false /* finished */, false /* userLeaving */,
+                true /* dontReport */, false /* autoEnteringPip*/);
+    }
+
+    public PauseActivityItem(@NonNull IBinder activityToken, boolean finished,
+            boolean userLeaving, boolean dontReport, boolean autoEnteringPip) {
+        super(activityToken);
+        mFinished = finished;
+        mUserLeaving = userLeaving;
+        mDontReport = dontReport;
+        mAutoEnteringPip = autoEnteringPip;
+    }
 
     @Override
     public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@@ -64,47 +77,9 @@
         ActivityClient.getInstance().activityPaused(getActivityToken());
     }
 
-    // ObjectPoolItem implementation
-
-    private PauseActivityItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    @NonNull
-    public static PauseActivityItem obtain(@NonNull IBinder activityToken, boolean finished,
-            boolean userLeaving, boolean dontReport, boolean autoEnteringPip) {
-        PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class);
-        if (instance == null) {
-            instance = new PauseActivityItem();
-        }
-        instance.setActivityToken(activityToken);
-        instance.mFinished = finished;
-        instance.mUserLeaving = userLeaving;
-        instance.mDontReport = dontReport;
-        instance.mAutoEnteringPip = autoEnteringPip;
-
-        return instance;
-    }
-
-    /** Obtain an instance initialized with default params. */
-    @NonNull
-    public static PauseActivityItem obtain(@NonNull IBinder activityToken) {
-        return obtain(activityToken, false /* finished */, false /* userLeaving */,
-                true /* dontReport */, false /* autoEnteringPip*/);
-    }
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        mFinished = false;
-        mUserLeaving = false;
-        mDontReport = false;
-        mAutoEnteringPip = false;
-        ObjectPool.recycle(this);
-    }
-
     // Parcelable implementation
 
-    /** Write to Parcel. */
+    /** Writes to Parcel. */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
@@ -114,7 +89,7 @@
         dest.writeBoolean(mAutoEnteringPip);
     }
 
-    /** Read from Parcel. */
+    /** Reads from Parcel. */
     private PauseActivityItem(@NonNull Parcel in) {
         super(in);
         mFinished = in.readBoolean();
diff --git a/core/java/android/app/servertransaction/PipStateTransactionItem.java b/core/java/android/app/servertransaction/PipStateTransactionItem.java
index 30289ef..ddeb2c1 100644
--- a/core/java/android/app/servertransaction/PipStateTransactionItem.java
+++ b/core/java/android/app/servertransaction/PipStateTransactionItem.java
@@ -28,11 +28,19 @@
 
 /**
  * Request an activity to enter picture-in-picture mode.
+ *
  * @hide
  */
 public final class PipStateTransactionItem extends ActivityTransactionItem {
 
-    private PictureInPictureUiState mPipState;
+    @NonNull
+    private final PictureInPictureUiState mPipState;
+
+    public PipStateTransactionItem(@NonNull IBinder activityToken,
+            @NonNull PictureInPictureUiState pipState) {
+        super(activityToken);
+        mPipState = pipState;
+    }
 
     @Override
     public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@@ -40,41 +48,16 @@
         client.handlePictureInPictureStateChanged(r, mPipState);
     }
 
-    // ObjectPoolItem implementation
-
-    private PipStateTransactionItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    @NonNull
-    public static PipStateTransactionItem obtain(@NonNull IBinder activityToken,
-            @NonNull PictureInPictureUiState pipState) {
-        PipStateTransactionItem instance = ObjectPool.obtain(PipStateTransactionItem.class);
-        if (instance == null) {
-            instance = new PipStateTransactionItem();
-        }
-        instance.setActivityToken(activityToken);
-        instance.mPipState = pipState;
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        mPipState = null;
-        ObjectPool.recycle(this);
-    }
-
     // Parcelable implementation
 
-    /** Write to Parcel. */
+    /** Writes to Parcel. */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         mPipState.writeToParcel(dest, flags);
     }
 
-    /** Read from Parcel. */
+    /** Reads from Parcel. */
     private PipStateTransactionItem(@NonNull Parcel in) {
         super(in);
         mPipState = PictureInPictureUiState.CREATOR.createFromParcel(in);
diff --git a/core/java/android/app/servertransaction/RefreshCallbackItem.java b/core/java/android/app/servertransaction/RefreshCallbackItem.java
index a3f82e9..57fd97a 100644
--- a/core/java/android/app/servertransaction/RefreshCallbackItem.java
+++ b/core/java/android/app/servertransaction/RefreshCallbackItem.java
@@ -44,7 +44,20 @@
     // Whether refresh should happen using the "stopped -> resumed" cycle or
     // "paused -> resumed" cycle.
     @LifecycleState
-    private int mPostExecutionState;
+    private final int mPostExecutionState;
+
+    /**
+     * Creates a new RefreshCallbackItem.
+     *
+     * @param activityToken      the target client activity.
+     * @param postExecutionState indicating whether refresh should happen using the
+     *                           "stopped -> "resumed" cycle or "paused -> resumed" cycle.
+     */
+    public RefreshCallbackItem(
+            @NonNull IBinder activityToken, @LifecycleState int postExecutionState) {
+        super(activityToken);
+        mPostExecutionState = postExecutionState;
+    }
 
     @Override
     public void execute(@NonNull ClientTransactionHandler client,
@@ -67,47 +80,21 @@
         return false;
     }
 
-    // ObjectPoolItem implementation
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        ObjectPool.recycle(this);
-    }
-
-    /**
-    * Obtain an instance initialized with provided params.
-    * @param postExecutionState indicating whether refresh should happen using the
-    *        "stopped -> resumed" cycle or "paused -> resumed" cycle.
-    */
-    @NonNull
-    public static RefreshCallbackItem obtain(@NonNull IBinder activityToken,
-            @LifecycleState int postExecutionState) {
-        if (postExecutionState != ON_STOP && postExecutionState != ON_PAUSE) {
-            throw new IllegalArgumentException(
-                    "Only ON_STOP or ON_PAUSE are allowed as a post execution state for "
-                            + "RefreshCallbackItem but got " + postExecutionState);
-        }
-        RefreshCallbackItem instance =
-                ObjectPool.obtain(RefreshCallbackItem.class);
-        if (instance == null) {
-            instance = new RefreshCallbackItem();
-        }
-        instance.setActivityToken(activityToken);
-        instance.mPostExecutionState = postExecutionState;
-        return instance;
-    }
-
-    private RefreshCallbackItem() {}
-
     // Parcelable implementation
 
+    /** Writes to Parcel. */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         dest.writeInt(mPostExecutionState);
     }
 
+    /** Reads from Parcel. */
+    private RefreshCallbackItem(@NonNull Parcel in) {
+        super(in);
+        mPostExecutionState = in.readInt();
+    }
+
     @Override
     public boolean equals(@Nullable Object o) {
         if (this == o) {
@@ -134,11 +121,6 @@
                 + ",mPostExecutionState=" + mPostExecutionState + "}";
     }
 
-    private RefreshCallbackItem(@NonNull Parcel in) {
-        super(in);
-        mPostExecutionState = in.readInt();
-    }
-
     public static final @NonNull Creator<RefreshCallbackItem> CREATOR = new Creator<>() {
 
         public RefreshCallbackItem createFromParcel(@NonNull Parcel in) {
diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java
index 4a0ea98..a28791f 100644
--- a/core/java/android/app/servertransaction/ResumeActivityItem.java
+++ b/core/java/android/app/servertransaction/ResumeActivityItem.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityClient;
 import android.app.ActivityManager;
+import android.app.ActivityManager.ProcessState;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
@@ -30,22 +31,37 @@
 
 /**
  * Request to move an activity to resumed state.
+ *
  * @hide
  */
 public class ResumeActivityItem extends ActivityLifecycleItem {
 
-    private static final String TAG = "ResumeActivityItem";
+    @ProcessState
+    private final int mProcState;
 
-    private int mProcState;
-    private boolean mUpdateProcState;
-    private boolean mIsForward;
+    private final boolean mIsForward;
+
     // Whether we should send compat fake focus when the activity is resumed. This is needed
     // because some game engines wait to get focus before drawing the content of the app.
-    private boolean mShouldSendCompatFakeFocus;
+    private final boolean mShouldSendCompatFakeFocus;
+
+    public ResumeActivityItem(@NonNull IBinder activityToken, boolean isForward,
+            boolean shouldSendCompatFakeFocus) {
+        this(activityToken, ActivityManager.PROCESS_STATE_UNKNOWN, isForward,
+                shouldSendCompatFakeFocus);
+    }
+
+    public ResumeActivityItem(@NonNull IBinder activityToken, @ProcessState int procState,
+            boolean isForward, boolean shouldSendCompatFakeFocus) {
+        super(activityToken);
+        mProcState = procState;
+        mIsForward = isForward;
+        mShouldSendCompatFakeFocus = shouldSendCompatFakeFocus;
+    }
 
     @Override
     public void preExecute(@NonNull ClientTransactionHandler client) {
-        if (mUpdateProcState) {
+        if (mProcState != ActivityManager.PROCESS_STATE_UNKNOWN) {
             client.updateProcessState(mProcState, false);
         }
     }
@@ -72,71 +88,21 @@
         return ON_RESUME;
     }
 
-    // ObjectPoolItem implementation
-
-    private ResumeActivityItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    @NonNull
-    public static ResumeActivityItem obtain(@NonNull IBinder activityToken, int procState,
-            boolean isForward, boolean shouldSendCompatFakeFocus) {
-        ResumeActivityItem instance = ObjectPool.obtain(ResumeActivityItem.class);
-        if (instance == null) {
-            instance = new ResumeActivityItem();
-        }
-        instance.setActivityToken(activityToken);
-        instance.mProcState = procState;
-        instance.mUpdateProcState = true;
-        instance.mIsForward = isForward;
-        instance.mShouldSendCompatFakeFocus = shouldSendCompatFakeFocus;
-
-        return instance;
-    }
-
-    /** Obtain an instance initialized with provided params. */
-    @NonNull
-    public static ResumeActivityItem obtain(@NonNull IBinder activityToken, boolean isForward,
-            boolean shouldSendCompatFakeFocus) {
-        ResumeActivityItem instance = ObjectPool.obtain(ResumeActivityItem.class);
-        if (instance == null) {
-            instance = new ResumeActivityItem();
-        }
-        instance.setActivityToken(activityToken);
-        instance.mProcState = ActivityManager.PROCESS_STATE_UNKNOWN;
-        instance.mUpdateProcState = false;
-        instance.mIsForward = isForward;
-        instance.mShouldSendCompatFakeFocus = shouldSendCompatFakeFocus;
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        mProcState = ActivityManager.PROCESS_STATE_UNKNOWN;
-        mUpdateProcState = false;
-        mIsForward = false;
-        mShouldSendCompatFakeFocus = false;
-        ObjectPool.recycle(this);
-    }
-
     // Parcelable implementation
 
-    /** Write to Parcel. */
+    /** Writes to Parcel. */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         dest.writeInt(mProcState);
-        dest.writeBoolean(mUpdateProcState);
         dest.writeBoolean(mIsForward);
         dest.writeBoolean(mShouldSendCompatFakeFocus);
     }
 
-    /** Read from Parcel. */
+    /** Reads from Parcel. */
     private ResumeActivityItem(@NonNull Parcel in) {
         super(in);
         mProcState = in.readInt();
-        mUpdateProcState = in.readBoolean();
         mIsForward = in.readBoolean();
         mShouldSendCompatFakeFocus = in.readBoolean();
     }
@@ -160,7 +126,7 @@
             return false;
         }
         final ResumeActivityItem other = (ResumeActivityItem) o;
-        return mProcState == other.mProcState && mUpdateProcState == other.mUpdateProcState
+        return mProcState == other.mProcState
                 && mIsForward == other.mIsForward
                 && mShouldSendCompatFakeFocus == other.mShouldSendCompatFakeFocus;
     }
@@ -170,7 +136,6 @@
         int result = 17;
         result = 31 * result + super.hashCode();
         result = 31 * result + mProcState;
-        result = 31 * result + (mUpdateProcState ? 1 : 0);
         result = 31 * result + (mIsForward ? 1 : 0);
         result = 31 * result + (mShouldSendCompatFakeFocus ? 1 : 0);
         return result;
@@ -180,7 +145,6 @@
     public String toString() {
         return "ResumeActivityItem{" + super.toString()
                 + ",procState=" + mProcState
-                + ",updateProcState=" + mUpdateProcState
                 + ",isForward=" + mIsForward
                 + ",shouldSendCompatFakeFocus=" + mShouldSendCompatFakeFocus + "}";
     }
diff --git a/core/java/android/app/servertransaction/StartActivityItem.java b/core/java/android/app/servertransaction/StartActivityItem.java
index a0f93ce..6bae92b 100644
--- a/core/java/android/app/servertransaction/StartActivityItem.java
+++ b/core/java/android/app/servertransaction/StartActivityItem.java
@@ -29,13 +29,19 @@
 
 /**
  * Request to move an activity to started and visible state.
+ *
  * @hide
  */
 public class StartActivityItem extends ActivityLifecycleItem {
 
-    private static final String TAG = "StartActivityItem";
+    @Nullable
+    private final SceneTransitionInfo mSceneTransitionInfo;
 
-    private SceneTransitionInfo mSceneTransitionInfo;
+    public StartActivityItem(@NonNull IBinder activityToken,
+            @Nullable SceneTransitionInfo sceneTransitionInfo) {
+        super(activityToken);
+        mSceneTransitionInfo = sceneTransitionInfo;
+    }
 
     @Override
     public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@@ -50,41 +56,16 @@
         return ON_START;
     }
 
-    // ObjectPoolItem implementation
-
-    private StartActivityItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    @NonNull
-    public static StartActivityItem obtain(@NonNull IBinder activityToken,
-            @Nullable SceneTransitionInfo sceneTransitionInfo) {
-        StartActivityItem instance = ObjectPool.obtain(StartActivityItem.class);
-        if (instance == null) {
-            instance = new StartActivityItem();
-        }
-        instance.setActivityToken(activityToken);
-        instance.mSceneTransitionInfo = sceneTransitionInfo;
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        mSceneTransitionInfo = null;
-        ObjectPool.recycle(this);
-    }
-
     // Parcelable implementation
 
-    /** Write to Parcel. */
+    /** Writes to Parcel. */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         dest.writeTypedObject(mSceneTransitionInfo, flags);
     }
 
-    /** Read from Parcel. */
+    /** Reads from Parcel. */
     private StartActivityItem(@NonNull Parcel in) {
         super(in);
         mSceneTransitionInfo = in.readTypedObject(SceneTransitionInfo.CREATOR);
diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java
index def7b3f..012b82e 100644
--- a/core/java/android/app/servertransaction/StopActivityItem.java
+++ b/core/java/android/app/servertransaction/StopActivityItem.java
@@ -27,11 +27,14 @@
 
 /**
  * Request to move an activity to stopped state.
+ *
  * @hide
  */
 public class StopActivityItem extends ActivityLifecycleItem {
 
-    private static final String TAG = "StopActivityItem";
+    public StopActivityItem(@NonNull IBinder activityToken) {
+        super(activityToken);
+    }
 
     @Override
     public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@@ -53,34 +56,9 @@
         return ON_STOP;
     }
 
-    // ObjectPoolItem implementation
-
-    private StopActivityItem() {}
-
-    /**
-     * Obtain an instance initialized with provided params.
-     * @param activityToken the activity that stops.
-     */
-    @NonNull
-    public static StopActivityItem obtain(@NonNull IBinder activityToken) {
-        StopActivityItem instance = ObjectPool.obtain(StopActivityItem.class);
-        if (instance == null) {
-            instance = new StopActivityItem();
-        }
-        instance.setActivityToken(activityToken);
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        ObjectPool.recycle(this);
-    }
-
     // Parcelable implementation
 
-    /** Read from Parcel. */
+    /** Reads from Parcel. */
     private StopActivityItem(@NonNull Parcel in) {
         super(in);
     }
diff --git a/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
index 23d4505..b5f8345 100644
--- a/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
+++ b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.app.servertransaction;
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
@@ -28,11 +29,17 @@
 
 /**
  * Top resumed activity changed callback.
+ *
  * @hide
  */
 public class TopResumedActivityChangeItem extends ActivityTransactionItem {
 
-    private boolean mOnTop;
+    private final boolean mOnTop;
+
+    public TopResumedActivityChangeItem(@NonNull IBinder activityToken, boolean onTop) {
+        super(activityToken);
+        mOnTop = onTop;
+    }
 
     @Override
     public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@@ -58,42 +65,16 @@
         ActivityClient.getInstance().activityTopResumedStateLost();
     }
 
-    // ObjectPoolItem implementation
-
-    private TopResumedActivityChangeItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    @NonNull
-    public static TopResumedActivityChangeItem obtain(@NonNull IBinder activityToken,
-            boolean onTop) {
-        TopResumedActivityChangeItem instance =
-                ObjectPool.obtain(TopResumedActivityChangeItem.class);
-        if (instance == null) {
-            instance = new TopResumedActivityChangeItem();
-        }
-        instance.setActivityToken(activityToken);
-        instance.mOnTop = onTop;
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        mOnTop = false;
-        ObjectPool.recycle(this);
-    }
-
     // Parcelable implementation
 
-    /** Write to Parcel. */
+    /** Writes to Parcel. */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         dest.writeBoolean(mOnTop);
     }
 
-    /** Read from Parcel. */
+    /** Reads from Parcel. */
     private TopResumedActivityChangeItem(@NonNull Parcel in) {
         super(in);
         mOnTop = in.readBoolean();
@@ -131,7 +112,6 @@
 
     @Override
     public String toString() {
-        return "TopResumedActivityChangeItem{" + super.toString()
-                + ",onTop=" + mOnTop + "}";
+        return "TopResumedActivityChangeItem{" + super.toString() + ",onTop=" + mOnTop + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 68012e2..3a23e6b 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -45,6 +45,7 @@
 
 /**
  * Class that manages transaction execution in the correct order.
+ *
  * @hide
  */
 public class TransactionExecutor {
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 9f622e9..785fa59 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -42,6 +42,7 @@
 
 /**
  * Helper class for {@link TransactionExecutor} that contains utils for lifecycle path resolution.
+ *
  * @hide
  */
 public class TransactionExecutorHelper {
@@ -54,7 +55,7 @@
     // Temp holder for lifecycle path.
     // No direct transition between two states should take more than one complete cycle of 6 states.
     @ActivityLifecycleItem.LifecycleState
-    private IntArray mLifecycleSequence = new IntArray(6);
+    private final IntArray mLifecycleSequence = new IntArray(6 /* initialCapacity */);
 
     /**
      * Calculate the path through main lifecycle states for an activity and fill
@@ -197,13 +198,13 @@
                 // Fall through to return the PAUSE item to ensure the activity is properly
                 // resumed while relaunching.
             case ON_PAUSE:
-                lifecycleItem = PauseActivityItem.obtain(r.token);
+                lifecycleItem = new PauseActivityItem(r.token);
                 break;
             case ON_STOP:
-                lifecycleItem = StopActivityItem.obtain(r.token);
+                lifecycleItem = new StopActivityItem(r.token);
                 break;
             default:
-                lifecycleItem = ResumeActivityItem.obtain(r.token, false /* isForward */,
+                lifecycleItem = new ResumeActivityItem(r.token, false /* isForward */,
                         false /* shouldSendCompatFakeFocus */);
                 break;
         }
diff --git a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
index 11947e9..5068c39 100644
--- a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
+++ b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
@@ -29,12 +29,24 @@
 
 /**
  * Transfer a splash screen view to an Activity.
+ *
  * @hide
  */
 public class TransferSplashScreenViewStateItem extends ActivityTransactionItem {
 
-    private SplashScreenViewParcelable mSplashScreenViewParcelable;
-    private SurfaceControl mStartingWindowLeash;
+    @Nullable
+    private final SplashScreenViewParcelable mSplashScreenViewParcelable;
+
+    @Nullable
+    private final SurfaceControl mStartingWindowLeash;
+
+    public TransferSplashScreenViewStateItem(@NonNull IBinder activityToken,
+            @Nullable SplashScreenViewParcelable parcelable,
+            @Nullable SurfaceControl startingWindowLeash) {
+        super(activityToken);
+        mSplashScreenViewParcelable = parcelable;
+        mStartingWindowLeash = startingWindowLeash;
+    }
 
     @Override
     public void execute(@NonNull ClientTransactionHandler client,
@@ -43,14 +55,9 @@
         client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable, mStartingWindowLeash);
     }
 
-    @Override
-    public void recycle() {
-        super.recycle();
-        mSplashScreenViewParcelable = null;
-        mStartingWindowLeash = null;
-        ObjectPool.recycle(this);
-    }
+    // Parcelable implementation
 
+    /** Writes to Parcel. */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
@@ -58,31 +65,13 @@
         dest.writeTypedObject(mStartingWindowLeash, flags);
     }
 
-    private TransferSplashScreenViewStateItem() {}
-
+    /** Reads from Parcel. */
     private TransferSplashScreenViewStateItem(@NonNull Parcel in) {
         super(in);
         mSplashScreenViewParcelable = in.readTypedObject(SplashScreenViewParcelable.CREATOR);
         mStartingWindowLeash = in.readTypedObject(SurfaceControl.CREATOR);
     }
 
-    /** Obtain an instance initialized with provided params. */
-    @NonNull
-    public static TransferSplashScreenViewStateItem obtain(
-            @NonNull IBinder activityToken, @Nullable SplashScreenViewParcelable parcelable,
-            @Nullable SurfaceControl startingWindowLeash) {
-        TransferSplashScreenViewStateItem instance =
-                ObjectPool.obtain(TransferSplashScreenViewStateItem.class);
-        if (instance == null) {
-            instance = new TransferSplashScreenViewStateItem();
-        }
-        instance.setActivityToken(activityToken);
-        instance.mSplashScreenViewParcelable = parcelable;
-        instance.mStartingWindowLeash = startingWindowLeash;
-
-        return instance;
-    }
-
     public static final @NonNull Creator<TransferSplashScreenViewStateItem> CREATOR =
             new Creator<>() {
                 public TransferSplashScreenViewStateItem createFromParcel(@NonNull Parcel in) {
diff --git a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
index f6a7291..b2e87bd 100644
--- a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
+++ b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
@@ -30,14 +30,22 @@
 
 /**
  * {@link android.window.WindowContext} configuration change message.
+ *
  * @hide
  */
 public class WindowContextInfoChangeItem extends ClientTransactionItem {
 
-    @Nullable
-    private IBinder mClientToken;
-    @Nullable
-    private WindowContextInfo mInfo;
+    @NonNull
+    private final IBinder mClientToken;
+
+    @NonNull
+    private final WindowContextInfo mInfo;
+
+    public WindowContextInfoChangeItem(
+            @NonNull IBinder clientToken, @NonNull Configuration config, int displayId) {
+        mClientToken = requireNonNull(clientToken);
+        mInfo = new WindowContextInfo(new Configuration(config), displayId);
+    }
 
     @Override
     public void execute(@NonNull ClientTransactionHandler client,
@@ -45,31 +53,6 @@
         client.handleWindowContextInfoChanged(mClientToken, mInfo);
     }
 
-    // ObjectPoolItem implementation
-
-    private WindowContextInfoChangeItem() {}
-
-    /** Obtains an instance initialized with provided params. */
-    public static WindowContextInfoChangeItem obtain(
-            @NonNull IBinder clientToken, @NonNull Configuration config, int displayId) {
-        WindowContextInfoChangeItem instance =
-                ObjectPool.obtain(WindowContextInfoChangeItem.class);
-        if (instance == null) {
-            instance = new WindowContextInfoChangeItem();
-        }
-        instance.mClientToken = requireNonNull(clientToken);
-        instance.mInfo = new WindowContextInfo(new Configuration(config), displayId);
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        mClientToken = null;
-        mInfo = null;
-        ObjectPool.recycle(this);
-    }
-
     // Parcelable implementation
 
     /** Writes to Parcel. */
@@ -82,7 +65,7 @@
     /** Reads from Parcel. */
     private WindowContextInfoChangeItem(@NonNull Parcel in) {
         mClientToken = in.readStrongBinder();
-        mInfo = in.readTypedObject(WindowContextInfo.CREATOR);
+        mInfo = requireNonNull(in.readTypedObject(WindowContextInfo.CREATOR));
     }
 
     public static final @NonNull Creator<WindowContextInfoChangeItem> CREATOR =
diff --git a/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java b/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java
index 1bea468..76b39d5 100644
--- a/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java
+++ b/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java
@@ -28,12 +28,17 @@
 
 /**
  * {@link android.window.WindowContext} window removal message.
+ *
  * @hide
  */
 public class WindowContextWindowRemovalItem extends ClientTransactionItem {
 
-    @Nullable
-    private IBinder mClientToken;
+    @NonNull
+    private final IBinder mClientToken;
+
+    public WindowContextWindowRemovalItem(@NonNull IBinder clientToken) {
+        mClientToken = requireNonNull(clientToken);
+    }
 
     @Override
     public void execute(@NonNull ClientTransactionHandler client,
@@ -41,28 +46,6 @@
         client.handleWindowContextWindowRemoval(mClientToken);
     }
 
-    // ObjectPoolItem implementation
-
-    private WindowContextWindowRemovalItem() {}
-
-    /** Obtains an instance initialized with provided params. */
-    public static WindowContextWindowRemovalItem obtain(@NonNull IBinder clientToken) {
-        WindowContextWindowRemovalItem instance =
-                ObjectPool.obtain(WindowContextWindowRemovalItem.class);
-        if (instance == null) {
-            instance = new WindowContextWindowRemovalItem();
-        }
-        instance.mClientToken = requireNonNull(clientToken);
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        mClientToken = null;
-        ObjectPool.recycle(this);
-    }
-
     // Parcelable implementation
 
     /** Writes to Parcel. */
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 5a39702..ce241c1 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -337,3 +337,13 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "private_space_search_illustration_config"
+  namespace: "profile_experiences"
+  description: "Check config to show/hide the private space search illustration and search tile content in Hide Private Space settings page"
+  bug: "346612477"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 811a834..7353dde 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -37,6 +37,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -470,6 +471,9 @@
         // Set to PowerManager.BRIGHTNESS_INVALID if there's no override.
         public float screenBrightnessOverride;
 
+        // Tag used to identify the app window requesting the brightness override.
+        public CharSequence screenBrightnessOverrideTag;
+
         // An override of the screen auto-brightness adjustment factor in the range -1 (dimmer) to
         // 1 (brighter). Set to Float.NaN if there's no override.
         public float screenAutoBrightnessAdjustmentOverride;
@@ -524,6 +528,7 @@
             policy = other.policy;
             useProximitySensor = other.useProximitySensor;
             screenBrightnessOverride = other.screenBrightnessOverride;
+            screenBrightnessOverrideTag = other.screenBrightnessOverrideTag;
             screenAutoBrightnessAdjustmentOverride = other.screenAutoBrightnessAdjustmentOverride;
             screenLowPowerBrightnessFactor = other.screenLowPowerBrightnessFactor;
             blockScreenOn = other.blockScreenOn;
@@ -544,8 +549,9 @@
             return other != null
                     && policy == other.policy
                     && useProximitySensor == other.useProximitySensor
-                    && floatEquals(screenBrightnessOverride,
-                            other.screenBrightnessOverride)
+                    && floatEquals(screenBrightnessOverride, other.screenBrightnessOverride)
+                    && Objects.equals(screenBrightnessOverrideTag,
+                            other.screenBrightnessOverrideTag)
                     && floatEquals(screenAutoBrightnessAdjustmentOverride,
                             other.screenAutoBrightnessAdjustmentOverride)
                     && screenLowPowerBrightnessFactor
diff --git a/core/java/android/os/CombinedVibration.java b/core/java/android/os/CombinedVibration.java
index f32a1f8..77d6cb7 100644
--- a/core/java/android/os/CombinedVibration.java
+++ b/core/java/android/os/CombinedVibration.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.TestApi;
+import android.os.vibrator.Flags;
 import android.util.SparseArray;
 
 import com.android.internal.util.Preconditions;
@@ -152,6 +153,9 @@
     /** @hide */
     public abstract boolean hasVibrator(int vibratorId);
 
+    /** @hide */
+    public abstract boolean hasVendorEffects();
+
     /**
      * Returns a compact version of the {@link #toString()} result for debugging purposes.
      *
@@ -424,6 +428,15 @@
             return true;
         }
 
+        /** @hide */
+        @Override
+        public boolean hasVendorEffects() {
+            if (!Flags.vendorVibrationEffects()) {
+                return false;
+            }
+            return mEffect instanceof VibrationEffect.VendorEffect;
+        }
+
         @Override
         public boolean equals(Object o) {
             if (this == o) {
@@ -605,6 +618,20 @@
             return mEffects.indexOfKey(vibratorId) >= 0;
         }
 
+        /** @hide */
+        @Override
+        public boolean hasVendorEffects() {
+            if (!Flags.vendorVibrationEffects()) {
+                return false;
+            }
+            for (int i = 0; i < mEffects.size(); i++) {
+                if (mEffects.get(i) instanceof VibrationEffect.VendorEffect) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
         @Override
         public boolean equals(Object o) {
             if (this == o) {
@@ -838,6 +865,17 @@
             return false;
         }
 
+        /** @hide */
+        @Override
+        public boolean hasVendorEffects() {
+            for (int i = 0; i < mEffects.size(); i++) {
+                if (mEffects.get(i).hasVendorEffects()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
         @Override
         public boolean equals(Object o) {
             if (this == o) {
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index df353e5..ce3156e 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -104,8 +104,10 @@
      * This method must only be called by the window manager.
      *
      * @param brightness The overridden brightness, or Float.NaN to disable the override.
+     * @param tag Source identifier of the app window that requests the override.
      */
-    public abstract void setScreenBrightnessOverrideFromWindowManager(float brightness);
+    public abstract void setScreenBrightnessOverrideFromWindowManager(
+            float brightness, CharSequence tag);
 
     /**
      * Used by the window manager to override the user activity timeout based on the
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index efbd96b..44edf29 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -16,18 +16,25 @@
 
 package android.os;
 
+import static android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS;
+
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.hardware.vibrator.IVibrator;
 import android.hardware.vibrator.V1_0.EffectStrength;
 import android.hardware.vibrator.V1_3.Effect;
 import android.net.Uri;
+import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.RampSegment;
@@ -46,6 +53,7 @@
 import java.util.Locale;
 import java.util.Objects;
 import java.util.StringJoiner;
+import java.util.function.BiFunction;
 
 /**
  * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
@@ -53,6 +61,9 @@
  * <p>These effects may be any number of things, from single shot vibrations to complex waveforms.
  */
 public abstract class VibrationEffect implements Parcelable {
+    private static final int PARCEL_TOKEN_COMPOSED = 1;
+    private static final int PARCEL_TOKEN_VENDOR_EFFECT = 2;
+
     // Stevens' coefficient to scale the perceived vibration intensity.
     private static final float SCALE_GAMMA = 0.65f;
     // If a vibration is playing for longer than 1s, it's probably not haptic feedback
@@ -316,6 +327,28 @@
     }
 
     /**
+     * Create a vendor-defined vibration effect.
+     *
+     * <p>Vendor effects offer more flexibility for accessing vendor-specific vibrator capabilities,
+     * enabling control over any vibration parameter and more generic vibration waveforms for apps
+     * provided by the device vendor.
+     *
+     * <p>This requires hardware-specific implementation of the effect and will not have any
+     * platform fallback support.
+     *
+     * @param effect An opaque representation of the vibration effect which can also be serialized.
+     * @return The desired effect.
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    @FlaggedApi(FLAG_VENDOR_VIBRATION_EFFECTS)
+    @RequiresPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)
+    public static VibrationEffect createVendorEffect(@NonNull PersistableBundle effect) {
+        return new VendorEffect(effect, VendorEffect.DEFAULT_STRENGTH, VendorEffect.DEFAULT_SCALE);
+    }
+
+    /**
      * Get a predefined vibration effect.
      *
      * <p>Predefined effects are a set of common vibration effects that should be identical,
@@ -508,7 +541,7 @@
      * Gets the estimated duration of the vibration in milliseconds.
      *
      * <p>For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
-     * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
+     * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. predefined effects where
      * the length is device and potentially run-time dependent), this returns -1.
      *
      * @hide
@@ -550,7 +583,19 @@
      *
      * @hide
      */
-    public abstract <T extends VibrationEffect> T resolve(int defaultAmplitude);
+    @NonNull
+    public abstract VibrationEffect resolve(int defaultAmplitude);
+
+    /**
+     * Applies given effect strength to predefined and vendor-specific effects.
+     *
+     * @param effectStrength new effect strength to be applied, one of
+     *                       VibrationEffect.EFFECT_STRENGTH_*.
+     * @return this if there is no change, or a copy of this effect with new strength otherwise
+     * @hide
+     */
+    @NonNull
+    public abstract VibrationEffect applyEffectStrength(int effectStrength);
 
     /**
      * Scale the vibration effect intensity with the given constraints.
@@ -562,7 +607,20 @@
      *
      * @hide
      */
-    public abstract <T extends VibrationEffect> T scale(float scaleFactor);
+    @NonNull
+    public abstract VibrationEffect scale(float scaleFactor);
+
+    /**
+     * Performs a linear scaling on the effect intensity with the given factor.
+     *
+     * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
+     *                    scale down the intensity, values larger than 1 will scale up
+     * @return this if there is no scaling to be done, or a copy of this effect with scaled
+     *         vibration intensity otherwise
+     * @hide
+     */
+    @NonNull
+    public abstract VibrationEffect scaleLinearly(float scaleFactor);
 
     /**
      * Ensures that the effect is repeating indefinitely or not. This is a lossy operation and
@@ -651,38 +709,26 @@
 
     /** @hide */
     public static String effectIdToString(int effectId) {
-        switch (effectId) {
-            case EFFECT_CLICK:
-                return "CLICK";
-            case EFFECT_TICK:
-                return "TICK";
-            case EFFECT_HEAVY_CLICK:
-                return "HEAVY_CLICK";
-            case EFFECT_DOUBLE_CLICK:
-                return "DOUBLE_CLICK";
-            case EFFECT_POP:
-                return "POP";
-            case EFFECT_THUD:
-                return "THUD";
-            case EFFECT_TEXTURE_TICK:
-                return "TEXTURE_TICK";
-            default:
-                return Integer.toString(effectId);
-        }
+        return switch (effectId) {
+            case EFFECT_CLICK -> "CLICK";
+            case EFFECT_TICK -> "TICK";
+            case EFFECT_HEAVY_CLICK -> "HEAVY_CLICK";
+            case EFFECT_DOUBLE_CLICK -> "DOUBLE_CLICK";
+            case EFFECT_POP -> "POP";
+            case EFFECT_THUD -> "THUD";
+            case EFFECT_TEXTURE_TICK -> "TEXTURE_TICK";
+            default -> Integer.toString(effectId);
+        };
     }
 
     /** @hide */
     public static String effectStrengthToString(int effectStrength) {
-        switch (effectStrength) {
-            case EFFECT_STRENGTH_LIGHT:
-                return "LIGHT";
-            case EFFECT_STRENGTH_MEDIUM:
-                return "MEDIUM";
-            case EFFECT_STRENGTH_STRONG:
-                return "STRONG";
-            default:
-                return Integer.toString(effectStrength);
-        }
+        return switch (effectStrength) {
+            case EFFECT_STRENGTH_LIGHT -> "LIGHT";
+            case EFFECT_STRENGTH_MEDIUM -> "MEDIUM";
+            case EFFECT_STRENGTH_STRONG -> "STRONG";
+            default -> Integer.toString(effectStrength);
+        };
     }
 
     /**
@@ -712,12 +758,15 @@
         private final ArrayList<VibrationEffectSegment> mSegments;
         private final int mRepeatIndex;
 
+        /** @hide */
         Composed(@NonNull Parcel in) {
-            this(in.readArrayList(
-                    VibrationEffectSegment.class.getClassLoader(), VibrationEffectSegment.class),
+            this(Objects.requireNonNull(in.readArrayList(
+                            VibrationEffectSegment.class.getClassLoader(),
+                            VibrationEffectSegment.class)),
                     in.readInt());
         }
 
+        /** @hide */
         Composed(@NonNull VibrationEffectSegment segment) {
             this(Arrays.asList(segment), /* repeatIndex= */ -1);
         }
@@ -844,7 +893,7 @@
             }
             int segmentCount = mSegments.size();
             if (segmentCount > MAX_HAPTIC_FEEDBACK_COMPOSITION_SIZE) {
-                // Vibration has some prebaked or primitive constants, it should be limited to the
+                // Vibration has some predefined or primitive constants, it should be limited to the
                 // max composition size used to classify haptic feedbacks.
                 return false;
             }
@@ -867,34 +916,28 @@
         @NonNull
         @Override
         public Composed resolve(int defaultAmplitude) {
-            int segmentCount = mSegments.size();
-            ArrayList<VibrationEffectSegment> resolvedSegments = new ArrayList<>(segmentCount);
-            for (int i = 0; i < segmentCount; i++) {
-                resolvedSegments.add(mSegments.get(i).resolve(defaultAmplitude));
-            }
-            if (resolvedSegments.equals(mSegments)) {
-                return this;
-            }
-            Composed resolved = new Composed(resolvedSegments, mRepeatIndex);
-            resolved.validate();
-            return resolved;
+            return applyToSegments(VibrationEffectSegment::resolve, defaultAmplitude);
+        }
+
+        /** @hide */
+        @NonNull
+        @Override
+        public VibrationEffect applyEffectStrength(int effectStrength) {
+            return applyToSegments(VibrationEffectSegment::applyEffectStrength, effectStrength);
         }
 
         /** @hide */
         @NonNull
         @Override
         public Composed scale(float scaleFactor) {
-            int segmentCount = mSegments.size();
-            ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount);
-            for (int i = 0; i < segmentCount; i++) {
-                scaledSegments.add(mSegments.get(i).scale(scaleFactor));
-            }
-            if (scaledSegments.equals(mSegments)) {
-                return this;
-            }
-            Composed scaled = new Composed(scaledSegments, mRepeatIndex);
-            scaled.validate();
-            return scaled;
+            return applyToSegments(VibrationEffectSegment::scale, scaleFactor);
+        }
+
+        /** @hide */
+        @NonNull
+        @Override
+        public Composed scaleLinearly(float scaleFactor) {
+            return applyToSegments(VibrationEffectSegment::scaleLinearly, scaleFactor);
         }
 
         /** @hide */
@@ -926,10 +969,9 @@
             if (this == o) {
                 return true;
             }
-            if (!(o instanceof Composed)) {
+            if (!(o instanceof Composed other)) {
                 return false;
             }
-            Composed other = (Composed) o;
             return mSegments.equals(other.mSegments) && mRepeatIndex == other.mRepeatIndex;
         }
 
@@ -969,6 +1011,7 @@
 
         @Override
         public void writeToParcel(@NonNull Parcel out, int flags) {
+            out.writeInt(PARCEL_TOKEN_COMPOSED);
             out.writeList(mSegments);
             out.writeInt(mRepeatIndex);
         }
@@ -1011,6 +1054,208 @@
 
             return stepSegment;
         }
+
+        private <T> Composed applyToSegments(
+                BiFunction<VibrationEffectSegment, T, VibrationEffectSegment> function, T param) {
+            int segmentCount = mSegments.size();
+            ArrayList<VibrationEffectSegment> updatedSegments = new ArrayList<>(segmentCount);
+            for (int i = 0; i < segmentCount; i++) {
+                updatedSegments.add(function.apply(mSegments.get(i), param));
+            }
+            if (mSegments.equals(updatedSegments)) {
+                return this;
+            }
+            Composed updated = new Composed(updatedSegments, mRepeatIndex);
+            updated.validate();
+            return updated;
+        }
+    }
+
+    /**
+     * Implementation of {@link VibrationEffect} described by a generic {@link PersistableBundle}
+     * defined by vendors.
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_VENDOR_VIBRATION_EFFECTS)
+    public static final class VendorEffect extends VibrationEffect {
+        /** @hide */
+        public static final int DEFAULT_STRENGTH = VibrationEffect.EFFECT_STRENGTH_MEDIUM;
+        /** @hide */
+        public static final float DEFAULT_SCALE = 1.0f;
+
+        private final PersistableBundle mVendorData;
+        private final int mEffectStrength;
+        private final float mLinearScale;
+
+        /** @hide */
+        VendorEffect(@NonNull Parcel in) {
+            this(Objects.requireNonNull(
+                    in.readPersistableBundle(VibrationEffect.class.getClassLoader())),
+                    in.readInt(), in.readFloat());
+        }
+
+        /** @hide */
+        public VendorEffect(@NonNull PersistableBundle vendorData, int effectStrength,
+                float linearScale) {
+            mVendorData = vendorData;
+            mEffectStrength = effectStrength;
+            mLinearScale = linearScale;
+        }
+
+        @NonNull
+        public PersistableBundle getVendorData() {
+            return mVendorData;
+        }
+
+        public int getEffectStrength() {
+            return mEffectStrength;
+        }
+
+        public float getLinearScale() {
+            return mLinearScale;
+        }
+
+        /** @hide */
+        @Override
+        @Nullable
+        public long[] computeCreateWaveformOffOnTimingsOrNull() {
+            return null;
+        }
+
+        /** @hide */
+        @Override
+        public void validate() {
+            Preconditions.checkArgument(!mVendorData.isEmpty(),
+                    "Vendor effect bundle must be non-empty");
+        }
+
+        @Override
+        public long getDuration() {
+            return -1; // UNKNOWN
+        }
+
+        /** @hide */
+        @Override
+        public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
+            return vibratorInfo.hasCapability(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+        }
+
+        /** @hide */
+        @Override
+        public boolean isHapticFeedbackCandidate() {
+            return false;
+        }
+
+        /** @hide */
+        @NonNull
+        @Override
+        public VendorEffect resolve(int defaultAmplitude) {
+            return this;
+        }
+
+        /** @hide */
+        @NonNull
+        @Override
+        public VibrationEffect applyEffectStrength(int effectStrength) {
+            if (mEffectStrength == effectStrength) {
+                return this;
+            }
+            VendorEffect updated = new VendorEffect(mVendorData, effectStrength, mLinearScale);
+            updated.validate();
+            return updated;
+        }
+
+        /** @hide */
+        @NonNull
+        @Override
+        public VendorEffect scale(float scaleFactor) {
+            // Vendor effect strength cannot be scaled with this method.
+            return this;
+        }
+
+        /** @hide */
+        @NonNull
+        @Override
+        public VibrationEffect scaleLinearly(float scaleFactor) {
+            if (Float.compare(mLinearScale, scaleFactor) == 0) {
+                return this;
+            }
+            VendorEffect updated = new VendorEffect(mVendorData, mEffectStrength, scaleFactor);
+            updated.validate();
+            return updated;
+        }
+
+        /** @hide */
+        @NonNull
+        @Override
+        public VendorEffect applyRepeatingIndefinitely(boolean wantRepeating, int loopDelayMs) {
+            return this;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof VendorEffect other)) {
+                return false;
+            }
+            return mEffectStrength == other.mEffectStrength
+                    && (Float.compare(mLinearScale, other.mLinearScale) == 0)
+                    // Make sure it calls unparcel for both before calling BaseBundle.kindofEquals.
+                    && mVendorData.size() == other.mVendorData.size()
+                    && BaseBundle.kindofEquals(mVendorData, other.mVendorData);
+        }
+
+        @Override
+        public int hashCode() {
+            // PersistableBundle does not implement hashCode, so use its size as a shortcut.
+            return Objects.hash(mVendorData.size(), mEffectStrength, mLinearScale);
+        }
+
+        @Override
+        public String toString() {
+            return String.format(Locale.ROOT,
+                    "VendorEffect{vendorData=%s, strength=%s, scale=%.2f}",
+                    mVendorData, effectStrengthToString(mEffectStrength), mLinearScale);
+        }
+
+        /** @hide */
+        @Override
+        public String toDebugString() {
+            return String.format(Locale.ROOT, "vendorEffect=%s, strength=%s, scale=%.2f",
+                    mVendorData.toShortString(), effectStrengthToString(mEffectStrength),
+                    mLinearScale);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
+            out.writeInt(PARCEL_TOKEN_VENDOR_EFFECT);
+            out.writePersistableBundle(mVendorData);
+            out.writeInt(mEffectStrength);
+            out.writeFloat(mLinearScale);
+        }
+
+        @NonNull
+        public static final Creator<VendorEffect> CREATOR =
+                new Creator<VendorEffect>() {
+                    @Override
+                    public VendorEffect createFromParcel(Parcel in) {
+                        return new VendorEffect(in);
+                    }
+
+                    @Override
+                    public VendorEffect[] newArray(int size) {
+                        return new VendorEffect[size];
+                    }
+                };
     }
 
     /**
@@ -1249,7 +1494,9 @@
             if (mRepeatIndex >= 0) {
                 throw new UnreachableAfterRepeatingIndefinitelyException();
             }
-            Composed composed = (Composed) effect;
+            if (!(effect instanceof Composed composed)) {
+                throw new IllegalArgumentException("Can't add vendor effects to composition.");
+            }
             if (composed.getRepeatIndex() >= 0) {
                 // Start repeating from the index relative to the composed waveform.
                 mRepeatIndex = mSegments.size() + composed.getRepeatIndex();
@@ -1285,28 +1532,18 @@
          * @hide
          */
         public static String primitiveToString(@PrimitiveType int id) {
-            switch (id) {
-                case PRIMITIVE_NOOP:
-                    return "NOOP";
-                case PRIMITIVE_CLICK:
-                    return "CLICK";
-                case PRIMITIVE_THUD:
-                    return "THUD";
-                case PRIMITIVE_SPIN:
-                    return "SPIN";
-                case PRIMITIVE_QUICK_RISE:
-                    return "QUICK_RISE";
-                case PRIMITIVE_SLOW_RISE:
-                    return "SLOW_RISE";
-                case PRIMITIVE_QUICK_FALL:
-                    return "QUICK_FALL";
-                case PRIMITIVE_TICK:
-                    return "TICK";
-                case PRIMITIVE_LOW_TICK:
-                    return "LOW_TICK";
-                default:
-                    return Integer.toString(id);
-            }
+            return switch (id) {
+                case PRIMITIVE_NOOP -> "NOOP";
+                case PRIMITIVE_CLICK -> "CLICK";
+                case PRIMITIVE_THUD -> "THUD";
+                case PRIMITIVE_SPIN -> "SPIN";
+                case PRIMITIVE_QUICK_RISE -> "QUICK_RISE";
+                case PRIMITIVE_SLOW_RISE -> "SLOW_RISE";
+                case PRIMITIVE_QUICK_FALL -> "QUICK_FALL";
+                case PRIMITIVE_TICK -> "TICK";
+                case PRIMITIVE_LOW_TICK -> "LOW_TICK";
+                default -> Integer.toString(id);
+            };
         }
     }
 
@@ -1640,7 +1877,17 @@
             new Parcelable.Creator<VibrationEffect>() {
                 @Override
                 public VibrationEffect createFromParcel(Parcel in) {
-                    return new Composed(in);
+                    switch (in.readInt()) {
+                        case PARCEL_TOKEN_COMPOSED:
+                            return new Composed(in);
+                        case PARCEL_TOKEN_VENDOR_EFFECT:
+                            if (Flags.vendorVibrationEffects()) {
+                                return new VendorEffect(in);
+                            } // else fall through
+                        default:
+                            throw new IllegalStateException(
+                                    "Unexpected vibration effect type token in parcel.");
+                    }
                 }
                 @Override
                 public VibrationEffect[] newArray(int size) {
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index b1ef05a..6a2daea 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -1102,7 +1102,6 @@
     /**
      * Instructs the zygote to pre-load the application code for the given Application.
      * Only the app zygote supports this function.
-     * TODO preloadPackageForAbi() can probably be removed and the callers an use this instead.
      */
     public boolean preloadApp(ApplicationInfo appInfo, String abi)
             throws ZygoteStartFailedEx, IOException {
@@ -1130,39 +1129,6 @@
     }
 
     /**
-     * Instructs the zygote to pre-load the classes and native libraries at the given paths
-     * for the specified abi. Not all zygotes support this function.
-     */
-    public boolean preloadPackageForAbi(
-            String packagePath, String libsPath, String libFileName, String cacheKey, String abi)
-            throws ZygoteStartFailedEx, IOException {
-        synchronized (mLock) {
-            ZygoteState state = openZygoteSocketIfNeeded(abi);
-            state.mZygoteOutputWriter.write("5");
-            state.mZygoteOutputWriter.newLine();
-
-            state.mZygoteOutputWriter.write("--preload-package");
-            state.mZygoteOutputWriter.newLine();
-
-            state.mZygoteOutputWriter.write(packagePath);
-            state.mZygoteOutputWriter.newLine();
-
-            state.mZygoteOutputWriter.write(libsPath);
-            state.mZygoteOutputWriter.newLine();
-
-            state.mZygoteOutputWriter.write(libFileName);
-            state.mZygoteOutputWriter.newLine();
-
-            state.mZygoteOutputWriter.write(cacheKey);
-            state.mZygoteOutputWriter.newLine();
-
-            state.mZygoteOutputWriter.flush();
-
-            return (state.mZygoteInputStream.readInt() == 0);
-        }
-    }
-
-    /**
      * Instructs the zygote to preload the default set of classes and resources. Returns
      * {@code true} if a preload was performed as a result of this call, and {@code false}
      * otherwise. The latter usually means that the zygote eagerly preloaded at startup
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index ad2f59d..f4e2a7e 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -53,3 +53,14 @@
         purpose: PURPOSE_FEATURE
     }
 }
+
+flag {
+    namespace: "haptics"
+    name: "vendor_vibration_effects"
+    is_exported: true
+    description: "Enabled System APIs for vendor-defined vibration effects"
+    bug: "345454923"
+    metadata {
+        purpose: PURPOSE_FEATURE
+    }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ff38920..850b979 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2630,7 +2630,7 @@
     /**
      * Activity Action: Show screen that let user select its Autofill Service.
      * <p>
-     * Input: Intent's data URI set with an application name, using the
+     * Input: Intent's data URI set with an application package name, using the
      * "package" schema (like "package:com.my.app").
      *
      * <p>
@@ -2650,7 +2650,7 @@
     /**
      * Activity Action: Show screen that let user enable a Credential Manager provider.
      * <p>
-     * Input: Intent's data URI set with an application name, using the
+     * Input: Intent's data URI set with an application package name, using the
      * "package" schema (like "package:com.my.app").
      *
      * <p>
diff --git a/core/java/android/service/notification/SystemZenRules.java b/core/java/android/service/notification/SystemZenRules.java
index 22234a9..1d18643 100644
--- a/core/java/android/service/notification/SystemZenRules.java
+++ b/core/java/android/service/notification/SystemZenRules.java
@@ -129,10 +129,7 @@
         }
         sb.append(daysSummary);
         sb.append(context.getString(R.string.zen_mode_trigger_summary_divider_text));
-        sb.append(context.getString(
-                R.string.zen_mode_trigger_summary_range_symbol_combination,
-                timeString(context, schedule.startHour, schedule.startMinute),
-                timeString(context, schedule.endHour, schedule.endMinute)));
+        sb.append(getTimeSummary(context, schedule));
 
         return sb.toString();
     }
@@ -142,7 +139,7 @@
      * adjacent days grouped together ("Sun-Wed" instead of "Sun,Mon,Tue,Wed").
      */
     @Nullable
-    private static String getShortDaysSummary(Context context, @NonNull ScheduleInfo schedule) {
+    public static String getShortDaysSummary(Context context, @NonNull ScheduleInfo schedule) {
         // Compute a list of days with contiguous days grouped together, for example: "Sun-Thu" or
         // "Sun-Mon,Wed,Fri"
         final int[] days = schedule.days;
@@ -224,6 +221,14 @@
         return null;
     }
 
+    /** Returns the time part of a {@link ScheduleInfo}, e.g. {@code 9:00-17:00}. */
+    public static String getTimeSummary(Context context, @NonNull ScheduleInfo schedule) {
+        return context.getString(
+                R.string.zen_mode_trigger_summary_range_symbol_combination,
+                timeString(context, schedule.startHour, schedule.startMinute),
+                timeString(context, schedule.endHour, schedule.endMinute));
+    }
+
     /**
      * Convenience method for representing the specified time in string format.
      */
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 02c63db..155a3e4 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -240,3 +240,13 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "dont_break_email_in_nobreak_tag"
+  namespace: "text"
+  description: "Prevent line break inside email."
+  bug: "350691716"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
\ No newline at end of file
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a23e383..64c7766 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -11004,6 +11004,11 @@
             ? afm.isAutofillable(this) : false;
     }
 
+    /**
+     * Returns whether the view is autofillable.
+     *
+     * @return whether the view is autofillable, and should send out autofill request to provider.
+     */
     private boolean isAutofillable() {
         if (DBG) {
             Log.d(VIEW_LOG_TAG, "isAutofillable() entered.");
@@ -27631,6 +27636,23 @@
         return null;
     }
 
+
+    /**
+     * Performs the traversal to find views that are autofillable.
+     * Autofillable views are added to the provided list.
+     *
+     * <strong>Note:</strong>This method does not stop at the root namespace
+     * boundary.
+     *
+     * @param autofillableViews The output list of autofillable Views.
+     * @hide
+     */
+    public void findAutofillableViewsByTraversal(@NonNull List<View> autofillableViews) {
+        if (isAutofillable()) {
+            autofillableViews.add(this);
+        }
+    }
+
     /**
      * Look for a child view with the given tag.  If this view has the given
      * tag, return this view.
@@ -30597,6 +30619,8 @@
         }
     }
 
+    // Note that if the function returns true, it indicates aapt did not generate this id.
+    // However false value does not indicate that aapt did generated this id.
     private static boolean isViewIdGenerated(int id) {
         return (id & 0xFF000000) == 0 && (id & 0x00FFFFFF) != 0;
     }
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index ceaca22..6f88386 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1500,6 +1500,19 @@
         return null;
     }
 
+    /** @hide */
+    @Override
+    public void findAutofillableViewsByTraversal(@NonNull List<View> autofillableViews) {
+        super.findAutofillableViewsByTraversal(autofillableViews);
+
+        final int childrenCount = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < childrenCount; i++) {
+            View child = children[i];
+            child.findAutofillableViewsByTraversal(autofillableViews);
+        }
+    }
+
     @Override
     public void dispatchWindowFocusChanged(boolean hasFocus) {
         super.dispatchWindowFocusChanged(hasFocus);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 37d5220..9e52a14 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -12900,11 +12900,6 @@
                     mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
         }
 
-        // If it's currently an intermittent update,
-        // we should keep mPreferredFrameRateCategory as NORMAL
-        if (intermittentUpdateState() == INTERMITTENT_STATE_INTERMITTENT) {
-            return;
-        }
 
         if (mFrameRateCategoryHighCount > 0) {
             mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
diff --git a/core/java/android/view/autofill/AutofillClientController.java b/core/java/android/view/autofill/AutofillClientController.java
index 2f7adaa..d505c733 100644
--- a/core/java/android/view/autofill/AutofillClientController.java
+++ b/core/java/android/view/autofill/AutofillClientController.java
@@ -39,6 +39,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 /**
  * A controller to manage the autofill requests for the {@link Activity}.
@@ -71,6 +72,7 @@
     private AutofillPopupWindow mAutofillPopupWindow;
     private boolean mAutoFillResetNeeded;
     private boolean mAutoFillIgnoreFirstResumePause;
+    private Boolean mRelayoutFix;
 
     /**
      * AutofillClientController constructor.
@@ -86,6 +88,18 @@
         return mAutofillManager;
     }
 
+    /**
+     * Whether to apply relayout fixes.
+     *
+     * @hide
+     */
+    public boolean isRelayoutFixEnabled() {
+        if (mRelayoutFix == null) {
+            mRelayoutFix = getAutofillManager().isRelayoutFixEnabled();
+        }
+        return mRelayoutFix;
+    }
+
     // ------------------ Called for Activity events ------------------
 
     /**
@@ -119,7 +133,54 @@
      * Called when the {@link Activity#onResume()} is called.
      */
     public void onActivityResumed() {
+        if (Helper.sVerbose) {
+            Log.v(TAG, "onActivityResumed()");
+        }
+        if (isRelayoutFixEnabled()) {
+            // Do nothing here. We'll handle it in onActivityPostResumed()
+            return;
+        }
+        if (Helper.sVerbose) {
+            Log.v(TAG, "onActivityResumed(): Relayout fix not enabled");
+        }
+        forResume();
+    }
+
+    /**
+     * Called when the {@link Activity#onPostResume()} is called.
+     */
+    public void onActivityPostResumed() {
+        if (Helper.sVerbose) {
+            Log.v(TAG, "onActivityPostResumed()");
+        }
+        if (!isRelayoutFixEnabled()) {
+            return;
+        }
+        if (Helper.sVerbose) {
+            Log.v(TAG, "onActivityPostResumed(): Relayout fix enabled");
+        }
+        forResume();
+    }
+
+    /**
+     * Code to execute when an app has resumed (or is about to resume)
+     */
+    private void forResume() {
         enableAutofillCompatibilityIfNeeded();
+        boolean relayoutFix = isRelayoutFixEnabled();
+        if (relayoutFix) {
+            if (getAutofillManager().shouldRetryFill()) {
+                if (Helper.sVerbose) {
+                    Log.v(TAG, "forResume(): Autofill potential relayout. Retrying fill.");
+                }
+                getAutofillManager().attemptRefill();
+            } else {
+                if (Helper.sVerbose) {
+                    Log.v(TAG, "forResume(): Not attempting refill.");
+                }
+            }
+        }
+
         if (mAutoFillResetNeeded) {
             if (!mAutoFillIgnoreFirstResumePause) {
                 View focus = mActivity.getCurrentFocus();
@@ -131,7 +192,16 @@
                     // ViewRootImpl.performTraversals() changes window visibility to VISIBLE.
                     // So we cannot call View.notifyEnterOrExited() which will do nothing
                     // when View.isVisibleToUser() is false.
-                    getAutofillManager().notifyViewEntered(focus);
+                    if (relayoutFix && getAutofillManager().isAuthenticationPending()) {
+                        if (Helper.sVerbose) {
+                            Log.v(TAG, "forResume(): ignoring focus due to auth pending");
+                        }
+                    } else {
+                        if (Helper.sVerbose) {
+                            Log.v(TAG, "forResume(): notifyViewEntered");
+                        }
+                        getAutofillManager().notifyViewEntered(focus);
+                    }
                 }
             }
         }
@@ -427,6 +497,22 @@
     }
 
     @Override
+    public List<View> autofillClientFindAutofillableViewsByTraversal() {
+        final ArrayList<View> views = new ArrayList<>();
+        final ArrayList<ViewRootImpl> roots =
+                WindowManagerGlobal.getInstance().getRootViews(mActivity.getActivityToken());
+
+        for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
+            final View rootView = roots.get(rootNum).getView();
+
+            if (rootView != null) {
+                rootView.findAutofillableViewsByTraversal(views);
+            }
+        }
+        return views;
+    }
+
+    @Override
     public boolean autofillClientIsFillUiShowing() {
         return mAutofillPopupWindow != null && mAutofillPopupWindow.isShowing();
     }
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 515ed0e..02a86c9 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -874,6 +874,13 @@
         @Nullable View autofillClientFindViewByAccessibilityIdTraversal(int viewId, int windowId);
 
         /**
+         * Finds all the autofillable views on the screen.
+         *
+         * @return The list of views that are autofillable.
+         */
+        List<View> autofillClientFindAutofillableViewsByTraversal();
+
+        /**
          * Runs the specified action on the UI thread.
          */
         void autofillClientRunOnUiThread(Runnable action);
@@ -1498,6 +1505,37 @@
     }
 
     /**
+     * Called to know whether authentication was pending.
+     * @hide
+     */
+    public boolean isAuthenticationPending() {
+        return mState == STATE_PENDING_AUTHENTICATION;
+    }
+
+    /**
+     * Called to check if we should retry fill.
+     * Useful for knowing whether to attempt refill after relayout.
+     *
+     * @hide
+     */
+    public boolean shouldRetryFill() {
+        // TODO: Implement in follow-up cl
+        return false;
+    }
+
+    /**
+     * Called when a potential relayout may have occurred.
+     *
+     * @return whether refill was done. True if refill was done partially or fully.
+     * @hide
+     */
+    public boolean attemptRefill() {
+        Log.i(TAG, "Attempting refill");
+        // TODO: Implement in follow-up cl
+        return false;
+    }
+
+    /**
      * Called when a {@link View} that supports autofill is entered.
      *
      * @param view {@link View} that was entered.
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0dadbe3..eb35817 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -8360,7 +8360,7 @@
         }
     }
 
-    private interface PendingResources<T> {
+    interface PendingResources<T> {
         T create(Context context, Resources appResources, HierarchyRootData rootData, int depth)
                 throws Exception;
     }
diff --git a/core/java/android/widget/RemoteViewsSerializers.java b/core/java/android/widget/RemoteViewsSerializers.java
new file mode 100644
index 0000000..600fea4
--- /dev/null
+++ b/core/java/android/widget/RemoteViewsSerializers.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.widget;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BlendMode;
+import android.graphics.drawable.Icon;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
+
+import androidx.annotation.NonNull;
+
+import java.io.ByteArrayOutputStream;
+import java.util.function.Function;
+
+/**
+ * This class provides serialization for certain types used within RemoteViews.
+ *
+ * @hide
+ */
+public class RemoteViewsSerializers {
+    private static final String TAG = "RemoteViews";
+
+    /**
+     * Write Icon to proto.
+     */
+    public static void writeIconToProto(@NonNull ProtoOutputStream out,
+            @NonNull Resources appResources, @NonNull Icon icon) {
+        if (icon.getTintList() != null) {
+            final long token = out.start(RemoteViewsProto.Icon.TINT_LIST);
+            icon.getTintList().writeToProto(out);
+            out.end(token);
+        }
+        out.write(RemoteViewsProto.Icon.BLEND_MODE, BlendMode.toValue(icon.getTintBlendMode()));
+        switch (icon.getType()) {
+            case Icon.TYPE_BITMAP:
+                final ByteArrayOutputStream bitmapBytes = new ByteArrayOutputStream();
+                icon.getBitmap().compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100, bitmapBytes);
+                out.write(RemoteViewsProto.Icon.BITMAP, bitmapBytes.toByteArray());
+                break;
+            case Icon.TYPE_ADAPTIVE_BITMAP:
+                final ByteArrayOutputStream adaptiveBitmapBytes = new ByteArrayOutputStream();
+                icon.getBitmap().compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100,
+                        adaptiveBitmapBytes);
+                out.write(RemoteViewsProto.Icon.ADAPTIVE_BITMAP, adaptiveBitmapBytes.toByteArray());
+                break;
+            case Icon.TYPE_RESOURCE:
+                out.write(RemoteViewsProto.Icon.RESOURCE,
+                        appResources.getResourceName(icon.getResId()));
+                break;
+            case Icon.TYPE_DATA:
+                out.write(RemoteViewsProto.Icon.DATA, icon.getDataBytes());
+                break;
+            case Icon.TYPE_URI:
+                out.write(RemoteViewsProto.Icon.URI, icon.getUriString());
+                break;
+            case Icon.TYPE_URI_ADAPTIVE_BITMAP:
+                out.write(RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP, icon.getUriString());
+                break;
+            default:
+                Log.e(TAG, "Tried to serialize unknown Icon type " + icon.getType());
+        }
+    }
+
+    /**
+     * Create Icon from proto.
+     */
+    @NonNull
+    public static Function<Resources, Icon> createIconFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        final LongSparseArray<Object> values = new LongSparseArray<>();
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.Icon.BLEND_MODE:
+                    values.put(RemoteViewsProto.Icon.BLEND_MODE,
+                            in.readInt(RemoteViewsProto.Icon.BLEND_MODE));
+                    break;
+                case (int) RemoteViewsProto.Icon.TINT_LIST:
+                    final long tintListToken = in.start(RemoteViewsProto.Icon.TINT_LIST);
+                    values.put(RemoteViewsProto.Icon.TINT_LIST, ColorStateList.createFromProto(in));
+                    in.end(tintListToken);
+                    break;
+                case (int) RemoteViewsProto.Icon.BITMAP:
+                    byte[] bitmapData = in.readBytes(RemoteViewsProto.Icon.BITMAP);
+                    values.put(RemoteViewsProto.Icon.BITMAP,
+                            BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length));
+                    break;
+                case (int) RemoteViewsProto.Icon.ADAPTIVE_BITMAP:
+                    final byte[] bitmapAdaptiveData = in.readBytes(
+                            RemoteViewsProto.Icon.ADAPTIVE_BITMAP);
+                    values.put(RemoteViewsProto.Icon.ADAPTIVE_BITMAP,
+                            BitmapFactory.decodeByteArray(bitmapAdaptiveData, 0,
+                                    bitmapAdaptiveData.length));
+                    break;
+                case (int) RemoteViewsProto.Icon.RESOURCE:
+                    values.put(RemoteViewsProto.Icon.RESOURCE,
+                            in.readString(RemoteViewsProto.Icon.RESOURCE));
+                    break;
+                case (int) RemoteViewsProto.Icon.DATA:
+                    values.put(RemoteViewsProto.Icon.DATA,
+                            in.readBytes(RemoteViewsProto.Icon.DATA));
+                    break;
+                case (int) RemoteViewsProto.Icon.URI:
+                    values.put(RemoteViewsProto.Icon.URI, in.readString(RemoteViewsProto.Icon.URI));
+                    break;
+                case (int) RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP:
+                    values.put(RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP,
+                            in.readString(RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP));
+                    break;
+                default:
+                    Log.w(TAG, "Unhandled field while reading Icon proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+
+        return (resources) -> {
+            final int blendMode = (int) values.get(RemoteViewsProto.Icon.BLEND_MODE, -1);
+            final ColorStateList tintList = (ColorStateList) values.get(
+                    RemoteViewsProto.Icon.TINT_LIST);
+            final Bitmap bitmap = (Bitmap) values.get(RemoteViewsProto.Icon.BITMAP);
+            final Bitmap bitmapAdaptive = (Bitmap) values.get(
+                    RemoteViewsProto.Icon.ADAPTIVE_BITMAP);
+            final String resName = (String) values.get(RemoteViewsProto.Icon.RESOURCE);
+            final int resource = resName != null ? resources.getIdentifier(resName, /* defType= */
+                    null,
+                    /* defPackage= */ null) : -1;
+            final byte[] data = (byte[]) values.get(RemoteViewsProto.Icon.DATA);
+            final String uri = (String) values.get(RemoteViewsProto.Icon.URI);
+            final String uriAdaptive = (String) values.get(
+                    RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP);
+            Icon icon;
+            if (bitmap != null) {
+                icon = Icon.createWithBitmap(bitmap);
+            } else if (bitmapAdaptive != null) {
+                icon = Icon.createWithAdaptiveBitmap(bitmapAdaptive);
+            } else if (resource != -1) {
+                icon = Icon.createWithResource(resources, resource);
+            } else if (data != null) {
+                icon = Icon.createWithData(data, 0, data.length);
+            } else if (uri != null) {
+                icon = Icon.createWithContentUri(uri);
+            } else if (uriAdaptive != null) {
+                icon = Icon.createWithAdaptiveBitmapContentUri(uriAdaptive);
+            } else {
+                // Either this Icon has no data or is of an unknown type.
+                return null;
+            }
+
+            if (tintList != null) {
+                icon.setTintList(tintList);
+            }
+            if (blendMode != -1) {
+                icon.setTintBlendMode(BlendMode.fromValue(blendMode));
+            }
+            return icon;
+        };
+    }
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 7f48c42..76989f9 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -180,6 +180,17 @@
 }
 
 flag {
+  name: "use_tasks_dim_only"
+  namespace: "windowing_frontend"
+  description: "Only use the task's dim and reparent it to the display area when needed instead of coordinating multiple dimmers"
+  bug: "352522056"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
     name: "release_snapshot_aggressively"
     namespace: "windowing_frontend"
     description: "Actively release task snapshot memory"
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index 9ed7384..8dee223 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -16,18 +16,15 @@
 
 package com.android.internal.os;
 
-import android.app.ApplicationLoaders;
 import android.app.LoadedApk;
 import android.content.pm.ApplicationInfo;
 import android.net.LocalSocket;
-import android.text.TextUtils;
 import android.util.Log;
 import android.webkit.WebViewFactory;
 import android.webkit.WebViewFactoryProvider;
 import android.webkit.WebViewLibraryLoader;
 
 import java.io.DataOutputStream;
-import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.Method;
 
@@ -83,28 +80,6 @@
             Log.i(TAG, "Application preload done");
         }
 
-        @Override
-        protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName,
-                String cacheKey) {
-            Log.i(TAG, "Beginning package preload");
-            // Ask ApplicationLoaders to create and cache a classloader for the WebView APK so that
-            // our children will reuse the same classloader instead of creating their own.
-            // This enables us to preload Java and native code in the webview zygote process and
-            // have the preloaded versions actually be used post-fork.
-            ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader(
-                    packagePath, libsPath, cacheKey);
-
-            // Add the APK to the Zygote's list of allowed files for children.
-            String[] packageList = TextUtils.split(packagePath, File.pathSeparator);
-            for (String packageEntry : packageList) {
-                Zygote.nativeAllowFileAcrossFork(packageEntry);
-            }
-
-            doPreload(loader, libFileName);
-
-            Log.i(TAG, "Package preload done");
-        }
-
         private void doPreload(ClassLoader loader, String libFileName) {
             // Load the native library using WebViewLibraryLoader to share the RELRO data with other
             // processes.
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index cab84bb..fafa085 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -938,8 +938,6 @@
             throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--get-pid");
         } else if (args.mPreloadDefault) {
             throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-default");
-        } else if (args.mPreloadPackage != null) {
-            throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-package");
         } else if (args.mPreloadApp != null) {
             throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-app");
         } else if (args.mStartChildZygote) {
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index 86b9a59..27ce64e 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -141,33 +141,12 @@
     String mAppDataDir;
 
     /**
-     * The APK path of the package to preload, when using --preload-package.
-     */
-    String mPreloadPackage;
-
-    /**
      * A Base64 string representing a serialize ApplicationInfo Parcel,
      when using --preload-app.
      */
     String mPreloadApp;
 
     /**
-     * The native library path of the package to preload, when using --preload-package.
-     */
-    String mPreloadPackageLibs;
-
-    /**
-     * The filename of the native library to preload, when using --preload-package.
-     */
-    String mPreloadPackageLibFileName;
-
-    /**
-     * The cache key under which to enter the preloaded package into the classloader cache, when
-     * using --preload-package.
-     */
-    String mPreloadPackageCacheKey;
-
-    /**
      * Whether this is a request to start preloading the default resources and classes. This
      * argument only makes sense when the zygote is in lazy preload mode (i.e, when it's started
      * with --enable-lazy-preload).
@@ -419,12 +398,6 @@
             } else if (arg.equals("--preload-app")) {
                 ++curArg;
                 mPreloadApp = args.nextArg();
-            } else if (arg.equals("--preload-package")) {
-                curArg += 4;
-                mPreloadPackage = args.nextArg();
-                mPreloadPackageLibs = args.nextArg();
-                mPreloadPackageLibFileName = args.nextArg();
-                mPreloadPackageCacheKey = args.nextArg();
             } else if (arg.equals("--preload-default")) {
                 mPreloadDefault = true;
                 expectRuntimeArgs = false;
@@ -504,11 +477,6 @@
             if (argCount > curArg) {
                 throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
             }
-        } else if (mPreloadPackage != null) {
-            if (argCount > curArg) {
-                throw new IllegalArgumentException(
-                    "Unexpected arguments after --preload-package.");
-            }
         } else if (mPreloadApp != null) {
             if (argCount > curArg) {
                 throw new IllegalArgumentException(
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index d4dcec9..2eab081 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -165,14 +165,6 @@
                     return null;
                 }
 
-                if (parsedArgs.mPreloadPackage != null) {
-                    handlePreloadPackage(parsedArgs.mPreloadPackage,
-                            parsedArgs.mPreloadPackageLibs,
-                            parsedArgs.mPreloadPackageLibFileName,
-                            parsedArgs.mPreloadPackageCacheKey);
-                    return null;
-                }
-
                 if (canPreloadApp() && parsedArgs.mPreloadApp != null) {
                     byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp);
                     Parcel appInfoParcel = Parcel.obtain();
@@ -475,11 +467,6 @@
         return mSocketOutStream;
     }
 
-    protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName,
-            String cacheKey) {
-        throw new RuntimeException("Zygote does not support package preloading");
-    }
-
     protected boolean canPreloadApp() {
         return false;
     }
diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto
index f08ea1b..37d1c5b 100644
--- a/core/proto/android/widget/remoteviews.proto
+++ b/core/proto/android/widget/remoteviews.proto
@@ -21,6 +21,7 @@
 package android.widget;
 
 import "frameworks/base/core/proto/android/privacy.proto";
+import "frameworks/base/core/proto/android/content/res/color_state_list.proto";
 
 /**
  * An android.widget.RemoteViews object. This is used by RemoteViews.createPreviewFromProto
@@ -71,6 +72,23 @@
         optional int32 view_type_count = 4;
         optional bool attached = 5;
     }
+
+    /**
+     * An android.graphics.drawable Icon.
+     */
+    message Icon {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+        optional int32 blend_mode = 1;
+        optional android.content.res.ColorStateListProto tint_list = 2;
+        oneof icon {
+            bytes bitmap = 3;
+            string resource = 4;
+            bytes data = 5;
+            string uri = 6;
+            string uri_adaptive_bitmap = 7;
+            bytes adaptive_bitmap = 8;
+        };
+    }
 }
 
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 193836e..f3dac23 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2611,6 +2611,14 @@
     <permission android:name="android.permission.VIBRATE_SYSTEM_CONSTANTS"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows access to perform vendor effects in the vibrator.
+         <p>Protection level: signature
+         @FlaggedApi("android.os.vibrator.vendor_vibration_effects")
+         @hide
+    -->
+    <permission android:name="android.permission.VIBRATE_VENDOR_EFFECTS"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows access to the vibrator state.
          <p>Protection level: signature
          @hide
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 236e7c5..8cb7646 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7069,6 +7069,9 @@
     <!-- Whether desktop mode is supported on the current device  -->
     <bool name="config_isDesktopModeSupported">false</bool>
 
+    <!-- Maximum number of active tasks on a given Desktop Windowing session. Set to 0 for unlimited. -->
+    <integer name="config_maxDesktopWindowingActiveTasks">0</integer>
+
     <!-- Frame rate compatibility value for Wallpaper
          FRAME_RATE_COMPATIBILITY_MIN (102) is used by default for lower power consumption -->
     <integer name="config_wallpaperFrameRateCompatibility">102</integer>
@@ -7093,4 +7096,8 @@
 
     <!-- Whether to enable usb state update via udc sysfs. -->
     <bool name="config_enableUdcSysfsUsbStateUpdate">false</bool>
+
+    <!-- Whether to enable the private space search illustration and search tile content in "Hide Private Space" settings page.
+         OEM/Partner can explicitly opt to hide the illustration and search tile content. -->
+    <bool name="config_enableSearchTileHideIllustrationInPrivateSpace">true</bool>
 </resources>
diff --git a/core/res/res/values/config_device_idle.xml b/core/res/res/values/config_device_idle.xml
index 7a707c0..cc7b891 100644
--- a/core/res/res/values/config_device_idle.xml
+++ b/core/res/res/values/config_device_idle.xml
@@ -28,7 +28,7 @@
     <integer name="device_idle_flex_time_short_ms">60000</integer>
 
     <!-- Default for DeviceIdleController.Constants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT -->
-    <integer name="device_idle_light_after_inactive_to_ms">60000</integer>
+    <integer name="device_idle_light_after_inactive_to_ms">240000</integer>
 
     <!-- Default for DeviceIdleController.Constants.LIGHT_IDLE_TIMEOUT -->
     <integer name="device_idle_light_idle_to_ms">300000</integer>
@@ -67,10 +67,10 @@
     <integer name="device_idle_min_deep_maintenance_time_ms">30000</integer>
 
     <!-- Default for DeviceIdleController.Constants.INACTIVE_TIMEOUT -->
-    <integer name="device_idle_inactive_to_ms">60000</integer>
+    <integer name="device_idle_inactive_to_ms">15000</integer>
 
     <!-- Default for DeviceIdleController.Constants.SENSING_TIMEOUT -->
-    <integer name="device_idle_sensing_to_ms">30000</integer>
+    <integer name="device_idle_sensing_to_ms">15000</integer>
 
     <!-- Default for DeviceIdleController.Constants.LOCATING_TIMEOUT -->
     <integer name="device_idle_locating_to_ms">15000</integer>
@@ -79,13 +79,13 @@
     <item name="device_idle_location_accuracy" format="float" type="integer">20.0</item>
 
     <!-- Default for DeviceIdleController.Constants.MOTION_INACTIVE_TIMEOUT -->
-    <integer name="device_idle_motion_inactive_to_ms">600000</integer>
+    <integer name="device_idle_motion_inactive_to_ms">30000</integer>
 
     <!-- Default for DeviceIdleController.Constants.MOTION_INACTIVE_TIMEOUT_FLEX -->
     <integer name="device_idle_motion_inactive_to_flex_ms">60000</integer>
 
     <!-- Default for DeviceIdleController.Constants.IDLE_AFTER_INACTIVE_TIMEOUT -->
-    <integer name="device_idle_idle_after_inactive_to_ms">60000</integer>
+    <integer name="device_idle_idle_after_inactive_to_ms">15000</integer>
 
     <!-- Default for DeviceIdleController.Constants.IDLE_PENDING_TIMEOUT -->
     <integer name="device_idle_idle_pending_to_ms">300000</integer>
@@ -100,7 +100,7 @@
     <integer name="device_idle_quick_doze_delay_to_ms">60000</integer>
 
     <!-- Default for DeviceIdleController.Constants.IDLE_TIMEOUT -->
-    <integer name="device_idle_idle_to_ms">900000</integer>
+    <integer name="device_idle_idle_to_ms">3600000</integer>
 
     <!-- Default for DeviceIdleController.Constants.MAX_IDLE_TIMEOUT -->
     <integer name="device_idle_max_idle_to_ms">21600000</integer>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 09688f2..4a425cb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -524,6 +524,7 @@
   <java-symbol type="bool" name="config_notificationCloseButtonSupported"/>
   <java-symbol type="bool" name="config_enableGaiaEducationInPrivateSpace"/>
   <java-symbol type="bool" name="config_enableUdcSysfsUsbStateUpdate"/>
+  <java-symbol type="bool" name="config_enableSearchTileHideIllustrationInPrivateSpace"/>
 
   <java-symbol type="color" name="tab_indicator_text_v4" />
 
@@ -5522,6 +5523,9 @@
   <!-- Whether desktop mode is supported on the current device  -->
   <java-symbol type="bool" name="config_isDesktopModeSupported" />
 
+  <!-- Maximum number of active tasks on a given Desktop Windowing session. Set to 0 for unlimited. -->
+  <java-symbol type="integer" name="config_maxDesktopWindowingActiveTasks"/>
+
   <!-- Frame rate compatibility value for Wallpaper -->
   <java-symbol type="integer" name="config_wallpaperFrameRateCompatibility" />
 
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index f5affd3..24f6cea 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -254,8 +254,8 @@
         try {
             // Send process level config change.
             ClientTransaction transaction = newTransaction(activityThread);
-            transaction.addTransactionItem(ConfigurationChangeItem.obtain(
-                    newConfig, DEVICE_ID_INVALID));
+            transaction.addTransactionItem(
+                    new ConfigurationChangeItem(newConfig, DEVICE_ID_INVALID));
             appThread.scheduleTransaction(transaction);
             InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
@@ -271,7 +271,7 @@
             newConfig.seq++;
             newConfig.smallestScreenWidthDp++;
             transaction = newTransaction(activityThread);
-            transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
+            transaction.addTransactionItem(new ActivityConfigurationChangeItem(
                     activity.getActivityToken(), newConfig, new ActivityWindowInfo()));
             appThread.scheduleTransaction(transaction);
             InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -474,16 +474,16 @@
         activity.mTestLatch = new CountDownLatch(1);
 
         ClientTransaction transaction = newTransaction(activityThread);
-        transaction.addTransactionItem(ConfigurationChangeItem.obtain(
-                processConfigLandscape, DEVICE_ID_INVALID));
+        transaction.addTransactionItem(
+                new ConfigurationChangeItem(processConfigLandscape, DEVICE_ID_INVALID));
         appThread.scheduleTransaction(transaction);
 
         transaction = newTransaction(activityThread);
-        transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
+        transaction.addTransactionItem(new ActivityConfigurationChangeItem(
                 activity.getActivityToken(), activityConfigLandscape, new ActivityWindowInfo()));
-        transaction.addTransactionItem(ConfigurationChangeItem.obtain(
-                processConfigPortrait, DEVICE_ID_INVALID));
-        transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
+        transaction.addTransactionItem(
+                new ConfigurationChangeItem(processConfigPortrait, DEVICE_ID_INVALID));
+        transaction.addTransactionItem(new ActivityConfigurationChangeItem(
                 activity.getActivityToken(), activityConfigPortrait, new ActivityWindowInfo()));
         appThread.scheduleTransaction(transaction);
 
@@ -847,7 +847,7 @@
             final ActivityWindowInfo newInfo = new ActivityWindowInfo();
             newInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
                     new Rect(0, 0, 1000, 1000));
-            final ActivityRelaunchItem relaunchItem = ActivityRelaunchItem.obtain(
+            final ActivityRelaunchItem relaunchItem = new ActivityRelaunchItem(
                     activity.getActivityToken(), null, null, 0,
                     new MergedConfiguration(currentConfig, currentConfig),
                     false /* preserveWindow */, newInfo);
@@ -881,7 +881,7 @@
             final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
             activityWindowInfo.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
             final ActivityConfigurationChangeItem activityConfigurationChangeItem =
-                    ActivityConfigurationChangeItem.obtain(
+                    new ActivityConfigurationChangeItem(
                             activity.getActivityToken(), config, activityWindowInfo);
             final ClientTransaction transaction = newTransaction(activity);
             transaction.addTransactionItem(activityConfigurationChangeItem);
@@ -898,7 +898,7 @@
                     new ActivityWindowInfo(activityWindowInfo);
             config.seq++;
             final ActivityConfigurationChangeItem activityConfigurationChangeItem2 =
-                    ActivityConfigurationChangeItem.obtain(
+                    new ActivityConfigurationChangeItem(
                             activity.getActivityToken(), config, activityWindowInfo2);
             final ClientTransaction transaction2 = newTransaction(activity);
             transaction2.addTransactionItem(activityConfigurationChangeItem2);
@@ -978,12 +978,12 @@
         } else {
             activityWindowInfo = record.getActivityWindowInfo();
         }
-        final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(
+        final ClientTransactionItem callbackItem = new ActivityRelaunchItem(
                 activity.getActivityToken(), null, null, 0,
                 new MergedConfiguration(currentConfig, currentConfig),
                 false /* preserveWindow */, activityWindowInfo);
         final ResumeActivityItem resumeStateRequest =
-                ResumeActivityItem.obtain(activity.getActivityToken(), true /* isForward */,
+                new ResumeActivityItem(activity.getActivityToken(), true /* isForward */,
                         false /* shouldSendCompatFakeFocus*/);
 
         final ClientTransaction transaction = newTransaction(activity);
@@ -996,7 +996,7 @@
     @NonNull
     private static ClientTransaction newResumeTransaction(@NonNull Activity activity) {
         final ResumeActivityItem resumeStateRequest =
-                ResumeActivityItem.obtain(activity.getActivityToken(), true /* isForward */,
+                new ResumeActivityItem(activity.getActivityToken(), true /* isForward */,
                         false /* shouldSendCompatFakeFocus */);
 
         final ClientTransaction transaction = newTransaction(activity);
@@ -1007,8 +1007,7 @@
 
     @NonNull
     private static ClientTransaction newStopTransaction(@NonNull Activity activity) {
-        final StopActivityItem stopStateRequest = StopActivityItem.obtain(
-                activity.getActivityToken());
+        final StopActivityItem stopStateRequest = new StopActivityItem(activity.getActivityToken());
 
         final ClientTransaction transaction = newTransaction(activity);
         transaction.addTransactionItem(stopStateRequest);
@@ -1019,7 +1018,7 @@
     @NonNull
     private static ClientTransaction newActivityConfigTransaction(@NonNull Activity activity,
             @NonNull Configuration config) {
-        final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(
+        final ActivityConfigurationChangeItem item = new ActivityConfigurationChangeItem(
                 activity.getActivityToken(), config, new ActivityWindowInfo());
 
         final ClientTransaction transaction = newTransaction(activity);
@@ -1031,8 +1030,7 @@
     @NonNull
     private static ClientTransaction newNewIntentTransaction(@NonNull Activity activity,
             @NonNull List<ReferrerIntent> intents, boolean resume) {
-        final NewIntentItem item = NewIntentItem.obtain(activity.getActivityToken(), intents,
-                resume);
+        final NewIntentItem item = new NewIntentItem(activity.getActivityToken(), intents, resume);
 
         final ClientTransaction transaction = newTransaction(activity);
         transaction.addTransactionItem(item);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
index ee7fa7c..f023196 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -21,13 +21,17 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.ClientTransactionHandler;
 import android.content.res.Configuration;
@@ -102,8 +106,8 @@
 
     @Test
     public void testDestroyActivityItem_preExecute() {
-        final DestroyActivityItem item = DestroyActivityItem
-                .obtain(mActivityToken, false /* finished */);
+        final DestroyActivityItem item =
+                new DestroyActivityItem(mActivityToken, false /* finished */);
 
         item.preExecute(mHandler);
 
@@ -113,8 +117,8 @@
 
     @Test
     public void testDestroyActivityItem_postExecute() {
-        final DestroyActivityItem item = DestroyActivityItem
-                .obtain(mActivityToken, false /* finished */);
+        final DestroyActivityItem item =
+                new DestroyActivityItem(mActivityToken, false /* finished */);
         item.preExecute(mHandler);
 
         item.postExecute(mHandler, mPendingActions);
@@ -124,8 +128,8 @@
 
     @Test
     public void testDestroyActivityItem_execute() {
-        final DestroyActivityItem item = DestroyActivityItem
-                .obtain(mActivityToken, false /* finished */);
+        final DestroyActivityItem item =
+                new DestroyActivityItem(mActivityToken, false /* finished */);
 
         item.execute(mHandler, mActivityClientRecord, mPendingActions);
 
@@ -134,9 +138,44 @@
     }
 
     @Test
+    public void testResumeActivityItem_preExecute_withProcState_updatesProcessState() {
+        final ResumeActivityItem item = new ResumeActivityItem(mActivityToken,
+                ActivityManager.PROCESS_STATE_TOP /* procState */,
+                true /* isForward */,
+                false /* shouldSendCompatFakeFocus*/);
+
+        item.preExecute(mHandler);
+
+        verify(mHandler).updateProcessState(ActivityManager.PROCESS_STATE_TOP, false);
+    }
+
+    @Test
+    public void testResumeActivityItem_preExecute_withUnknownProcState_skipsProcessStateUpdate() {
+        final ResumeActivityItem item = new ResumeActivityItem(mActivityToken,
+                ActivityManager.PROCESS_STATE_UNKNOWN /* procState */,
+                true /* isForward */,
+                false /* shouldSendCompatFakeFocus*/);
+
+        item.preExecute(mHandler);
+
+        verify(mHandler, never()).updateProcessState(anyInt(), anyBoolean());
+    }
+
+    @Test
+    public void testResumeActivityItem_preExecute_withoutProcState_skipsProcessStateUpdate() {
+        final ResumeActivityItem item = new ResumeActivityItem(mActivityToken,
+                true /* isForward */,
+                false /* shouldSendCompatFakeFocus*/);
+
+        item.preExecute(mHandler);
+
+        verify(mHandler, never()).updateProcessState(anyInt(), anyBoolean());
+    }
+
+    @Test
     public void testWindowContextInfoChangeItem_execute() {
-        final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
-                .obtain(mWindowClientToken, mConfiguration, DEFAULT_DISPLAY);
+        final WindowContextInfoChangeItem item = new WindowContextInfoChangeItem(mWindowClientToken,
+                mConfiguration, DEFAULT_DISPLAY);
 
         item.execute(mHandler, mPendingActions);
 
@@ -146,8 +185,8 @@
 
     @Test
     public void testWindowContextWindowRemovalItem_execute() {
-        final WindowContextWindowRemovalItem item = WindowContextWindowRemovalItem.obtain(
-                mWindowClientToken);
+        final WindowContextWindowRemovalItem item =
+                new WindowContextWindowRemovalItem(mWindowClientToken);
 
         item.execute(mHandler, mPendingActions);
 
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
deleted file mode 100644
index da88478..0000000
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.servertransaction;
-
-import static android.app.servertransaction.TestUtils.config;
-import static android.app.servertransaction.TestUtils.mergedConfig;
-import static android.app.servertransaction.TestUtils.referrerIntentList;
-import static android.app.servertransaction.TestUtils.resultInfoList;
-
-import static org.junit.Assert.assertNotSame;
-
-import android.annotation.NonNull;
-import android.app.ActivityOptions;
-import android.app.servertransaction.TestUtils.LaunchActivityItemBuilder;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.res.Configuration;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.PersistableBundle;
-import android.platform.test.annotations.Presubmit;
-import android.window.ActivityWindowInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.function.Supplier;
-
-/**
- * Tests for {@link ObjectPool}.
- *
- * <p>Build/Install/Run:
- *  atest FrameworksCoreTests:ObjectPoolTests
- *
- * <p>This test class is a part of Window Manager Service tests and specified in
- * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class ObjectPoolTests {
-
-    @Rule
-    public final MockitoRule mocks = MockitoJUnit.rule();
-
-    @Mock
-    private IBinder mActivityToken;
-
-    // 1. Check if two obtained objects from pool are not the same.
-    // 2. Check if the state of the object is cleared after recycling.
-    // 3. Check if the same object is obtained from pool after recycling.
-
-    @Test
-    public void testRecycleActivityConfigurationChangeItem() {
-        testRecycle(() -> ActivityConfigurationChangeItem.obtain(mActivityToken, config(),
-                new ActivityWindowInfo()));
-    }
-
-    @Test
-    public void testRecycleActivityResultItem() {
-        testRecycle(() -> ActivityResultItem.obtain(mActivityToken, resultInfoList()));
-    }
-
-    @Test
-    public void testRecycleConfigurationChangeItem() {
-        testRecycle(() -> ConfigurationChangeItem.obtain(config(), 1));
-    }
-
-    @Test
-    public void testRecycleDestroyActivityItem() {
-        testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true));
-    }
-
-    @Test
-    public void testRecycleLaunchActivityItem() {
-        final IBinder activityToken = new Binder();
-        final Intent intent = new Intent("action");
-        final int ident = 57;
-        final ActivityInfo activityInfo = new ActivityInfo();
-        activityInfo.flags = 42;
-        activityInfo.setMaxAspectRatio(2.4f);
-        activityInfo.launchToken = "token";
-        activityInfo.applicationInfo = new ApplicationInfo();
-        activityInfo.packageName = "packageName";
-        activityInfo.name = "name";
-        final Configuration overrideConfig = new Configuration();
-        overrideConfig.assetsSeq = 5;
-        final String referrer = "referrer";
-        final int procState = 4;
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final PersistableBundle persistableBundle = new PersistableBundle();
-        persistableBundle.putInt("k", 4);
-        final IBinder assistToken = new Binder();
-        final IBinder shareableActivityToken = new Binder();
-        final int deviceId = 3;
-        final IBinder taskFragmentToken = new Binder();
-        final IBinder initialCallerInfoAccessToken = new Binder();
-        final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
-
-        testRecycle(() -> new LaunchActivityItemBuilder(
-                activityToken, intent, activityInfo)
-                .setIdent(ident)
-                .setCurConfig(config())
-                .setOverrideConfig(overrideConfig)
-                .setReferrer(referrer)
-                .setProcState(procState)
-                .setState(bundle)
-                .setPersistentState(persistableBundle)
-                .setPendingResults(resultInfoList())
-                .setPendingNewIntents(referrerIntentList())
-                .setIsForward(true)
-                .setAssistToken(assistToken)
-                .setShareableActivityToken(shareableActivityToken)
-                .setTaskFragmentToken(taskFragmentToken)
-                .setDeviceId(deviceId)
-                .setInitialCallerInfoAccessToken(initialCallerInfoAccessToken)
-                .setActivityWindowInfo(activityWindowInfo)
-                .build());
-    }
-
-    @Test
-    public void testRecycleActivityRelaunchItem() {
-        testRecycle(() -> ActivityRelaunchItem.obtain(mActivityToken,
-                resultInfoList(), referrerIntentList(), 42, mergedConfig(), true,
-                new ActivityWindowInfo()));
-    }
-
-    @Test
-    public void testRecycleMoveToDisplayItem() {
-        testRecycle(() -> MoveToDisplayItem.obtain(mActivityToken, 4, config(),
-                new ActivityWindowInfo()));
-    }
-
-    @Test
-    public void testRecycleNewIntentItem() {
-        testRecycle(() -> NewIntentItem.obtain(mActivityToken, referrerIntentList(), false));
-    }
-
-    @Test
-    public void testRecyclePauseActivityItemItem() {
-        testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, true, true));
-    }
-
-    @Test
-    public void testRecycleResumeActivityItem() {
-        testRecycle(() -> ResumeActivityItem.obtain(mActivityToken, 3, true, false));
-    }
-
-    @Test
-    public void testRecycleStartActivityItem() {
-        testRecycle(() -> StartActivityItem.obtain(mActivityToken,
-                new ActivityOptions.SceneTransitionInfo()));
-    }
-
-    @Test
-    public void testRecycleStopItem() {
-        testRecycle(() -> StopActivityItem.obtain(mActivityToken));
-    }
-
-    private void testRecycle(@NonNull Supplier<? extends ObjectPoolItem> obtain) {
-        // Reuse the same object after recycle.
-        final ObjectPoolItem item = obtain.get();
-        item.recycle();
-        final ObjectPoolItem item2 = obtain.get();
-
-        assertNotSame(item, item2);  // Different instance.
-
-        // Create new object when the pool is empty.
-        final ObjectPoolItem item3 = obtain.get();
-
-        assertNotSame(item, item3);
-    }
-}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index c1b9efd..24bc547 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -271,7 +271,7 @@
 
         @NonNull
         LaunchActivityItem build() {
-            return LaunchActivityItem.obtain(mActivityToken, mIntent, mIdent, mInfo,
+            return new LaunchActivityItem(mActivityToken, mIntent, mIdent, mInfo,
                     mCurConfig, mOverrideConfig, mDeviceId, mReferrer, mVoiceInteractor,
                     mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
                     mActivityOptions != null ? mActivityOptions.getSceneTransitionInfo() : null,
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index ad86e75..76a53d4 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -275,7 +275,7 @@
         final IBinder token = mock(IBinder.class);
         final ClientTransaction destroyTransaction = new ClientTransaction();
         destroyTransaction.addTransactionItem(
-                DestroyActivityItem.obtain(token, false /* finished */));
+                new DestroyActivityItem(token, false /* finished */));
         destroyTransaction.preExecute(mTransactionHandler);
         // The activity should be added to to-be-destroyed container.
         assertEquals(1, activitiesToBeDestroyed.size());
@@ -525,10 +525,6 @@
         }
 
         @Override
-        public void recycle() {
-        }
-
-        @Override
         public void writeToParcel(@NonNull Parcel dest, int flags) {
         }
 
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index e995035..59d8c8d 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -74,11 +74,13 @@
     @Test
     public void testConfigurationChange() {
         // Write to parcel
-        ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config(), 1 /* deviceId */);
+        final ConfigurationChangeItem item =
+                new ConfigurationChangeItem(config(), 1 /* deviceId */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
-        ConfigurationChangeItem result = ConfigurationChangeItem.CREATOR.createFromParcel(mParcel);
+        final ConfigurationChangeItem result =
+                ConfigurationChangeItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
         assertEquals(item, result);
@@ -90,7 +92,7 @@
         final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
         activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 500, 1000),
                 new Rect(0, 0, 500, 500));
-        ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(
+        final ActivityConfigurationChangeItem item = new ActivityConfigurationChangeItem(
                 mActivityToken, config(), activityWindowInfo);
         writeAndPrepareForReading(item);
 
@@ -108,12 +110,12 @@
         final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
         activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 500, 1000),
                 new Rect(0, 0, 500, 500));
-        MoveToDisplayItem item = MoveToDisplayItem.obtain(mActivityToken, 4 /* targetDisplayId */,
-                config(), activityWindowInfo);
+        final MoveToDisplayItem item = new MoveToDisplayItem(mActivityToken,
+                4 /* targetDisplayId */, config(), activityWindowInfo);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
-        MoveToDisplayItem result = MoveToDisplayItem.CREATOR.createFromParcel(mParcel);
+        final MoveToDisplayItem result = MoveToDisplayItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
         assertEquals(item, result);
@@ -122,11 +124,12 @@
     @Test
     public void testNewIntent() {
         // Write to parcel
-        NewIntentItem item = NewIntentItem.obtain(mActivityToken, referrerIntentList(), false);
+        final NewIntentItem item =
+                new NewIntentItem(mActivityToken, referrerIntentList(), false /* resume */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
-        NewIntentItem result = NewIntentItem.CREATOR.createFromParcel(mParcel);
+        final NewIntentItem result = NewIntentItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
         assertEquals(item, result);
@@ -135,11 +138,11 @@
     @Test
     public void testActivityResult() {
         // Write to parcel
-        ActivityResultItem item = ActivityResultItem.obtain(mActivityToken, resultInfoList());
+        final ActivityResultItem item = new ActivityResultItem(mActivityToken, resultInfoList());
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
-        ActivityResultItem result = ActivityResultItem.CREATOR.createFromParcel(mParcel);
+        final ActivityResultItem result = ActivityResultItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
         assertEquals(item, result);
@@ -147,11 +150,12 @@
 
     @Test
     public void testDestroy() {
-        DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true /* finished */);
+        final DestroyActivityItem item =
+                new DestroyActivityItem(mActivityToken, true /* finished */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
-        DestroyActivityItem result = DestroyActivityItem.CREATOR.createFromParcel(mParcel);
+        final DestroyActivityItem result = DestroyActivityItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
         assertEquals(item, result);
@@ -162,7 +166,7 @@
         // Write to parcel
         final IBinder activityToken = new Binder();
         final Intent intent = new Intent("action");
-        int ident = 57;
+        final int ident = 57;
         final ActivityInfo activityInfo = new ActivityInfo();
         activityInfo.flags = 42;
         activityInfo.setMaxAspectRatio(2.4f);
@@ -173,7 +177,7 @@
         final Configuration overrideConfig = new Configuration();
         overrideConfig.assetsSeq = 5;
         final String referrer = "referrer";
-        int procState = 4;
+        final int procState = 4;
         final Bundle bundle = new Bundle();
         bundle.putString("key", "value");
         bundle.putParcelable("data", new ParcelableData(1));
@@ -215,17 +219,17 @@
     @Test
     public void testRelaunch() {
         // Write to parcel
-        Configuration overrideConfig = new Configuration();
+        final Configuration overrideConfig = new Configuration();
         overrideConfig.assetsSeq = 5;
         final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
         activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 500, 1000),
                 new Rect(0, 0, 500, 500));
-        ActivityRelaunchItem item = ActivityRelaunchItem.obtain(mActivityToken, resultInfoList(),
+        final ActivityRelaunchItem item = new ActivityRelaunchItem(mActivityToken, resultInfoList(),
                 referrerIntentList(), 35, mergedConfig(), true, activityWindowInfo);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
-        ActivityRelaunchItem result = ActivityRelaunchItem.CREATOR.createFromParcel(mParcel);
+        final ActivityRelaunchItem result = ActivityRelaunchItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
         assertEquals(item, result);
@@ -234,12 +238,12 @@
     @Test
     public void testPause() {
         // Write to parcel
-        PauseActivityItem item = PauseActivityItem.obtain(mActivityToken, true /* finished */,
+        final PauseActivityItem item = new PauseActivityItem(mActivityToken, true /* finished */,
                 true /* userLeaving */, true /* dontReport */, true /* autoEnteringPip */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
-        PauseActivityItem result = PauseActivityItem.CREATOR.createFromParcel(mParcel);
+        final PauseActivityItem result = PauseActivityItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
         assertEquals(item, result);
@@ -248,12 +252,12 @@
     @Test
     public void testResume() {
         // Write to parcel
-        ResumeActivityItem item = ResumeActivityItem.obtain(mActivityToken, 27 /* procState */,
+        final ResumeActivityItem item = new ResumeActivityItem(mActivityToken, 27 /* procState */,
                 true /* isForward */, false /* shouldSendCompatFakeFocus */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
-        ResumeActivityItem result = ResumeActivityItem.CREATOR.createFromParcel(mParcel);
+        final ResumeActivityItem result = ResumeActivityItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
         assertEquals(item, result);
@@ -262,11 +266,11 @@
     @Test
     public void testStop() {
         // Write to parcel
-        StopActivityItem item = StopActivityItem.obtain(mActivityToken);
+        final StopActivityItem item = new StopActivityItem(mActivityToken);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
-        StopActivityItem result = StopActivityItem.CREATOR.createFromParcel(mParcel);
+        final StopActivityItem result = StopActivityItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
         assertEquals(item, result);
@@ -275,12 +279,12 @@
     @Test
     public void testStart() {
         // Write to parcel
-        StartActivityItem item = StartActivityItem.obtain(mActivityToken,
+        final StartActivityItem item = new StartActivityItem(mActivityToken,
                 new ActivityOptions.SceneTransitionInfo());
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
-        StartActivityItem result = StartActivityItem.CREATOR.createFromParcel(mParcel);
+        final StartActivityItem result = StartActivityItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
         assertEquals(item, result);
@@ -289,11 +293,12 @@
     @Test
     public void testClientTransaction() {
         // Write to parcel
-        NewIntentItem callback1 = NewIntentItem.obtain(mActivityToken, new ArrayList<>(), true);
-        ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
+        final NewIntentItem callback1 =
+                new NewIntentItem(mActivityToken, new ArrayList<>(), true /* resume */);
+        final ActivityConfigurationChangeItem callback2 = new ActivityConfigurationChangeItem(
                 mActivityToken, config(), new ActivityWindowInfo());
 
-        StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken);
+        final StopActivityItem lifecycleRequest = new StopActivityItem(mActivityToken);
 
         final ClientTransaction transaction = new ClientTransaction();
         transaction.addTransactionItem(callback1);
@@ -303,7 +308,7 @@
         writeAndPrepareForReading(transaction);
 
         // Read from parcel and assert
-        ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
+        final ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
 
         assertEquals(transaction.hashCode(), result.hashCode());
         assertEquals(transaction, result);
@@ -324,9 +329,7 @@
      *   android.app.servertransaction.TransactionParcelTests$ParcelableData".
      */
     public static class ParcelableData implements Parcelable {
-        int mValue;
-
-        ParcelableData() {}
+        private final int mValue;
 
         ParcelableData(int value) {
             mValue = value;
@@ -342,12 +345,10 @@
             dest.writeInt(mValue);
         }
 
-        public static final Creator<ParcelableData> CREATOR = new Creator<ParcelableData>() {
+        public static final Creator<ParcelableData> CREATOR = new Creator<>() {
             @Override
             public ParcelableData createFromParcel(Parcel source) {
-                final ParcelableData data = new ParcelableData();
-                data.mValue = source.readInt();
-                return data;
+                return new ParcelableData(source.readInt());
             }
 
             @Override
diff --git a/core/tests/coretests/src/android/view/ViewGroupTest.java b/core/tests/coretests/src/android/view/ViewGroupTest.java
index bce3f3e..ae3ad36 100644
--- a/core/tests/coretests/src/android/view/ViewGroupTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupTest.java
@@ -19,6 +19,7 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
@@ -29,11 +30,16 @@
 import android.content.Context;
 import android.graphics.Region;
 import android.platform.test.annotations.Presubmit;
+import android.view.autofill.AutofillId;
 
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 
 /**
  * Test basic functions of ViewGroup.
@@ -182,6 +188,31 @@
         assertRegionContainPoint(3 /* x */, r, false /* contain */); // Outside of bounds
     }
 
+    @Test
+    public void testfindAutofillableViewsByTraversal() {
+        final Context context = getInstrumentation().getContext();
+        final TestView viewGroup = new TestView(context, 200 /* right */);
+
+        // viewA and viewC are autofillable. ViewB isn't.
+        final TestView viewA = spy(new AutofillableTestView(context, 100 /* right */));
+        final TestView viewB = spy(new NonAutofillableTestView(context, 200 /* right */));
+        final TestView viewC = spy(new AutofillableTestView(context, 300 /* right */));
+
+        viewGroup.addView(viewA);
+        viewGroup.addView(viewB);
+        viewGroup.addView(viewC);
+
+        List<View> autofillableViews = new ArrayList<>();
+        viewGroup.findAutofillableViewsByTraversal(autofillableViews);
+
+        verify(viewA).findAutofillableViewsByTraversal(autofillableViews);
+        verify(viewB).findAutofillableViewsByTraversal(autofillableViews);
+        verify(viewC).findAutofillableViewsByTraversal(autofillableViews);
+
+        assertEquals("Size of autofillable views", 2, autofillableViews.size());
+        assertTrue(autofillableViews.containsAll(Arrays.asList(viewA, viewC)));
+    }
+
     private static void getUnobscuredTouchableRegion(Region outRegion, View view) {
         outRegion.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
         final ViewParent parent = view.getParent();
@@ -210,4 +241,29 @@
             // We don't layout this view.
         }
     }
+
+    public static class AutofillableTestView extends TestView {
+        AutofillableTestView(Context context, int right) {
+            super(context, right);
+            setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
+            // Need to set autofill id in such a way that the view is considered part of activity.
+            setAutofillId(new AutofillId(LAST_APP_AUTOFILL_ID + 5));
+        }
+
+        @Override
+        public @AutofillType int getAutofillType() {
+            return AUTOFILL_TYPE_TEXT;
+        }
+    }
+
+    public static class NonAutofillableTestView extends TestView {
+        NonAutofillableTestView(Context context, int right) {
+            super(context, right);
+        }
+
+        @Override
+        public @AutofillType int getAutofillType() {
+            return AUTOFILL_TYPE_NONE;
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt b/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt
new file mode 100644
index 0000000..44d10d3
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.widget
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Bitmap
+import android.graphics.BlendMode
+import android.graphics.Color
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Icon
+import android.util.proto.ProtoInputStream
+import android.util.proto.ProtoOutputStream
+import android.widget.RemoteViewsSerializers.createIconFromProto
+import android.widget.RemoteViewsSerializers.writeIconToProto
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.frameworks.coretests.R
+import com.google.common.truth.Truth.assertThat
+import java.io.ByteArrayOutputStream
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RemoteViewsSerializersTest {
+    private val context = ApplicationProvider.getApplicationContext<Context>()
+
+    /**
+     * Based on android.graphics.drawable.IconTest#testParcel
+     */
+    @Test
+    fun testWriteIconToProto() {
+        val bitmap = (context.getDrawable(R.drawable.landscape) as BitmapDrawable).bitmap
+        val bitmapData = ByteArrayOutputStream().let {
+            bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
+            it.toByteArray()
+        }
+
+        for (icon in listOf(
+            Icon.createWithBitmap(bitmap),
+            Icon.createWithAdaptiveBitmap(bitmap),
+            Icon.createWithData(bitmapData, 0, bitmapData.size),
+            Icon.createWithResource(context, R.drawable.landscape),
+            Icon.createWithContentUri("content://com.example.myapp/my_icon"),
+            Icon.createWithAdaptiveBitmapContentUri("content://com.example.myapp/my_icon"),
+        )) {
+            icon.tintList = ColorStateList.valueOf(Color.RED)
+            icon.tintBlendMode = BlendMode.SRC_OVER
+            val bytes = ProtoOutputStream().let {
+                writeIconToProto(it, context.resources, icon)
+                it.bytes
+            }
+
+            val copy = ProtoInputStream(bytes).let {
+                createIconFromProto(it).apply(context.resources)
+            }
+            assertThat(copy.type).isEqualTo(icon.type)
+            assertThat(copy.tintBlendMode).isEqualTo(icon.tintBlendMode)
+            assertThat(equalColorStateLists(copy.tintList, icon.tintList)).isTrue()
+
+            when (icon.type) {
+                Icon.TYPE_DATA, Icon.TYPE_URI, Icon.TYPE_URI_ADAPTIVE_BITMAP,
+                Icon.TYPE_RESOURCE -> {
+                    assertThat(copy.sameAs(icon)).isTrue()
+                }
+
+                Icon.TYPE_BITMAP, Icon.TYPE_ADAPTIVE_BITMAP -> {
+                    assertThat(copy.bitmap.sameAs(icon.bitmap)).isTrue()
+                }
+            }
+        }
+    }
+}
+
+fun equalColorStateLists(a: ColorStateList?, b: ColorStateList?): Boolean {
+    if (a == null && b == null) return true
+    return a != null && b != null &&
+            a.colors.contentEquals(b.colors) &&
+            a.states.foldIndexed(true) { i, acc, it -> acc && it.contentEquals(b.states[i])}
+}
diff --git a/core/tests/vibrator/Android.bp b/core/tests/vibrator/Android.bp
index 3ebe150..920ab59 100644
--- a/core/tests/vibrator/Android.bp
+++ b/core/tests/vibrator/Android.bp
@@ -18,6 +18,7 @@
         "androidx.test.ext.junit",
         "androidx.test.runner",
         "androidx.test.rules",
+        "flag-junit",
         "mockito-target-minus-junit4",
         "truth",
         "testng",
diff --git a/core/tests/vibrator/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
index e875875..098ade4 100644
--- a/core/tests/vibrator/src/android/os/VibrationEffectTest.java
+++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
@@ -20,6 +20,8 @@
 import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
 import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
@@ -29,6 +31,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertNotEquals;
 import static org.testng.Assert.assertThrows;
 
 import android.content.ContentInterface;
@@ -38,8 +41,12 @@
 import android.hardware.vibrator.IVibrator;
 import android.net.Uri;
 import android.os.VibrationEffect.Composition.UnreachableAfterRepeatingIndefinitelyException;
+import android.os.vibrator.Flags;
+import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 
 import com.android.internal.R;
 
@@ -284,10 +291,13 @@
     }
 
     @Test
-    public void computeLegacyPattern_notPatternPased() {
-        VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
-
-        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    public void computeLegacyPattern_notPatternBased() {
+        assertNull(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
+                .computeCreateWaveformOffOnTimingsOrNull());
+        if (Flags.vendorVibrationEffects()) {
+            assertNull(VibrationEffect.createVendorEffect(createNonEmptyBundle())
+                    .computeCreateWaveformOffOnTimingsOrNull());
+        }
     }
 
     @Test
@@ -472,6 +482,18 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void testValidateVendorEffect() {
+        PersistableBundle vendorData = new PersistableBundle();
+        vendorData.putInt("key", 1);
+        VibrationEffect.createVendorEffect(vendorData).validate();
+
+        PersistableBundle emptyData = new PersistableBundle();
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.createVendorEffect(emptyData).validate());
+    }
+
+    @Test
     public void testValidateWaveform() {
         VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1).validate();
         VibrationEffect.createWaveform(new long[]{10, 10}, new int[] {0, 0}, -1).validate();
@@ -634,16 +656,16 @@
 
     @Test
     public void testResolveOneShot() {
-        VibrationEffect.Composed resolved = DEFAULT_ONE_SHOT.resolve(51);
-        assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude());
+        VibrationEffect resolved = DEFAULT_ONE_SHOT.resolve(51);
+        assertEquals(0.2f, getStepSegment(resolved, 0).getAmplitude());
 
         assertThrows(IllegalArgumentException.class, () -> DEFAULT_ONE_SHOT.resolve(1000));
     }
 
     @Test
     public void testResolveWaveform() {
-        VibrationEffect.Composed resolved = TEST_WAVEFORM.resolve(102);
-        assertEquals(0.4f, ((StepSegment) resolved.getSegments().get(2)).getAmplitude());
+        VibrationEffect resolved = TEST_WAVEFORM.resolve(102);
+        assertEquals(0.4f, getStepSegment(resolved, 2).getAmplitude());
 
         assertThrows(IllegalArgumentException.class, () -> TEST_WAVEFORM.resolve(1000));
     }
@@ -655,63 +677,127 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void testResolveVendorEffect() {
+        VibrationEffect effect = VibrationEffect.createVendorEffect(createNonEmptyBundle());
+        assertEquals(effect, effect.resolve(51));
+    }
+
+    @Test
     public void testResolveComposed() {
         VibrationEffect effect = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1)
                 .compose();
         assertEquals(effect, effect.resolve(51));
 
-        VibrationEffect.Composed resolved = VibrationEffect.startComposition()
+        VibrationEffect resolved = VibrationEffect.startComposition()
                 .addEffect(DEFAULT_ONE_SHOT)
                 .compose()
                 .resolve(51);
-        assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude());
+        assertEquals(0.2f, getStepSegment(resolved, 0).getAmplitude());
     }
 
     @Test
     public void testScaleOneShot() {
-        VibrationEffect.Composed scaledUp = TEST_ONE_SHOT.scale(1.5f);
-        assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude());
+        VibrationEffect scaledUp = TEST_ONE_SHOT.scale(1.5f);
+        assertTrue(100 / 255f < getStepSegment(scaledUp, 0).getAmplitude());
 
-        VibrationEffect.Composed scaledDown = TEST_ONE_SHOT.scale(0.5f);
-        assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude());
+        VibrationEffect scaledDown = TEST_ONE_SHOT.scale(0.5f);
+        assertTrue(100 / 255f > getStepSegment(scaledDown, 0).getAmplitude());
     }
 
     @Test
     public void testScaleWaveform() {
-        VibrationEffect.Composed scaledUp = TEST_WAVEFORM.scale(1.5f);
-        assertEquals(1f, ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude(), 1e-5f);
+        VibrationEffect scaledUp = TEST_WAVEFORM.scale(1.5f);
+        assertEquals(1f, getStepSegment(scaledUp, 0).getAmplitude(), 1e-5f);
 
-        VibrationEffect.Composed scaledDown = TEST_WAVEFORM.scale(0.5f);
-        assertTrue(1f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude());
+        VibrationEffect scaledDown = TEST_WAVEFORM.scale(0.5f);
+        assertTrue(1f > getStepSegment(scaledDown, 0).getAmplitude());
     }
 
     @Test
     public void testScalePrebaked() {
         VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
 
-        VibrationEffect.Composed scaledUp = effect.scale(1.5f);
+        VibrationEffect scaledUp = effect.scale(1.5f);
         assertEquals(effect, scaledUp);
 
-        VibrationEffect.Composed scaledDown = effect.scale(0.5f);
+        VibrationEffect scaledDown = effect.scale(0.5f);
+        assertEquals(effect, scaledDown);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void testScaleVendorEffect() {
+        VibrationEffect effect = VibrationEffect.createVendorEffect(createNonEmptyBundle());
+
+        VibrationEffect scaledUp = effect.scale(1.5f);
+        assertEquals(effect, scaledUp);
+
+        VibrationEffect scaledDown = effect.scale(0.5f);
         assertEquals(effect, scaledDown);
     }
 
     @Test
     public void testScaleComposed() {
-        VibrationEffect.Composed effect =
-                (VibrationEffect.Composed) VibrationEffect.startComposition()
+        VibrationEffect effect = VibrationEffect.startComposition()
                     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
                     .addEffect(TEST_ONE_SHOT)
                     .compose();
 
-        VibrationEffect.Composed scaledUp = effect.scale(1.5f);
-        assertTrue(0.5f < ((PrimitiveSegment) scaledUp.getSegments().get(0)).getScale());
-        assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(1)).getAmplitude());
+        VibrationEffect scaledUp = effect.scale(1.5f);
+        assertTrue(0.5f < getPrimitiveSegment(scaledUp, 0).getScale());
+        assertTrue(100 / 255f < getStepSegment(scaledUp, 1).getAmplitude());
 
-        VibrationEffect.Composed scaledDown = effect.scale(0.5f);
-        assertTrue(0.5f > ((PrimitiveSegment) scaledDown.getSegments().get(0)).getScale());
-        assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(1)).getAmplitude());
+        VibrationEffect scaledDown = effect.scale(0.5f);
+        assertTrue(0.5f > getPrimitiveSegment(scaledDown, 0).getScale());
+        assertTrue(100 / 255f > getStepSegment(scaledDown, 1).getAmplitude());
+    }
+
+    @Test
+    public void testApplyEffectStrengthToOneShotWaveformAndPrimitives() {
+        VibrationEffect oneShot = VibrationEffect.createOneShot(100, 100);
+        VibrationEffect waveform = VibrationEffect.createWaveform(new long[] { 10, 20 }, 0);
+        VibrationEffect composition = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .compose();
+
+        assertEquals(oneShot, oneShot.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG));
+        assertEquals(waveform,
+                waveform.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG));
+        assertEquals(composition,
+                composition.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG));
+    }
+
+    @Test
+    public void testApplyEffectStrengthToPredefinedEffect() {
+        VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
+
+        VibrationEffect scaledUp =
+                effect.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG);
+        assertNotEquals(effect, scaledUp);
+        assertEquals(VibrationEffect.EFFECT_STRENGTH_STRONG,
+                getPrebakedSegment(scaledUp, 0).getEffectStrength());
+
+        VibrationEffect scaledDown =
+                effect.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT);
+        assertNotEquals(effect, scaledDown);
+        assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT,
+                getPrebakedSegment(scaledDown, 0).getEffectStrength());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void testApplyEffectStrengthToVendorEffect() {
+        VibrationEffect effect = VibrationEffect.createVendorEffect(createNonEmptyBundle());
+
+        VibrationEffect scaledUp =
+                effect.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG);
+        assertNotEquals(effect, scaledUp);
+
+        VibrationEffect scaledDown =
+                effect.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT);
+        assertNotEquals(effect, scaledDown);
     }
 
     private void doTestApplyRepeatingWithNonRepeatingOriginal(@NotNull VibrationEffect original) {
@@ -819,6 +905,15 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void testApplyRepeatingIndefinitely_vendorEffect() {
+        VibrationEffect effect = VibrationEffect.createVendorEffect(createNonEmptyBundle());
+
+        assertEquals(effect, effect.applyRepeatingIndefinitely(true, 10));
+        assertEquals(effect, effect.applyRepeatingIndefinitely(false, 10));
+    }
+
+    @Test
     public void testDuration() {
         assertEquals(1, VibrationEffect.createOneShot(1, 1).getDuration());
         assertEquals(-1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK).getDuration());
@@ -832,6 +927,10 @@
                 new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1).getDuration());
         assertEquals(Long.MAX_VALUE, VibrationEffect.createWaveform(
                 new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0).getDuration());
+        if (Flags.vendorVibrationEffects()) {
+            assertEquals(-1,
+                    VibrationEffect.createVendorEffect(createNonEmptyBundle()).getDuration());
+        }
     }
 
     @Test
@@ -872,6 +971,19 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void testAreVibrationFeaturesSupported_vendorEffects() {
+        VibratorInfo supportedVibratorInfo = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS)
+                .build();
+
+        assertTrue(VibrationEffect.createVendorEffect(createNonEmptyBundle())
+                .areVibrationFeaturesSupported(supportedVibratorInfo));
+        assertFalse(VibrationEffect.createVendorEffect(createNonEmptyBundle())
+                .areVibrationFeaturesSupported(new VibratorInfo.Builder(/* id= */ 1).build()));
+    }
+
+    @Test
     public void testIsHapticFeedbackCandidate_repeatingEffects_notCandidates() {
         assertFalse(VibrationEffect.createWaveform(
                 new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0).isHapticFeedbackCandidate());
@@ -952,6 +1064,13 @@
         assertTrue(VibrationEffect.get(VibrationEffect.EFFECT_TICK).isHapticFeedbackCandidate());
     }
 
+    @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void testIsHapticFeedbackCandidate_vendorEffects_notCandidates() {
+        assertFalse(VibrationEffect.createVendorEffect(createNonEmptyBundle())
+                .isHapticFeedbackCandidate());
+    }
+
     private void assertArrayEq(long[] expected, long[] actual) {
         assertTrue(
                 String.format("Expected pattern %s, but was %s",
@@ -992,4 +1111,35 @@
 
         return context;
     }
+
+    private StepSegment getStepSegment(VibrationEffect effect, int index) {
+        VibrationEffectSegment segment = getEffectSegment(effect, index);
+        assertThat(segment).isInstanceOf(StepSegment.class);
+        return (StepSegment) segment;
+    }
+
+    private PrimitiveSegment getPrimitiveSegment(VibrationEffect effect, int index) {
+        VibrationEffectSegment segment = getEffectSegment(effect, index);
+        assertThat(segment).isInstanceOf(PrimitiveSegment.class);
+        return (PrimitiveSegment) segment;
+    }
+
+    private PrebakedSegment getPrebakedSegment(VibrationEffect effect, int index) {
+        VibrationEffectSegment segment = getEffectSegment(effect, index);
+        assertThat(segment).isInstanceOf(PrebakedSegment.class);
+        return (PrebakedSegment) segment;
+    }
+
+    private VibrationEffectSegment getEffectSegment(VibrationEffect effect, int index) {
+        assertThat(effect).isInstanceOf(VibrationEffect.Composed.class);
+        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+        assertThat(index).isLessThan(composed.getSegments().size());
+        return composed.getSegments().get(index);
+    }
+
+    private PersistableBundle createNonEmptyBundle() {
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putInt("key", 1);
+        return bundle;
+    }
 }
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index a1ba24c..282385a 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -89,20 +89,15 @@
     private static final int DESKTOP_DENSITY_MAX = 1000;
 
     /**
-     * Default value for {@code MAX_TASK_LIMIT}.
-     */
-    @VisibleForTesting
-    public static final int DEFAULT_MAX_TASK_LIMIT = 4;
-
-    // TODO(b/335131008): add a config-overlay field for the max number of tasks in Desktop Mode
-    /**
-     * Flag declaring the maximum number of Tasks to show in Desktop Mode at any one time.
+     * Sysprop declaring the maximum number of Tasks to show in Desktop Mode at any one time.
      *
-     * <p> The limit does NOT affect Picture-in-Picture, Bubbles, or System Modals (like a screen
+     * <p>If it is not defined, then {@code R.integer.config_maxDesktopWindowingActiveTasks} is
+     * used.
+     *
+     * <p>The limit does NOT affect Picture-in-Picture, Bubbles, or System Modals (like a screen
      * recording window, or Bluetooth pairing window).
      */
-    private static final int MAX_TASK_LIMIT = SystemProperties.getInt(
-            "persist.wm.debug.desktop_max_task_limit", DEFAULT_MAX_TASK_LIMIT);
+    private static final String MAX_TASK_LIMIT_SYS_PROP = "persist.wm.debug.desktop_max_task_limit";
 
     /**
      * Return {@code true} if veiled resizing is active. If false, fluid resizing is used.
@@ -139,8 +134,9 @@
     /**
      * Return the maximum limit on the number of Tasks to show in Desktop Mode at any one time.
      */
-    public static int getMaxTaskLimit() {
-        return MAX_TASK_LIMIT;
+    public static int getMaxTaskLimit(@NonNull Context context) {
+        return SystemProperties.getInt(MAX_TASK_LIMIT_SYS_PROP,
+                context.getResources().getInteger(R.integer.config_maxDesktopWindowingActiveTasks));
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index fca8a62..949a723 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -734,6 +734,9 @@
     public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
         if (canShowAsBubbleBar()) {
             mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
+            if (mLayerView != null && !mLayerView.isExpandedViewDragged()) {
+                mLayerView.updateExpandedView();
+            }
             BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
             bubbleBarUpdate.bubbleBarLocation = bubbleBarLocation;
             mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index fa1091c..d45ed0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -38,6 +38,9 @@
     var isStuckToDismiss: Boolean = false
         private set
 
+    var isDragged: Boolean = false
+        private set
+
     private var expandedViewInitialTranslationX = 0f
     private var expandedViewInitialTranslationY = 0f
     private val magnetizedExpandedView: MagnetizedObject<BubbleBarExpandedView> =
@@ -94,6 +97,7 @@
             // While animating, don't allow new touch events
             if (expandedView.isAnimating) return false
             pinController.onDragStart(bubblePositioner.isBubbleBarOnLeft)
+            isDragged = true
             return true
         }
 
@@ -141,6 +145,7 @@
                 dismissView.hide()
             }
             isMoving = false
+            isDragged = false
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index badc409..9fa85cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -175,6 +175,11 @@
         return mIsExpanded;
     }
 
+    /** Return whether the expanded view is being dragged */
+    public boolean isExpandedViewDragged() {
+        return mDragController != null && mDragController.isDragged();
+    }
+
     /** Shows the expanded view of the provided bubble. */
     public void showExpandedView(BubbleViewProvider b) {
         BubbleBarExpandedView expandedView = b.getBubbleBarExpandedView();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 8f587d4..afe46f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -567,13 +567,15 @@
             Transitions transitions,
             @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
             ShellTaskOrganizer shellTaskOrganizer) {
+        int maxTaskLimit = DesktopModeStatus.getMaxTaskLimit(context);
         if (!DesktopModeStatus.canEnterDesktopMode(context)
-                || DESKTOP_WINDOWING_MODE.isEnabled(context)) {
+                || DESKTOP_WINDOWING_MODE.isEnabled(context)
+                || maxTaskLimit <= 0) {
             return Optional.empty();
         }
         return Optional.of(
                 new DesktopTasksLimiter(
-                        transitions, desktopModeTaskRepository, shellTaskOrganizer));
+                        transitions, desktopModeTaskRepository, shellTaskOrganizer, maxTaskLimit));
     }
 
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 534cc22..c5ed1be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -26,7 +26,6 @@
 import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.protolog.ShellProtoLogGroup
-import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TransitionObserver
 
@@ -40,12 +39,17 @@
         transitions: Transitions,
         private val taskRepository: DesktopModeTaskRepository,
         private val shellTaskOrganizer: ShellTaskOrganizer,
+        private val maxTasksLimit: Int,
 ) {
     private val minimizeTransitionObserver = MinimizeTransitionObserver()
     @VisibleForTesting
     val leftoverMinimizedTasksRemover = LeftoverMinimizedTasksRemover()
 
     init {
+        require(maxTasksLimit > 0) {
+            "DesktopTasksLimiter should not be created with a maxTasksLimit at 0 or less. " +
+                    "Current value: $maxTasksLimit."
+        }
         transitions.registerObserver(minimizeTransitionObserver)
         taskRepository.addActiveTaskListener(leftoverMinimizedTasksRemover)
     }
@@ -194,12 +198,6 @@
     }
 
     /**
-     * Returns the maximum number of tasks that should ever be displayed at the same time in Desktop
-     * Mode.
-     */
-    fun getMaxTaskLimit(): Int = DesktopModeStatus.getMaxTaskLimit()
-
-    /**
      * Returns the Task to minimize given 1. a list of visible tasks ordered from front to back and
      * 2. a new task placed in front of all the others.
      */
@@ -216,7 +214,7 @@
     fun getTaskToMinimizeIfNeeded(
             visibleFreeformTaskIdsOrderedFrontToBack: List<Int>
     ): RunningTaskInfo? {
-        if (visibleFreeformTaskIdsOrderedFrontToBack.size <= getMaxTaskLimit()) {
+        if (visibleFreeformTaskIdsOrderedFrontToBack.size <= maxTasksLimit) {
             ProtoLog.v(
                     ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                     "DesktopTasksLimiter: no need to minimize; tasks below limit")
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 37510ef4..e66018f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -126,11 +126,11 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.kotlin.anyOrNull
 import org.mockito.kotlin.atLeastOnce
 import org.mockito.kotlin.capture
 import org.mockito.quality.Strictness
-import org.mockito.Mockito.`when` as whenever
 
 /**
  * Test class for {@link DesktopTasksController}
@@ -204,7 +204,12 @@
     shellInit = spy(ShellInit(testExecutor))
     desktopModeTaskRepository = DesktopModeTaskRepository()
     desktopTasksLimiter =
-        DesktopTasksLimiter(transitions, desktopModeTaskRepository, shellTaskOrganizer)
+        DesktopTasksLimiter(
+            transitions,
+            desktopModeTaskRepository,
+            shellTaskOrganizer,
+            MAX_TASK_LIMIT,
+        )
 
     whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
     whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
@@ -780,45 +785,41 @@
   @Test
   @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun moveToDesktop_desktopWallpaperDisabled_bringsTasksOver_dontShowBackTask() {
-    val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
-    val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
+    val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
     val newTask = setUpFullscreenTask()
     val homeTask = setUpHomeTask()
 
     controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
 
     val wct = getLatestEnterDesktopWct()
-    assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + home
+    assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 1) // visible tasks + home
     wct.assertReorderAt(0, homeTask)
     wct.assertReorderSequenceInRange(
-      range = 1..<(taskLimit + 1),
-      *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
-      newTask
-    )
+        range = 1..<(MAX_TASK_LIMIT + 1),
+        *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
+        newTask)
   }
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun moveToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
-    val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
-    val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
+    val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
     val newTask = setUpFullscreenTask()
     val homeTask = setUpHomeTask()
 
     controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
 
     val wct = getLatestEnterDesktopWct()
-    assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 2) // tasks + home + wallpaper
+    assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 2) // tasks + home + wallpaper
     // Move home to front
     wct.assertReorderAt(0, homeTask)
     // Add desktop wallpaper activity
     wct.assertPendingIntentAt(1, desktopWallpaperIntent)
     // Bring freeform tasks to front
     wct.assertReorderSequenceInRange(
-      range = 2..<(taskLimit + 2),
-      *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
-      newTask
-    )
+        range = 2..<(MAX_TASK_LIMIT + 2),
+        *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
+        newTask)
   }
 
   @Test
@@ -932,9 +933,8 @@
 
   @Test
   fun moveTaskToFront_bringsTasksOverLimit_minimizesBackTask() {
-    val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
     setUpHomeTask()
-    val freeformTasks = (1..taskLimit + 1).map { _ -> setUpFreeformTask() }
+    val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() }
 
     controller.moveTaskToFront(freeformTasks[0])
 
@@ -1141,8 +1141,7 @@
   fun handleRequest_fullscreenTaskToFreeform_bringsTasksOverLimit_otherTaskIsMinimized() {
     assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
-    val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
-    val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
+    val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
     freeformTasks.forEach { markTaskVisible(it) }
     val fullscreenTask = createFullscreenTask()
 
@@ -1187,8 +1186,7 @@
   fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_minimize() {
     assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
-    val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
-    val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
+    val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
     freeformTasks.forEach { markTaskVisible(it) }
     val newFreeformTask = createFreeformTask()
 
@@ -2599,9 +2597,10 @@
     return TransitionRequestInfo(type, task, null /* remoteTransition */)
   }
 
-  companion object {
+  private companion object {
     const val SECOND_DISPLAY = 2
-    private val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
+    val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
+    const val MAX_TASK_LIMIT = 6
   }
 }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 8d9fd91..70f3bf8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -38,6 +38,7 @@
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.util.StubTransaction
 import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -77,8 +78,8 @@
 
         desktopTaskRepo = DesktopModeTaskRepository()
 
-        desktopTasksLimiter = DesktopTasksLimiter(
-                transitions, desktopTaskRepo, shellTaskOrganizer)
+        desktopTasksLimiter =
+            DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT)
     }
 
     @After
@@ -86,12 +87,18 @@
         mockitoSession.finishMocking()
     }
 
-    // Currently, the task limit can be overridden through an adb flag. This test ensures the limit
-    // hasn't been overridden.
     @Test
-    fun getMaxTaskLimit_isSameAsConstant() {
-        assertThat(desktopTasksLimiter.getMaxTaskLimit()).isEqualTo(
-            DesktopModeStatus.DEFAULT_MAX_TASK_LIMIT)
+    fun createDesktopTasksLimiter_withZeroLimit_shouldThrow() {
+        assertFailsWith<IllegalArgumentException> {
+            DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, 0)
+        }
+    }
+
+    @Test
+    fun createDesktopTasksLimiter_withNegativeLimit_shouldThrow() {
+        assertFailsWith<IllegalArgumentException> {
+            DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, -5)
+        }
     }
 
     @Test
@@ -247,8 +254,7 @@
 
     @Test
     fun addAndGetMinimizeTaskChangesIfNeeded_tasksWithinLimit_noTaskMinimized() {
-        val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
-        (1..<taskLimit).forEach { _ -> setUpFreeformTask() }
+        (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
 
         val wct = WindowContainerTransaction()
         val minimizedTaskId =
@@ -263,9 +269,8 @@
 
     @Test
     fun addAndGetMinimizeTaskChangesIfNeeded_tasksAboveLimit_backTaskMinimized() {
-        val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
         // The following list will be ordered bottom -> top, as the last task is moved to top last.
-        val tasks = (1..taskLimit).map { setUpFreeformTask() }
+        val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
 
         val wct = WindowContainerTransaction()
         val minimizedTaskId =
@@ -282,8 +287,7 @@
 
     @Test
     fun addAndGetMinimizeTaskChangesIfNeeded_nonMinimizedTasksWithinLimit_noTaskMinimized() {
-        val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
-        val tasks = (1..taskLimit).map { setUpFreeformTask() }
+        val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
         desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = tasks[0].taskId)
 
         val wct = WindowContainerTransaction()
@@ -299,8 +303,7 @@
 
     @Test
     fun getTaskToMinimizeIfNeeded_tasksWithinLimit_returnsNull() {
-        val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
-        val tasks = (1..taskLimit).map { setUpFreeformTask() }
+        val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
 
         val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded(
                 visibleFreeformTaskIdsOrderedFrontToBack = tasks.map { it.taskId })
@@ -310,8 +313,7 @@
 
     @Test
     fun getTaskToMinimizeIfNeeded_tasksAboveLimit_returnsBackTask() {
-        val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
-        val tasks = (1..taskLimit + 1).map { setUpFreeformTask() }
+        val tasks = (1..MAX_TASK_LIMIT + 1).map { setUpFreeformTask() }
 
         val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded(
                 visibleFreeformTaskIdsOrderedFrontToBack = tasks.map { it.taskId })
@@ -321,9 +323,21 @@
     }
 
     @Test
+    fun getTaskToMinimizeIfNeeded_tasksAboveLimit_otherLimit_returnsBackTask() {
+        desktopTasksLimiter =
+            DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT2)
+        val tasks = (1..MAX_TASK_LIMIT2 + 1).map { setUpFreeformTask() }
+
+        val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded(
+            visibleFreeformTaskIdsOrderedFrontToBack = tasks.map { it.taskId })
+
+        // first == front, last == back
+        assertThat(minimizedTask).isEqualTo(tasks.last())
+    }
+
+    @Test
     fun getTaskToMinimizeIfNeeded_withNewTask_tasksAboveLimit_returnsBackTask() {
-        val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
-        val tasks = (1..taskLimit).map { setUpFreeformTask() }
+        val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
 
         val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded(
                 visibleFreeformTaskIdsOrderedFrontToBack = tasks.map { it.taskId },
@@ -358,4 +372,9 @@
                 visible = false
         )
     }
+
+    private companion object {
+        const val MAX_TASK_LIMIT = 6
+        const val MAX_TASK_LIMIT2 = 9
+    }
 }
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 2fff4f5..a39f30b 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -44,6 +44,9 @@
         "-Werror",
         "-Wunreachable-code",
     ],
+    header_libs: [
+        "native_headers",
+    ],
     target: {
         windows: {
             // The Windows compiler warns incorrectly for value initialization with {}.
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index 6744c6c..aebc4db 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -162,8 +162,8 @@
 
 static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/,
         jobject fileDescriptor, jlong length, jboolean preferAnimation, jobject source) {
-#ifndef __ANDROID__ // LayoutLib for Windows does not support F_DUPFD_CLOEXEC
-    return throw_exception(env, kSourceException, "Only supported on Android", nullptr, source);
+#ifdef _WIN32  // LayoutLib for Windows does not support F_DUPFD_CLOEXEC
+    return throw_exception(env, kSourceException, "Not supported on Windows", nullptr, source);
 #else
     int descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
 
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index f64233f..6776f61 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -24,7 +24,6 @@
 #include <utils/String8.h>
 #include <utils/Thread.h>
 
-#include <gui/IProducerListener.h>
 #include <gui/Surface.h>
 #include <ui/PublicFormat.h>
 #include <android_runtime/AndroidRuntime.h>
@@ -65,15 +64,18 @@
 
 // ----------------------------------------------------------------------------
 
-class JNIImageWriterContext : public BnProducerListener {
+class JNIImageWriterContext : public SurfaceListener {
 public:
     JNIImageWriterContext(JNIEnv* env, jobject weakThiz, jclass clazz);
 
     virtual ~JNIImageWriterContext();
 
-    // Implementation of IProducerListener, used to notify the ImageWriter that the consumer
+    // Implementation of SurfaceListener, used to notify the ImageWriter that the consumer
     // has returned a buffer and it is ready for ImageWriter to dequeue.
     virtual void onBufferReleased();
+    virtual bool needsReleaseNotify() override { return true; };
+    virtual void onBuffersDiscarded(const std::vector<sp<GraphicBuffer>>& /*buffers*/) override {};
+    virtual void onBufferDetached(int /*slot*/) override {};
 
     void setProducer(const sp<Surface>& producer) { mProducer = producer; }
     Surface* getProducer() { return mProducer.get(); }
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 403e219..83a986b1 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -112,3 +112,10 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "settings_catalyst"
+    namespace: "android_settings"
+    description: "Settings catalyst project migration"
+    bug: "323791114"
+}
\ No newline at end of file
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 1c3515a..2f90ccc 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -573,6 +573,7 @@
         "lottie_compose",
         "LowLightDreamLib",
         "TraceurCommon",
+        "Traceur-res",
         "//frameworks/libs/systemui:motion_tool_lib",
         "notification_flags_lib",
         "PlatformComposeCore",
@@ -749,6 +750,7 @@
         "androidx.compose.animation_animation-graphics",
         "androidx.lifecycle_lifecycle-viewmodel-compose",
         "TraceurCommon",
+        "Traceur-res",
     ],
 }
 
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 02e0423..eb9d0ab 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1243,4 +1243,13 @@
    metadata {
         purpose: PURPOSE_BUGFIX
    }
-}
\ No newline at end of file
+}
+flag {
+   name: "classic_flags_multi_user"
+   namespace: "systemui"
+   description: "Make the classic feature flag loading multi user aware."
+   bug: "345443431"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 5b328b8..fb13b57 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -712,9 +712,7 @@
         val hasReachedTargetScene =
             (targetScene == toScene && progress >= 1f) ||
                 (targetScene == fromScene && progress <= 0f)
-        val skipAnimation =
-            hasReachedTargetScene &&
-                currentOverscrollSpec?.transformationSpec?.transformations?.isEmpty() == true
+        val skipAnimation = hasReachedTargetScene && !canOverscroll()
 
         return startOffsetAnimation {
             val animatable = Animatable(dragOffset, OffsetVisibilityThreshold)
@@ -767,12 +765,7 @@
 
                                         // Immediately stop this transition if we are bouncing on a
                                         // scene that does not bounce.
-                                        val overscrollSpec = currentOverscrollSpec
-                                        if (
-                                            overscrollSpec != null &&
-                                                overscrollSpec.transformationSpec.transformations
-                                                    .isEmpty()
-                                        ) {
+                                        if (!canOverscroll()) {
                                             snapToScene(targetScene)
                                         }
                                     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 2b78b5a..324e7bd 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -115,18 +115,6 @@
     }
 }
 
-private val TRAVERSE_KEY = Any()
-
-/** Find the nearest [PointersInfoOwner] ancestor or throw. */
-internal fun DelegatableNode.requireAncestorPointersInfoOwner(): PointersInfoOwner {
-    val ancestorNode =
-        checkNotNull(findNearestAncestor(TRAVERSE_KEY)) {
-            "This should never happen! Couldn't find a MultiPointerDraggableNode. " +
-                "Are we inside an SceneTransitionLayout?"
-        }
-    return ancestorNode as PointersInfoOwner
-}
-
 internal class MultiPointerDraggableNode(
     orientation: Orientation,
     enabled: () -> Boolean,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 08e8e72..2a739d7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -233,6 +233,12 @@
                 }
             }
 
+        /** Returns if the [progress] value of this transition can go beyond range `[0; 1]` */
+        fun canOverscroll(): Boolean {
+            val overscrollSpec = currentOverscrollSpec ?: return true
+            return overscrollSpec.transformationSpec.transformations.isNotEmpty()
+        }
+
         /**
          * An animatable that animates from 1f to 0f. This will be used to nicely animate the sudden
          * jump of values when this transitions interrupts another one.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
new file mode 100644
index 0000000..01dbc6b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.education.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.education.data.repository.contextualEducationRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shared.education.GestureType
+import com.android.systemui.shared.education.GestureType.BACK_GESTURE
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val repository = kosmos.contextualEducationRepository
+    private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor
+
+    @Before
+    fun setup() {
+        underTest.start()
+    }
+
+    @Test
+    fun newEducationInfoOnMaxSignalCountReached() =
+        testScope.runTest {
+            tryTriggeringEducation(BACK_GESTURE)
+            val model by collectLastValue(underTest.educationTriggered)
+            assertThat(model?.gestureType).isEqualTo(BACK_GESTURE)
+        }
+
+    @Test
+    fun noEducationInfoBeforeMaxSignalCountReached() =
+        testScope.runTest {
+            repository.incrementSignalCount(BACK_GESTURE)
+            val model by collectLastValue(underTest.educationTriggered)
+            assertThat(model).isNull()
+        }
+
+    @Test
+    fun noEducationInfoWhenShortcutTriggeredPreviously() =
+        testScope.runTest {
+            val model by collectLastValue(underTest.educationTriggered)
+            repository.updateShortcutTriggerTime(BACK_GESTURE)
+            tryTriggeringEducation(BACK_GESTURE)
+            assertThat(model).isNull()
+        }
+
+    private suspend fun tryTriggeringEducation(gestureType: GestureType) {
+        // Increment max number of signal to try triggering education
+        for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) {
+            repository.incrementSignalCount(gestureType)
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 3fd1c20..d9708a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -362,6 +362,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun dreamingLockscreenHostedToLockscreen() =
         testScope.runTest {
             // GIVEN a device dreaming with the lockscreen hosted dream and not dozing
@@ -449,6 +450,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun dreamingLockscreenHostedToDozing() =
         testScope.runTest {
             // GIVEN a device is dreaming with lockscreen hosted dream
@@ -480,6 +482,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun dreamingLockscreenHostedToOccluded() =
         testScope.runTest {
             // GIVEN device is dreaming with lockscreen hosted dream and not occluded
@@ -977,6 +980,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun alternateBouncerToGlanceableHub() =
         testScope.runTest {
             // GIVEN the device is idle on the glanceable hub
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 23b28e3..1df2553 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -27,6 +27,8 @@
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
@@ -64,6 +66,7 @@
     private val placeholderViewModel by lazy { kosmos.notificationsPlaceholderViewModel }
     private val scrollViewModel by lazy { kosmos.notificationScrollViewModel }
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val fakeKeyguardRepository by lazy { kosmos.fakeKeyguardRepository }
     private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource }
 
     @Test
@@ -73,9 +76,11 @@
             val leftOffset = MutableStateFlow(0)
             val shape by collectLastValue(scrollViewModel.shadeScrimShape(radius, leftOffset))
 
+            // When: receive scrim bounds
             placeholderViewModel.onScrimBoundsChanged(
                 ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f)
             )
+            // Then: shape is updated
             assertThat(shape)
                 .isEqualTo(
                     ShadeScrimShape(
@@ -86,11 +91,13 @@
                     )
                 )
 
+            // When: receive new scrim bounds
             leftOffset.value = 200
             radius.value = 24
             placeholderViewModel.onScrimBoundsChanged(
                 ShadeScrimBounds(left = 210f, top = 200f, right = 300f, bottom = 550f)
             )
+            // Then: shape is updated
             assertThat(shape)
                 .isEqualTo(
                     ShadeScrimShape(
@@ -100,6 +107,16 @@
                         bottomRadius = 0
                     )
                 )
+
+            // When: QuickSettings shows up full screen
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(Scenes.QuickSettings)
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            // Then: shape is null
+            assertThat(shape).isNull()
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
index 6ddc074..7e9f437 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.scene.data.repository.Idle
 import com.android.systemui.scene.data.repository.setTransition
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -627,6 +628,7 @@
                 fakeSettings,
                 seenNotificationsInteractor,
                 statusBarStateController,
+                sceneInteractor = kosmos.sceneInteractor,
             )
         keyguardCoordinator.attach(notifPipeline)
         testScope.runTest {
diff --git a/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml b/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml
new file mode 100644
index 0000000..6180bf5
--- /dev/null
+++ b/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@+id/categories"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginTop="@dimen/qqs_layout_margin_top"
+        android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+
+    <TextView
+        android:id="@+id/cpu_buffer_size"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginTop="@dimen/qqs_layout_margin_top"
+        android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+
+    <!-- Attach to Bugreport Switch -->
+    <LinearLayout
+        android:id="@+id/attach_to_bugreport_switch_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/qqs_layout_margin_top"
+        android:orientation="horizontal">
+
+        <TextView
+            android:id="@+id/attach_to_bugreport_switch_label"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:minHeight="@dimen/screenrecord_option_icon_size"
+            android:layout_weight="1"
+            android:layout_gravity="fill_vertical"
+            android:gravity="start"
+            android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+
+        <Switch
+            android:id="@+id/attach_to_bugreport_switch"
+            android:layout_width="wrap_content"
+            android:minHeight="@dimen/screenrecord_option_icon_size"
+            android:layout_height="wrap_content"
+            android:gravity="end"
+            android:layout_gravity="fill_vertical"
+            android:layout_weight="0" />
+    </LinearLayout>
+
+    <!-- Winscope Switch -->
+    <LinearLayout
+        android:id="@+id/winscope_switch_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/qqs_layout_margin_top"
+        android:orientation="horizontal">
+
+        <TextView
+            android:id="@+id/winscope_switch_label"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:minHeight="@dimen/screenrecord_option_icon_size"
+            android:layout_weight="1"
+            android:layout_gravity="fill_vertical"
+            android:gravity="start"
+            android:textAppearance="@style/TextAppearance.Dialog.Body.Message"/>
+
+        <Switch
+            android:id="@+id/winscope_switch"
+            android:layout_width="wrap_content"
+            android:minHeight="@dimen/screenrecord_option_icon_size"
+            android:layout_height="wrap_content"
+            android:gravity="end"
+            android:layout_gravity="fill_vertical"
+            android:layout_weight="0" />
+    </LinearLayout>
+
+    <!-- Trace Debuggable Apps Switch -->
+    <LinearLayout
+        android:id="@+id/trace_debuggable_apps_switch_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/qqs_layout_margin_top"
+        android:orientation="horizontal">
+
+        <TextView
+            android:id="@+id/debuggable_apps_switch_label"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:minHeight="@dimen/screenrecord_option_icon_size"
+            android:layout_weight="1"
+            android:layout_gravity="fill_vertical"
+            android:gravity="start"
+            android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+
+        <Switch
+            android:id="@+id/trace_debuggable_apps_switch"
+            android:layout_width="wrap_content"
+            android:minHeight="@dimen/screenrecord_option_icon_size"
+            android:layout_height="wrap_content"
+            android:gravity="end"
+            android:layout_gravity="fill_vertical"
+            android:layout_weight="0" />
+    </LinearLayout>
+
+    <!-- Long Traces Switch -->
+    <LinearLayout
+        android:id="@+id/long_traces_switch_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/qqs_layout_margin_top"
+        android:orientation="horizontal">
+
+        <TextView
+            android:id="@+id/long_traces_switch_label"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:minHeight="@dimen/screenrecord_option_icon_size"
+            android:layout_weight="1"
+            android:layout_gravity="fill_vertical"
+            android:gravity="start"
+            android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+
+        <Switch
+            android:id="@+id/long_traces_switch"
+            android:layout_width="wrap_content"
+            android:minHeight="@dimen/screenrecord_option_icon_size"
+            android:layout_height="wrap_content"
+            android:gravity="end"
+            android:layout_gravity="fill_vertical"
+            android:layout_weight="0" />
+    </LinearLayout>
+
+    <TextView
+        android:id="@+id/long_trace_size"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginTop="@dimen/qqs_layout_margin_top"
+        android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+
+    <TextView
+        android:id="@+id/long_trace_duration"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginTop="@dimen/qqs_layout_margin_top"
+        android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+</LinearLayout>
+
diff --git a/packages/SystemUI/res/layout/ongoing_activity_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
index 154397d..dc56097 100644
--- a/packages/SystemUI/res/layout/ongoing_activity_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
@@ -23,8 +23,7 @@
     android:layout_gravity="center_vertical|start"
     android:layout_marginStart="5dp"
 >
-    <!-- TODO(b/332662551): Update this content description when this supports more than just
-         phone calls. -->
+    <!-- TODO(b/354930838): Fix these paddings for the new screen chips. -->
     <com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
         android:id="@+id/ongoing_activity_chip_background"
         android:layout_width="wrap_content"
@@ -32,18 +31,18 @@
         android:layout_gravity="center_vertical"
         android:gravity="center"
         android:background="@drawable/ongoing_activity_chip_bg"
-        android:paddingStart="@dimen/ongoing_activity_chip_side_padding"
-        android:paddingEnd="@dimen/ongoing_activity_chip_side_padding"
+        android:paddingStart="@dimen/ongoing_activity_chip_side_padding_with_notif_icon"
+        android:paddingEnd="@dimen/ongoing_activity_chip_side_padding_with_notif_icon"
         android:minWidth="@dimen/min_clickable_item_size"
     >
 
         <ImageView
             android:src="@*android:drawable/ic_phone"
             android:id="@+id/ongoing_activity_chip_icon"
-            android:contentDescription="@string/ongoing_phone_call_content_description"
             android:layout_width="@dimen/ongoing_activity_chip_icon_size"
             android:layout_height="@dimen/ongoing_activity_chip_icon_size"
             android:tint="?android:attr/colorPrimary"
+            android:visibility="gone"
         />
 
         <!-- Only one of [ongoing_activity_chip_time, ongoing_activity_chip_text] will ever
@@ -54,7 +53,7 @@
             android:layout_height="wrap_content"
             android:singleLine="true"
             android:gravity="center|start"
-            android:paddingStart="@dimen/ongoing_activity_chip_icon_text_padding"
+            android:paddingEnd="@dimen/ongoing_activity_chip_text_end_padding_with_notif_icon"
             android:textAppearance="@android:style/TextAppearance.Material.Small"
             android:fontFamily="@*android:string/config_headlineFontFamily"
             android:textColor="?android:attr/colorPrimary"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index eda7bb0..e459348 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1730,9 +1730,15 @@
 
     <!-- Ongoing activity chip -->
     <dimen name="ongoing_activity_chip_side_padding">12dp</dimen>
+    <!-- The start padding to use on the activity chip if the icon comes from the notification's smallIcon field (the small notif icons embed their own padding, so the chip itself can have less padding) -->
+    <dimen name="ongoing_activity_chip_side_padding_with_notif_icon">6dp</dimen>
     <dimen name="ongoing_activity_chip_icon_size">16dp</dimen>
+    <!-- The size to use for the icon in the ongoing activity chip if the icon comes from the notification's smallIcon field -->
+    <dimen name="ongoing_activity_chip_notif_icon_size">22dp</dimen>
+
     <!-- The padding between the icon and the text. -->
     <dimen name="ongoing_activity_chip_icon_text_padding">4dp</dimen>
+    <dimen name="ongoing_activity_chip_text_end_padding_with_notif_icon">6dp</dimen>
     <dimen name="ongoing_activity_chip_corner_radius">28dp</dimen>
 
     <!-- Status bar user chip -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index acc12d7..8146cc5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -948,6 +948,10 @@
     <string name="performance">Performance</string>
     <string name="user_interface">User Interface</string>
     <string name="thermal">Thermal</string>
+    <string name="custom">Custom</string>
+
+    <string name="custom_trace_settings_dialog_title">Custom Trace Settings</string>
+    <string name="restore_default">Restore Default</string>
 
     <!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_onehanded_label">One-handed mode</string>
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 56de5a3..8ae11ab 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.Process;
@@ -81,7 +80,9 @@
 
     public SystemUIApplication() {
         super();
-        Trace.registerWithPerfetto();
+        if (!isSubprocess()) {
+            Trace.registerWithPerfetto();
+        }
         Log.v(TAG, "SystemUIApplication constructed.");
         // SysUI may be building without protolog preprocessing in some cases
         ProtoLog.REQUIRE_PROTOLOGTOOL = false;
@@ -182,9 +183,7 @@
         } else {
             // We don't need to startServices for sub-process that is doing some tasks.
             // (screenshots, sweetsweetdesserts or tuner ..)
-            String processName = ActivityThread.currentProcessName();
-            ApplicationInfo info = getApplicationInfo();
-            if (processName != null && processName.startsWith(info.processName + ":")) {
+            if (isSubprocess()) {
                 return;
             }
             // For a secondary user, boot-completed will never be called because it has already
@@ -195,6 +194,12 @@
         }
     }
 
+    /** Returns whether this is a subprocess (e.g. com.android.systemui:screenshot) */
+    private boolean isSubprocess() {
+        String processName = ActivityThread.currentProcessName();
+        return processName != null && processName.contains(":");
+    }
+
     /**
      * Makes sure that all the CoreStartables are running. If they are already running, this is a
      * no-op. This is needed to conditionally start all the services, as we only need to have it in
diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
index 0e2e2e6..b8019ab 100644
--- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.education.data.repository.ContextualEducationRepository
 import com.android.systemui.education.data.repository.ContextualEducationRepositoryImpl
 import com.android.systemui.education.domain.interactor.ContextualEducationInteractor
+import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor
 import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractor
 import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractorImpl
 import com.android.systemui.shared.education.GestureType
@@ -73,7 +74,7 @@
                 implLazy.get()
             } else {
                 // No-op implementation when the flag is disabled.
-                return NoOpCoreStartable
+                return NoOpContextualEducationInteractor
             }
         }
 
@@ -88,6 +89,18 @@
                 return NoOpKeyboardTouchpadEduStatsInteractor
             }
         }
+
+        @Provides
+        fun provideKeyboardTouchpadEduInteractor(
+            implLazy: Lazy<KeyboardTouchpadEduInteractor>
+        ): CoreStartable {
+            return if (Flags.keyboardTouchpadContextualEducation()) {
+                implLazy.get()
+            } else {
+                // No-op implementation when the flag is disabled.
+                return NoOpKeyboardTouchpadEduInteractor
+            }
+        }
     }
 
     private object NoOpKeyboardTouchpadEduStatsInteractor : KeyboardTouchpadEduStatsInteractor {
@@ -96,7 +109,11 @@
         override fun updateShortcutTriggerTime(gestureType: GestureType) {}
     }
 
-    private object NoOpCoreStartable : CoreStartable {
+    private object NoOpContextualEducationInteractor : CoreStartable {
+        override fun start() {}
+    }
+
+    private object NoOpKeyboardTouchpadEduInteractor : CoreStartable {
         override fun start() {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
index e2aa911..3036d97 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
@@ -19,12 +19,17 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.education.data.model.GestureEduModel
 import com.android.systemui.education.data.repository.ContextualEducationRepository
 import com.android.systemui.shared.education.GestureType
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.launch
 
 /**
@@ -36,16 +41,28 @@
 @Inject
 constructor(
     @Background private val backgroundScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val selectedUserInteractor: SelectedUserInteractor,
     private val repository: ContextualEducationRepository,
 ) : CoreStartable {
 
+    val backGestureModelFlow = readEduModelsOnSignalCountChanged(GestureType.BACK_GESTURE)
+
     override fun start() {
         backgroundScope.launch {
             selectedUserInteractor.selectedUser.collectLatest { repository.setUser(it) }
         }
     }
 
+    private fun readEduModelsOnSignalCountChanged(gestureType: GestureType): Flow<GestureEduModel> {
+        return repository
+            .readGestureEduModelFlow(gestureType)
+            .distinctUntilChanged(
+                areEquivalent = { old, new -> old.signalCount == new.signalCount }
+            )
+            .flowOn(backgroundDispatcher)
+    }
+
     suspend fun incrementSignalCount(gestureType: GestureType) =
         repository.incrementSignalCount(gestureType)
 
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
new file mode 100644
index 0000000..247abf1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.education.domain.interactor
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.education.data.model.GestureEduModel
+import com.android.systemui.education.shared.model.EducationInfo
+import com.android.systemui.education.shared.model.EducationUiType
+import com.android.systemui.shared.education.GestureType.BACK_GESTURE
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.launch
+
+/** Allow listening to new contextual education triggered */
+@SysUISingleton
+class KeyboardTouchpadEduInteractor
+@Inject
+constructor(
+    @Background private val backgroundScope: CoroutineScope,
+    private val contextualEducationInteractor: ContextualEducationInteractor
+) : CoreStartable {
+
+    companion object {
+        const val MAX_SIGNAL_COUNT: Int = 2
+    }
+
+    private val _educationTriggered = MutableStateFlow<EducationInfo?>(null)
+    val educationTriggered = _educationTriggered.asStateFlow()
+
+    override fun start() {
+        backgroundScope.launch {
+            contextualEducationInteractor.backGestureModelFlow
+                .mapNotNull { getEduType(it) }
+                .collect { _educationTriggered.value = EducationInfo(BACK_GESTURE, it) }
+        }
+    }
+
+    private fun getEduType(model: GestureEduModel): EducationUiType? {
+        if (isEducationNeeded(model)) {
+            return EducationUiType.Toast
+        } else {
+            return null
+        }
+    }
+
+    private fun isEducationNeeded(model: GestureEduModel): Boolean {
+        // Todo: b/354884305 - add complete education logic to show education in correct scenarios
+        val shortcutWasTriggered = model.lastShortcutTriggeredTime == null
+        val signalCountReached = model.signalCount >= MAX_SIGNAL_COUNT
+
+        return shortcutWasTriggered && signalCountReached
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt b/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt
new file mode 100644
index 0000000..85f4012
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.education.shared.model
+
+import com.android.systemui.shared.education.GestureType
+
+/**
+ * Model for education triggered. [gestureType] indicates what gesture it is trying to educate about
+ * and [educationUiType] is how we educate user in the UI
+ */
+data class EducationInfo(val gestureType: GestureType, val educationUiType: EducationUiType)
+
+enum class EducationUiType {
+    Toast,
+    Notification,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index b44a8cf..a915241 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
@@ -91,12 +90,12 @@
                 edgeWithoutSceneContainer =
                     Edge.create(from = KeyguardState.ALTERNATE_BOUNCER, to = KeyguardState.GONE)
             )
-            .map<TransitionStep, Boolean?> {
+            .map {
                 // The alt bouncer is pretty fast to hide, so start the surface behind animation
                 // around 30%.
                 it.value > 0.3f
             }
-            .onStart {
+            .onStart<Boolean?> {
                 // Default to null ("don't care, use a reasonable default").
                 emit(null)
             }
@@ -145,6 +144,7 @@
                             }
                         } else {
                             if (isIdleOnCommunal) {
+                                if (SceneContainerFlag.isEnabled) return@collect
                                 KeyguardState.GLANCEABLE_HUB
                             } else if (isOccluded) {
                                 KeyguardState.OCCLUDED
@@ -158,7 +158,6 @@
     }
 
     private fun listenForAlternateBouncerToGone() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         if (KeyguardWmStateRefactor.isEnabled) {
             // Handled via #dismissAlternateBouncer.
@@ -170,9 +169,9 @@
                     keyguardInteractor.isKeyguardGoingAway.filter { it }.map {}, // map to Unit
                     keyguardInteractor.isKeyguardOccluded.flatMapLatest { keyguardOccluded ->
                         if (keyguardOccluded) {
-                            primaryBouncerInteractor.keyguardAuthenticatedBiometricsHandled.drop(
-                                1
-                            ) // drop the initial state
+                            primaryBouncerInteractor.keyguardAuthenticatedBiometricsHandled
+                                // drop the initial state
+                                .drop(1)
                         } else {
                             emptyFlow()
                         }
@@ -184,7 +183,6 @@
     }
 
     private fun listenForAlternateBouncerToPrimaryBouncer() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
index 117dbcf..f3bd0e9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -61,6 +61,7 @@
     ) {
 
     override fun start() {
+        if (SceneContainerFlag.isEnabled) return
         listenForDreamingLockscreenHostedToLockscreen()
         listenForDreamingLockscreenHostedToGone()
         listenForDreamingLockscreenHostedToDozing()
@@ -96,8 +97,6 @@
     }
 
     private fun listenForDreamingLockscreenHostedToPrimaryBouncer() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
                 .filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing }
@@ -106,8 +105,6 @@
     }
 
     private fun listenForDreamingLockscreenHostedToGone() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.biometricUnlockState
                 .filterRelevantKeyguardStateAnd { biometricUnlockState ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 4c3a75e..3775d19 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -100,7 +100,6 @@
 
     private fun listenForDreamingToGlanceableHub() {
         if (!communalSettingsInteractor.isCommunalFlagEnabled()) return
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         scope.launch("$TAG#listenForDreamingToGlanceableHub", mainDispatcher) {
             glanceableHubTransitions.listenForGlanceableHubTransition(
@@ -195,7 +194,7 @@
 
     private fun listenForDreamingToGoneWhenDismissable() {
         if (SceneContainerFlag.isEnabled) {
-            return // TODO(b/336576536): Check if adaptation for scene framework is needed
+            return
         }
 
         if (KeyguardWmStateRefactor.isEnabled) {
@@ -217,7 +216,7 @@
     }
 
     private fun listenForDreamingToGoneFromBiometricUnlock() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        // TODO(b/353542570): Adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.biometricUnlockState
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 6b1be93c..91ee287 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -75,7 +75,6 @@
     ) {
 
     override fun start() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
             return
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index ef76f38..8f4110c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -69,7 +69,7 @@
     ) {
 
     override fun start() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        // KeyguardState.GONE does not exist with SceneContainerFlag enabled
         if (SceneContainerFlag.isEnabled) return
         listenForGoneToAodOrDozing()
         listenForGoneToDreaming()
@@ -100,21 +100,19 @@
                     }
             }
 
-            if (!SceneContainerFlag.isEnabled) {
-                scope.launch {
-                    keyguardRepository.isKeyguardEnabled
-                        .filterRelevantKeyguardStateAnd { enabled -> enabled }
-                        .sample(keyguardEnabledInteractor.showKeyguardWhenReenabled)
-                        .filter { reshow -> reshow }
-                        .collect {
-                            startTransitionTo(
-                                KeyguardState.LOCKSCREEN,
-                                ownerReason =
-                                    "Keyguard was re-enabled, and we weren't GONE when it " +
-                                        "was originally disabled"
-                            )
-                        }
-                }
+            scope.launch {
+                keyguardRepository.isKeyguardEnabled
+                    .filterRelevantKeyguardStateAnd { enabled -> enabled }
+                    .sample(keyguardEnabledInteractor.showKeyguardWhenReenabled)
+                    .filter { reshow -> reshow }
+                    .collect {
+                        startTransitionTo(
+                            KeyguardState.LOCKSCREEN,
+                            ownerReason =
+                                "Keyguard was re-enabled, and we weren't GONE when it " +
+                                    "was originally disabled"
+                        )
+                    }
             }
         } else {
             scope.launch("$TAG#listenForGoneToLockscreenOrHub") {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 16c014f..206bbc5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -351,7 +351,6 @@
      * keyguard transition.
      */
     private fun listenForLockscreenToGlanceableHub() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
             return
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 2f32040..710b710a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -143,7 +143,6 @@
         if (restartDreamOnUnocclude() && dreamFromOccluded) {
             startTransitionTo(KeyguardState.DREAMING)
         } else if (isIdleOnCommunal || showCommunalFromOccluded) {
-            // TODO(b/336576536): Check if adaptation for scene framework is needed
             if (SceneContainerFlag.isEnabled) return
             if (communalSceneKtfRefactor()) {
                 communalSceneInteractor.changeScene(
@@ -159,8 +158,6 @@
     }
 
     private fun listenForOccludedToGone() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         if (KeyguardWmStateRefactor.isEnabled) {
             // We don't think OCCLUDED to GONE is possible. You should always have to go via a
             // *_BOUNCER state to end up GONE. Launching an activity over a dismissable keyguard
@@ -168,7 +165,8 @@
             // If we're wrong - sorry, add it back here.
             return
         }
-
+        // TODO(b/353545202): Adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.isKeyguardOccluded
                 .sample(keyguardInteractor.isKeyguardShowing, ::Pair)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 9adcaa2..e2d7851 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -103,7 +103,6 @@
     }
 
     private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         if (KeyguardWmStateRefactor.isEnabled) {
             scope.launch {
@@ -177,13 +176,11 @@
     }
 
     private fun listenForPrimaryBouncerToAsleep() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         scope.launch { listenForSleepTransition() }
     }
 
     private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
@@ -197,7 +194,6 @@
     }
 
     private fun listenForPrimaryBouncerToGone() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         if (KeyguardWmStateRefactor.isEnabled) {
             // This is handled in KeyguardSecurityContainerController and
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
index af1ce2b..f9ab1bb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -50,7 +50,6 @@
         fromState: KeyguardState,
         toState: KeyguardState,
     ) {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         val toScene =
             if (fromState == KeyguardState.GLANCEABLE_HUB) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index f9bfaff..797d466 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -398,7 +398,6 @@
      * including KeyguardSecurityContainerController and WindowManager.
      */
     fun startDismissKeyguardTransition(reason: String = "") {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         Log.d(TAG, "#startDismissKeyguardTransition(reason=$reason)")
         when (val startedState = repository.currentTransitionInfoInternal.value.to) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 69a157f..addb014 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -19,6 +19,7 @@
 import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
 
 import static com.android.settingslib.flags.Flags.legacyLeAudioSharing;
+import static com.android.systemui.Flags.communalHub;
 import static com.android.systemui.Flags.mediaLockscreenLaunchAnimation;
 import static com.android.systemui.media.controls.shared.model.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
 
@@ -90,6 +91,8 @@
 import com.android.systemui.animation.GhostedViewTransitionAnimatorController;
 import com.android.systemui.bluetooth.BroadcastDialogController;
 import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
+import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
@@ -202,10 +205,12 @@
     );
 
     // Time in millis for playing turbulence noise that is played after a touch ripple.
-    @VisibleForTesting static final long TURBULENCE_NOISE_PLAY_DURATION = 7500L;
+    @VisibleForTesting
+    static final long TURBULENCE_NOISE_PLAY_DURATION = 7500L;
 
     private final SeekBarViewModel mSeekBarViewModel;
     private final MediaFlags mMediaFlags;
+    private final CommunalSceneInteractor mCommunalSceneInteractor;
     private SeekBarObserver mSeekBarObserver;
     protected final Executor mBackgroundExecutor;
     private final DelayableExecutor mMainExecutor;
@@ -293,8 +298,9 @@
      * Initialize a new control panel
      *
      * @param backgroundExecutor background executor, used for processing artwork
-     * @param mainExecutor main thread executor, used if we receive callbacks on the background
-     *                     thread that then trigger UI changes.
+     * @param mainExecutor       main thread executor, used if we receive callbacks on the
+     *                           background
+     *                           thread that then trigger UI changes.
      * @param activityStarter    activity starter
      */
     @Inject
@@ -314,6 +320,7 @@
             MediaUiEventLogger logger,
             KeyguardStateController keyguardStateController,
             ActivityIntentHelper activityIntentHelper,
+            CommunalSceneInteractor communalSceneInteractor,
             NotificationLockscreenUserManager lockscreenUserManager,
             BroadcastDialogController broadcastDialogController,
             GlobalSettings globalSettings,
@@ -337,6 +344,7 @@
         mLockscreenUserManager = lockscreenUserManager;
         mBroadcastDialogController = broadcastDialogController;
         mMediaFlags = mediaFlags;
+        mCommunalSceneInteractor = communalSceneInteractor;
 
         mSeekBarViewModel.setLogSeek(() -> {
             if (mPackageName != null && mInstanceId != null) {
@@ -375,6 +383,7 @@
 
     /**
      * Get the recommendation view holder used to display Smartspace media recs.
+     *
      * @return the recommendation view holder
      */
     @Nullable
@@ -693,7 +702,7 @@
             // TODO(b/233698402): Use the package name instead of app label to avoid the
             // unexpected result.
             mIsCurrentBroadcastedApp = device != null
-                && TextUtils.equals(device.getName(),
+                    && TextUtils.equals(device.getName(),
                     mContext.getString(R.string.broadcasting_description_is_broadcasting));
             useDisabledAlpha = !mIsCurrentBroadcastedApp;
             // Always be enabled if the broadcast button is shown
@@ -764,7 +773,7 @@
                             PendingIntent deviceIntent = device.getIntent();
                             boolean showOverLockscreen = mKeyguardStateController.isShowing()
                                     && mActivityIntentHelper.wouldPendingShowOverLockscreen(
-                                        deviceIntent, mLockscreenUserManager.getCurrentUserId());
+                                    deviceIntent, mLockscreenUserManager.getCurrentUserId());
                             if (deviceIntent.isActivity()) {
                                 if (!showOverLockscreen) {
                                     mActivityStarter.postStartActivityDismissingKeyguard(
@@ -825,24 +834,26 @@
         ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
         ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
         return mMetadataAnimationHandler.setNext(
-            new Triple(data.getSong(), data.getArtist(), data.isExplicit()),
-            () -> {
-                titleText.setText(data.getSong());
-                artistText.setText(data.getArtist());
-                setVisibleAndAlpha(expandedSet, R.id.media_explicit_indicator, data.isExplicit());
-                setVisibleAndAlpha(collapsedSet, R.id.media_explicit_indicator, data.isExplicit());
+                new Triple(data.getSong(), data.getArtist(), data.isExplicit()),
+                () -> {
+                    titleText.setText(data.getSong());
+                    artistText.setText(data.getArtist());
+                    setVisibleAndAlpha(expandedSet, R.id.media_explicit_indicator,
+                            data.isExplicit());
+                    setVisibleAndAlpha(collapsedSet, R.id.media_explicit_indicator,
+                            data.isExplicit());
 
-                // refreshState is required here to resize the text views (and prevent ellipsis)
-                mMediaViewController.refreshState();
-                return Unit.INSTANCE;
-            },
-            () -> {
-                // After finishing the enter animation, we refresh state. This could pop if
-                // something is incorrectly bound, but needs to be run if other elements were
-                // updated while the enter animation was running
-                mMediaViewController.refreshState();
-                return Unit.INSTANCE;
-            });
+                    // refreshState is required here to resize the text views (and prevent ellipsis)
+                    mMediaViewController.refreshState();
+                    return Unit.INSTANCE;
+                },
+                () -> {
+                    // After finishing the enter animation, we refresh state. This could pop if
+                    // something is incorrectly bound, but needs to be run if other elements were
+                    // updated while the enter animation was running
+                    mMediaViewController.refreshState();
+                    return Unit.INSTANCE;
+                });
     }
 
     // We may want to look into unifying this with bindRecommendationContentDescription if/when we
@@ -1105,7 +1116,7 @@
 
     private LayerDrawable setupGradientColorOnDrawable(Drawable albumArt, GradientDrawable gradient,
             ColorScheme mutableColorScheme, float startAlpha, float endAlpha) {
-        gradient.setColors(new int[] {
+        gradient.setColors(new int[]{
                 ColorUtilKt.getColorWithAlpha(
                         MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
                         startAlpha),
@@ -1113,7 +1124,7 @@
                         MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
                         endAlpha),
         });
-        return new LayerDrawable(new Drawable[] { albumArt, gradient });
+        return new LayerDrawable(new Drawable[]{albumArt, gradient});
     }
 
     private void scaleTransitionDrawableLayer(TransitionDrawable transitionDrawable, int layer,
@@ -1143,7 +1154,7 @@
         ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
         if (semanticActions != null) {
             // Hide all the generic buttons
-            for (ImageButton b: genericButtons) {
+            for (ImageButton b : genericButtons) {
                 setVisibleAndAlpha(collapsedSet, b.getId(), false);
                 setVisibleAndAlpha(expandedSet, b.getId(), false);
             }
@@ -1346,6 +1357,7 @@
                 /* shouldInverseNoiseLuminosity= */ false
         );
     }
+
     private void clearButton(final ImageButton button) {
         button.setImageDrawable(null);
         button.setContentDescription(null);
@@ -1421,19 +1433,33 @@
 
         // TODO(b/174236650): Make sure that the carousel indicator also fades out.
         // TODO(b/174236650): Instrument the animation to measure jank.
-        return new GhostedViewTransitionAnimatorController(player,
-                InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER) {
-            @Override
-            protected float getCurrentTopCornerRadius() {
-                return mContext.getResources().getDimension(R.dimen.notification_corner_radius);
-            }
+        final ActivityTransitionAnimator.Controller controller =
+                new GhostedViewTransitionAnimatorController(player,
+                        InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER) {
+                    @Override
+                    protected float getCurrentTopCornerRadius() {
+                        return mContext.getResources().getDimension(
+                                R.dimen.notification_corner_radius);
+                    }
 
-            @Override
-            protected float getCurrentBottomCornerRadius() {
-                // TODO(b/184121838): Make IlluminationDrawable support top and bottom radius.
-                return getCurrentTopCornerRadius();
-            }
-        };
+                    @Override
+                    protected float getCurrentBottomCornerRadius() {
+                        // TODO(b/184121838): Make IlluminationDrawable support top and bottom
+                        //  radius.
+                        return getCurrentTopCornerRadius();
+                    }
+                };
+
+        // When on the hub, wrap in the communal animation controller to ensure we exit the hub
+        // at the proper stage of the animation.
+        if (communalHub()
+                && mMediaViewController.getCurrentEndLocation()
+                == MediaHierarchyManager.LOCATION_COMMUNAL_HUB) {
+            mCommunalSceneInteractor.setIsLaunchingWidget(true);
+            return new CommunalTransitionAnimatorController(controller,
+                    mCommunalSceneInteractor);
+        }
+        return controller;
     }
 
     /** Bind this recommendation view based on the given data. */
@@ -1934,6 +1960,7 @@
 
     /**
      * Get the surface given the current end location for MediaViewController
+     *
      * @return surface used for Smartspace logging
      */
     protected int getSurfaceForSmartspaceLogging() {
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt
new file mode 100644
index 0000000..56270ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.annotation.SuppressLint
+import android.app.AlertDialog
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.widget.Switch
+import android.widget.TextView
+import com.android.systemui.recordissue.IssueRecordingState.Companion.TAG_TITLE_DELIMITER
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.traceur.PresetTraceConfigs
+import com.android.traceur.TraceConfig
+import com.android.traceur.res.R as T
+import java.util.function.Consumer
+
+class CustomTraceSettingsDialogDelegate(
+    private val factory: SystemUIDialog.Factory,
+    private val customTraceState: CustomTraceState,
+    private val tagTitles: Set<String>,
+    private val onSave: Runnable,
+) : SystemUIDialog.Delegate {
+
+    private val builder = TraceConfig.Builder(customTraceState.traceConfig)
+
+    override fun createDialog(): SystemUIDialog = factory.create(this)
+
+    override fun beforeCreate(dialog: SystemUIDialog?, savedInstanceState: Bundle?) {
+        super.beforeCreate(dialog, savedInstanceState)
+
+        dialog?.apply {
+            setTitle(R.string.custom_trace_settings_dialog_title)
+            setView(
+                LayoutInflater.from(context).inflate(R.layout.custom_trace_settings_dialog, null)
+            )
+            setPositiveButton(R.string.save) { _, _ ->
+                onSave.run()
+                customTraceState.traceConfig = builder.build()
+            }
+            setNegativeButton(R.string.cancel) { _, _ -> }
+        }
+    }
+
+    @SuppressLint("SetTextI18n")
+    override fun onCreate(dialog: SystemUIDialog?, savedInstanceState: Bundle?) {
+        super.onCreate(dialog, savedInstanceState)
+
+        dialog?.apply {
+            requireViewById<TextView>(R.id.categories).apply {
+                text =
+                    context.getString(T.string.categories) +
+                        "\n" +
+                        if (
+                            builder.tags == null ||
+                                builder.tags!! == PresetTraceConfigs.getDefaultConfig().tags
+                        ) {
+                            context.getString(R.string.notification_alert_title)
+                        } else {
+                            tagTitles
+                                .filter {
+                                    builder.tags!!.contains(it.substringBefore(TAG_TITLE_DELIMITER))
+                                }
+                                .joinToString()
+                        }
+                setOnClickListener { showCategorySelector(this) }
+            }
+            requireViewById<Switch>(R.id.attach_to_bugreport_switch).apply {
+                isChecked = builder.attachToBugreport
+                setOnCheckedChangeListener { _, isChecked -> builder.attachToBugreport = isChecked }
+            }
+            requireViewById<TextView>(R.id.cpu_buffer_size).setupSingleChoiceText(
+                T.array.buffer_size_values,
+                T.array.buffer_size_names,
+                builder.bufferSizeKb,
+                T.string.buffer_size,
+            ) {
+                builder.bufferSizeKb = it
+            }
+            val longTraceSizeText: TextView =
+                requireViewById<TextView>(R.id.long_trace_size).setupSingleChoiceText(
+                    T.array.long_trace_size_values,
+                    T.array.long_trace_size_names,
+                    builder.maxLongTraceSizeMb,
+                    T.string.max_long_trace_size,
+                ) {
+                    builder.maxLongTraceSizeMb = it
+                }
+            val longTraceDurationText: TextView =
+                requireViewById<TextView>(R.id.long_trace_duration).setupSingleChoiceText(
+                    T.array.long_trace_duration_values,
+                    T.array.long_trace_duration_names,
+                    builder.maxLongTraceDurationMinutes,
+                    T.string.max_long_trace_duration,
+                ) {
+                    builder.maxLongTraceDurationMinutes = it
+                }
+            requireViewById<Switch>(R.id.long_traces_switch).apply {
+                isChecked = builder.longTrace
+                val disabledAlpha by lazy { getDisabledAlpha(context) }
+                val alpha = if (isChecked) 1f else disabledAlpha
+                longTraceDurationText.alpha = alpha
+                longTraceSizeText.alpha = alpha
+
+                setOnCheckedChangeListener { _, isChecked ->
+                    builder.longTrace = isChecked
+                    longTraceDurationText.isEnabled = isChecked
+                    longTraceSizeText.isEnabled = isChecked
+
+                    val newAlpha = if (isChecked) 1f else disabledAlpha
+                    longTraceDurationText.alpha = newAlpha
+                    longTraceSizeText.alpha = newAlpha
+                }
+            }
+            requireViewById<Switch>(R.id.winscope_switch).apply {
+                isChecked = builder.winscope
+                setOnCheckedChangeListener { _, isChecked -> builder.winscope = isChecked }
+            }
+            requireViewById<Switch>(R.id.trace_debuggable_apps_switch).apply {
+                isChecked = builder.apps
+                setOnCheckedChangeListener { _, isChecked -> builder.apps = isChecked }
+            }
+            requireViewById<TextView>(R.id.long_traces_switch_label).text =
+                context.getString(T.string.long_traces)
+            requireViewById<TextView>(R.id.debuggable_apps_switch_label).text =
+                context.getString(T.string.trace_debuggable_applications)
+            requireViewById<TextView>(R.id.winscope_switch_label).text =
+                context.getString(T.string.winscope_tracing)
+            requireViewById<TextView>(R.id.attach_to_bugreport_switch_label).text =
+                context.getString(T.string.attach_to_bug_report)
+        }
+    }
+
+    @SuppressLint("SetTextI18n")
+    private fun showCategorySelector(root: TextView) {
+        showDialog(root.context) {
+            val tags = builder.tags ?: PresetTraceConfigs.getDefaultConfig().tags
+            val titlesToCheckmarks =
+                tagTitles.associateBy(
+                    { it },
+                    { tags.contains(it.substringBefore(TAG_TITLE_DELIMITER)) }
+                )
+            val titles = titlesToCheckmarks.keys.toTypedArray()
+            val checkmarks = titlesToCheckmarks.values.toBooleanArray()
+            val checkedTitleSuffixes =
+                titlesToCheckmarks.entries
+                    .filter { it.value }
+                    .map { it.key.substringAfter(TAG_TITLE_DELIMITER) }
+                    .toMutableSet()
+
+            val newTags = tags.toMutableSet()
+            setMultiChoiceItems(titles, checkmarks) { _, i, isChecked ->
+                val tag = titles[i].substringBefore(TAG_TITLE_DELIMITER)
+                val titleSuffix = titles[i].substringAfter(TAG_TITLE_DELIMITER)
+                if (isChecked) {
+                    newTags.add(tag)
+                    checkedTitleSuffixes.add(titleSuffix)
+                } else {
+                    newTags.remove(tag)
+                    checkedTitleSuffixes.remove(titleSuffix)
+                }
+            }
+            setPositiveButton(R.string.save) { _, _ ->
+                root.text =
+                    root.context.resources.getString(T.string.categories) +
+                        "\n" +
+                        checkedTitleSuffixes.joinToString()
+                builder.tags = newTags
+            }
+            setNeutralButton(R.string.restore_default) { _, _ ->
+                root.text =
+                    context.getString(T.string.categories) +
+                        "\n" +
+                        context.getString(R.string.notification_alert_title)
+                builder.tags = null
+            }
+            setNegativeButton(R.string.cancel) { _, _ -> }
+        }
+    }
+
+    @SuppressLint("SetTextI18n")
+    private fun TextView.setupSingleChoiceText(
+        resValues: Int,
+        resNames: Int,
+        startingValue: Int,
+        alertTitleRes: Int,
+        onChosen: Consumer<Int>,
+    ): TextView {
+        val values = resources.getStringArray(resValues).map { Integer.parseInt(it) }
+        val names = resources.getStringArray(resNames)
+        val startingIndex = values.indexOf(startingValue)
+        text = resources.getString(alertTitleRes) + "\n${names[startingIndex]}"
+
+        setOnClickListener {
+            showDialog(context) {
+                setTitle(alertTitleRes)
+                setSingleChoiceItems(names, startingIndex) { d, i ->
+                    text = resources.getString(alertTitleRes) + "\n${names[i]}"
+                    onChosen.accept(values[i])
+                    d.dismiss()
+                }
+            }
+        }
+        return this
+    }
+
+    private fun showDialog(context: Context, onBuilder: AlertDialog.Builder.() -> Unit) =
+        AlertDialog.Builder(context, R.style.Theme_SystemUI_Dialog_Alert)
+            .apply { onBuilder() }
+            .create()
+            .also { SystemUIDialog.applyFlags(it) }
+            .show()
+
+    private fun getDisabledAlpha(context: Context): Float {
+        val ta = context.obtainStyledAttributes(intArrayOf(android.R.attr.disabledAlpha))
+        val alpha = ta.getFloat(0, 0f)
+        ta.recycle()
+        return alpha
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceState.kt b/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceState.kt
new file mode 100644
index 0000000..14dfcc5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceState.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.content.SharedPreferences
+import com.android.traceur.PresetTraceConfigs.TraceOptions
+import com.android.traceur.PresetTraceConfigs.getDefaultConfig
+import com.android.traceur.TraceConfig
+
+class CustomTraceState(private val prefs: SharedPreferences) {
+
+    private var enabledTags: Set<String>?
+        get() = prefs.getStringSet(KEY_TAGS, getDefaultConfig().tags) ?: getDefaultConfig().tags
+        set(value) = prefs.edit().putStringSet(KEY_TAGS, value).apply()
+
+    var traceConfig: TraceConfig
+        get() = TraceConfig(options, enabledTags)
+        set(value) {
+            enabledTags = value.tags
+            options = value.options
+        }
+
+    private var options: TraceOptions
+        get() =
+            TraceOptions(
+                prefs.getInt(KEY_CUSTOM_BUFFER_SIZE_KB, getDefaultConfig().bufferSizeKb),
+                prefs.getBoolean(KEY_WINSCOPE, getDefaultConfig().winscope),
+                prefs.getBoolean(KEY_APPS, getDefaultConfig().apps),
+                prefs.getBoolean(KEY_LONG_TRACE, getDefaultConfig().longTrace),
+                prefs.getBoolean(KEY_ATTACH_TO_BUGREPORT, getDefaultConfig().attachToBugreport),
+                prefs.getInt(KEY_LONG_TRACE_SIZE_MB, getDefaultConfig().maxLongTraceSizeMb),
+                prefs.getInt(
+                    KEY_LONG_TRACE_DURATION_MINUTES,
+                    getDefaultConfig().maxLongTraceDurationMinutes
+                ),
+            )
+        set(value) {
+            prefs
+                .edit()
+                .putInt(KEY_CUSTOM_BUFFER_SIZE_KB, value.bufferSizeKb)
+                .putBoolean(KEY_WINSCOPE, value.winscope)
+                .putBoolean(KEY_APPS, value.apps)
+                .putBoolean(KEY_LONG_TRACE, value.longTrace)
+                .putBoolean(KEY_ATTACH_TO_BUGREPORT, value.attachToBugreport)
+                .putInt(KEY_LONG_TRACE_SIZE_MB, value.maxLongTraceSizeMb)
+                .putInt(KEY_LONG_TRACE_DURATION_MINUTES, value.maxLongTraceDurationMinutes)
+                .apply()
+        }
+
+    companion object {
+        private const val KEY_CUSTOM_BUFFER_SIZE_KB = "key_bufferSizeKb"
+        private const val KEY_WINSCOPE = "key_winscope"
+        private const val KEY_APPS = "key_apps"
+        private const val KEY_LONG_TRACE = "key_longTrace"
+        private const val KEY_ATTACH_TO_BUGREPORT = "key_attachToBugReport"
+        private const val KEY_LONG_TRACE_SIZE_MB = "key_maxLongTraceSizeMb"
+        private const val KEY_LONG_TRACE_DURATION_MINUTES = "key_maxLongTraceDurationInMinutes"
+        private const val KEY_TAGS = "key_tags"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
index 7612900..16642ab 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
@@ -38,6 +38,8 @@
     private val prefs =
         userFileManager.getSharedPreferences(TILE_SPEC, Context.MODE_PRIVATE, userTracker.userId)
 
+    val customTraceState = CustomTraceState(prefs)
+
     var takeBugreport
         get() = prefs.getBoolean(KEY_TAKE_BUG_REPORT, false)
         set(value) = prefs.edit().putBoolean(KEY_TAKE_BUG_REPORT, value).apply()
@@ -55,7 +57,12 @@
         set(value) = prefs.edit().putInt(KEY_ISSUE_TYPE_RES, value).apply()
 
     val traceConfig: TraceConfig
-        get() = ALL_ISSUE_TYPES[issueTypeRes] ?: PresetTraceConfigs.getDefaultConfig()
+        get() = ALL_ISSUE_TYPES[issueTypeRes] ?: customTraceState.traceConfig
+
+    // The 1st part of the title before the ": " is the tag, and the 2nd part is the description
+    var tagTitles: Set<String>
+        get() = prefs.getStringSet(KEY_TAG_TITLES, emptySet()) ?: emptySet()
+        set(value) = prefs.edit().putStringSet(KEY_TAG_TITLES, value).apply()
 
     private val listeners = CopyOnWriteArrayList<Runnable>()
 
@@ -81,8 +88,10 @@
         private const val KEY_TAKE_BUG_REPORT = "key_takeBugReport"
         private const val HAS_APPROVED_SCREEN_RECORDING = "HasApprovedScreenRecord"
         private const val KEY_RECORD_SCREEN = "key_recordScreen"
+        private const val KEY_TAG_TITLES = "key_tagTitles"
         const val KEY_ISSUE_TYPE_RES = "key_issueTypeRes"
         const val ISSUE_TYPE_NOT_SET = -1
+        const val TAG_TITLE_DELIMITER = ": "
 
         val ALL_ISSUE_TYPES: Map<Int, TraceConfig?> =
             hashMapOf(
@@ -90,6 +99,7 @@
                 Pair(R.string.user_interface, PresetTraceConfigs.getUiConfig()),
                 Pair(R.string.battery, PresetTraceConfigs.getBatteryConfig()),
                 Pair(R.string.thermal, PresetTraceConfigs.getThermalConfig()),
+                Pair(R.string.custom, null),
             )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index 8a51ad4..dd2dbf3 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -88,7 +88,7 @@
             setPositiveButton(R.string.qs_record_issue_start) { _, _ -> onStarted.run() }
         }
         bgExecutor.execute {
-            traceurMessageSender.onBoundToTraceur.add { traceurMessageSender.getTags() }
+            traceurMessageSender.onBoundToTraceur.add { traceurMessageSender.getTags(state) }
             traceurMessageSender.bindToTraceur(dialog.context)
         }
     }
@@ -170,18 +170,8 @@
     @MainThread
     private fun onIssueTypeClicked(context: Context, onIssueTypeSelected: Runnable) {
         val popupMenu = PopupMenu(context, issueTypeButton)
-
-        ALL_ISSUE_TYPES.keys.forEach {
-            popupMenu.menu.add(it).apply {
-                setIcon(R.drawable.arrow_pointing_down)
-                if (it != state.issueTypeRes) {
-                    iconTintList = ColorStateList.valueOf(Color.TRANSPARENT)
-                }
-                intent = Intent().putExtra(KEY_ISSUE_TYPE_RES, it)
-            }
-        }
-        popupMenu.apply {
-            setOnMenuItemClickListener {
+        val onMenuItemClickListener =
+            PopupMenu.OnMenuItemClickListener {
                 issueTypeButton.text = it.title
                 state.issueTypeRes =
                     it.intent?.getIntExtra(KEY_ISSUE_TYPE_RES, ISSUE_TYPE_NOT_SET)
@@ -189,6 +179,32 @@
                 onIssueTypeSelected.run()
                 true
             }
+        ALL_ISSUE_TYPES.keys.forEach {
+            popupMenu.menu.add(it).apply {
+                setIcon(R.drawable.arrow_pointing_down)
+                if (it != state.issueTypeRes) {
+                    iconTintList = ColorStateList.valueOf(Color.TRANSPARENT)
+                }
+                intent = Intent().putExtra(KEY_ISSUE_TYPE_RES, it)
+
+                if (it == R.string.custom) {
+                    setOnMenuItemClickListener {
+                        CustomTraceSettingsDialogDelegate(
+                                factory,
+                                state.customTraceState,
+                                state.tagTitles
+                            ) {
+                                onMenuItemClickListener.onMenuItemClick(it)
+                            }
+                            .createDialog()
+                            .show()
+                        true
+                    }
+                }
+            }
+        }
+        popupMenu.apply {
+            setOnMenuItemClickListener(onMenuItemClickListener)
             setForceShowIcon(true)
             show()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
index a31a9ef..8bfd14a 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
@@ -33,6 +33,7 @@
 import androidx.annotation.WorkerThread
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.recordissue.IssueRecordingState.Companion.TAG_TITLE_DELIMITER
 import com.android.traceur.FileSender
 import com.android.traceur.MessageConstants
 import com.android.traceur.TraceConfig
@@ -112,8 +113,8 @@
     }
 
     @WorkerThread
-    fun getTags() {
-        val replyHandler = Messenger(TagsHandler(backgroundLooper))
+    fun getTags(state: IssueRecordingState) {
+        val replyHandler = Messenger(TagsHandler(backgroundLooper, state))
         notifyTraceur(MessageConstants.TAGS_WHAT, replyTo = replyHandler)
     }
 
@@ -165,7 +166,8 @@
         }
     }
 
-    private class TagsHandler(looper: Looper) : Handler(looper) {
+    private class TagsHandler(looper: Looper, private val state: IssueRecordingState) :
+        Handler(looper) {
 
         override fun handleMessage(msg: Message) {
             if (MessageConstants.TAGS_WHAT == msg.what) {
@@ -174,16 +176,11 @@
                     msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAG_DESCRIPTIONS)
                 if (keys == null || values == null) {
                     throw IllegalArgumentException(
-                        "Neither keys: $keys, nor values: $values can " + "be null"
+                        "Neither keys: $keys, nor values: $values can be null"
                     )
                 }
-
-                val tags = keys.zip(values).map { "${it.first}: ${it.second}" }.toSet()
-                Log.e(
-                    TAG,
-                    "These tags: $tags will be saved and used for the Custom Trace" +
-                        " Config dialog in a future CL. This log will be removed."
-                )
+                state.tagTitles =
+                    keys.zip(values).map { it.first + TAG_TITLE_DELIMITER + it.second }.toSet()
             } else {
                 throw IllegalArgumentException("received unknown msg.what: " + msg.what)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index bd08685..67032f7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -789,6 +789,9 @@
 
     /** update Qs height state */
     void setExpansionHeight(float height) {
+        if (mExpansionHeight == height) {
+            return;
+        }
         int maxHeight = getMaxExpansionHeight();
         height = Math.min(Math.max(
                 height, getMinExpansionHeight()), maxHeight);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index f39ee9a..9e221d3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade.domain.interactor
 
+import com.android.app.tracing.FlowTracing.traceAsCounter
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -74,6 +75,7 @@
                 }
             }
             .distinctUntilChanged()
+            .traceAsCounter("panel_expansion") { (it * 100f).toInt() }
             .stateIn(scope, SharingStarted.Eagerly, 0f)
 
     override val qsExpansion: StateFlow<Float> = repository.qsExpansion
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index b2142a5..9617b54 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade.domain.interactor
 
+import com.android.app.tracing.FlowTracing.traceAsCounter
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.dagger.SysUISingleton
@@ -46,6 +47,7 @@
 ) : BaseShadeInteractor {
     override val shadeExpansion: StateFlow<Float> =
         sceneBasedExpansion(sceneInteractor, SceneFamilies.NotifShade)
+            .traceAsCounter("panel_expansion") { (it * 100f).toInt() }
             .stateIn(scope, SharingStarted.Eagerly, 0f)
 
     private val sceneBasedQsExpansion =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 59fd0ca..0e3f775 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -105,6 +105,8 @@
     }
 
     companion object {
+        // TODO(b/354930838): Use the icon from the call notification instead of hard-coding the
+        // icon to always be a phone.
         private val phoneIcon =
             Icon.Resource(
                 com.android.internal.R.drawable.ic_phone,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
index 5b25b11..0103fff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
@@ -28,6 +28,8 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.expansionChanges
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -87,6 +89,7 @@
     private val secureSettings: SecureSettings,
     private val seenNotificationsInteractor: SeenNotificationsInteractor,
     private val statusBarStateController: StatusBarStateController,
+    private val sceneInteractor: SceneInteractor,
 ) : Coordinator, Dumpable {
 
     private val unseenNotifications = mutableSetOf<NotificationEntry>()
@@ -106,12 +109,15 @@
         // Whether or not keyguard is visible (or occluded).
         @Suppress("DEPRECATION")
         val isKeyguardPresentFlow: Flow<Boolean> =
-            keyguardTransitionInteractor
-                .transitionValue(
-                    scene = Scenes.Gone,
-                    stateWithoutSceneContainer = KeyguardState.GONE,
-                )
-                .map { it == 0f }
+            if (SceneContainerFlag.isEnabled) {
+                    sceneInteractor.transitionState.map {
+                        !it.isTransitioning(to = Scenes.Gone) && !it.isIdle(Scenes.Gone)
+                    }
+                } else {
+                    keyguardTransitionInteractor.transitions.map { step ->
+                        step.to != KeyguardState.GONE
+                    }
+                }
                 .distinctUntilChanged()
                 .onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 0f6f03a..3a2f95e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -263,7 +263,8 @@
 
     private boolean isReorderingAllowed() {
         final boolean sleepyAndDozed = mFullyDozed && mSleepy;
-        final boolean stackShowing = mPanelExpanded || mLockscreenShowing;
+        final boolean stackShowing = mPanelExpanded
+                || (SceneContainerFlag.isEnabled() && mLockscreenShowing);
         return (sleepyAndDozed || !stackShowing || mCommunalShowing) && !mPulsing;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 331d3cc..a39c487 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -123,6 +123,8 @@
             // Construct the status bar icon view.
             val sbIcon = iconBuilder.createIconView(entry)
             sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+            val sbChipIcon = iconBuilder.createIconView(entry)
+            sbChipIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
 
             // Construct the shelf icon view.
             val shelfIcon = iconBuilder.createIconView(entry)
@@ -139,9 +141,17 @@
 
             try {
                 setIcon(entry, normalIconDescriptor, sbIcon)
+                setIcon(entry, normalIconDescriptor, sbChipIcon)
                 setIcon(entry, sensitiveIconDescriptor, shelfIcon)
                 setIcon(entry, sensitiveIconDescriptor, aodIcon)
-                entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, entry.icons)
+                entry.icons =
+                    IconPack.buildPack(
+                        sbIcon,
+                        sbChipIcon,
+                        shelfIcon,
+                        aodIcon,
+                        entry.icons,
+                    )
             } catch (e: InflationException) {
                 entry.icons = IconPack.buildEmptyPack(entry.icons)
                 throw e
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
index d029ce7..82bb5ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
@@ -29,6 +29,7 @@
 
     private final boolean mAreIconsAvailable;
     @Nullable private final StatusBarIconView mStatusBarIcon;
+    @Nullable private final StatusBarIconView mStatusBarChipIcon;
     @Nullable private final StatusBarIconView mShelfIcon;
     @Nullable private final StatusBarIconView mAodIcon;
 
@@ -43,7 +44,7 @@
      * haven't been inflated yet or there was an error while inflating them).
      */
     public static IconPack buildEmptyPack(@Nullable IconPack fromSource) {
-        return new IconPack(false, null, null, null, fromSource);
+        return new IconPack(false, null, null, null, null, fromSource);
     }
 
     /**
@@ -51,20 +52,24 @@
      */
     public static IconPack buildPack(
             @NonNull StatusBarIconView statusBarIcon,
+            @NonNull StatusBarIconView statusBarChipIcon,
             @NonNull StatusBarIconView shelfIcon,
             @NonNull StatusBarIconView aodIcon,
             @Nullable IconPack source) {
-        return new IconPack(true, statusBarIcon, shelfIcon, aodIcon, source);
+        return new IconPack(
+                true, statusBarIcon, statusBarChipIcon, shelfIcon, aodIcon, source);
     }
 
     private IconPack(
             boolean areIconsAvailable,
             @Nullable StatusBarIconView statusBarIcon,
+            @Nullable StatusBarIconView statusBarChipIcon,
             @Nullable StatusBarIconView shelfIcon,
             @Nullable StatusBarIconView aodIcon,
             @Nullable IconPack source) {
         mAreIconsAvailable = areIconsAvailable;
         mStatusBarIcon = statusBarIcon;
+        mStatusBarChipIcon = statusBarChipIcon;
         mShelfIcon = shelfIcon;
         mAodIcon = aodIcon;
         if (source != null) {
@@ -79,6 +84,17 @@
     }
 
     /**
+     * The version of the notification icon that appears inside a chip within the status bar.
+     *
+     * Separate from {@link #getStatusBarIcon()} so that we don't have to worry about detaching and
+     * re-attaching the same view when the chip appears and hides.
+     */
+    @Nullable
+    public StatusBarIconView getStatusBarChipIcon() {
+        return mStatusBarChipIcon;
+    }
+
+    /**
      * The version of the icon that appears in the "shelf" at the bottom of the notification shade.
      * In general, this icon also appears somewhere on the notification and is "sucked" into the
      * shelf as the scrolls beyond it.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 57e52b7..2ba79a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -112,14 +112,22 @@
     private operator fun SceneKey.contains(scene: SceneKey) =
         sceneInteractor.isSceneInFamily(scene, this)
 
+    private val qsAllowsClipping: Flow<Boolean> =
+        combine(shadeInteractor.shadeMode, shadeInteractor.qsExpansion) { shadeMode, qsExpansion ->
+                qsExpansion < 0.5f || shadeMode != ShadeMode.Single
+            }
+            .distinctUntilChanged()
+
     /** The bounds of the notification stack in the current scene. */
     private val shadeScrimClipping: Flow<ShadeScrimClipping?> =
         combine(
+                qsAllowsClipping,
                 stackAppearanceInteractor.shadeScrimBounds,
                 stackAppearanceInteractor.shadeScrimRounding,
-            ) { bounds, rounding ->
-                bounds?.let { ShadeScrimClipping(it, rounding) }
+            ) { qsAllowsClipping, bounds, rounding ->
+                bounds?.takeIf { qsAllowsClipping }?.let { ShadeScrimClipping(it, rounding) }
             }
+            .distinctUntilChanged()
             .dumpWhileCollecting("stackClipping")
 
     fun shadeScrimShape(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 3898088..2ea8da8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -24,8 +24,12 @@
 import android.app.UidObserver
 import android.content.Context
 import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.annotation.IdRes
 import androidx.annotation.VisibleForTesting
 import com.android.internal.jank.InteractionJankMonitor
+import com.android.settingslib.Utils
 import com.android.systemui.CoreStartable
 import com.android.systemui.Dumpable
 import com.android.systemui.Flags
@@ -38,6 +42,7 @@
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
@@ -115,6 +120,7 @@
                                 Notification.EXTRA_CALL_TYPE,
                                 -1
                             ) == CALL_TYPE_ONGOING,
+                            entry.icons.statusBarChipIcon,
                             statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
                         )
                     if (newOngoingCallInfo == callNotificationInfo) {
@@ -176,8 +182,7 @@
     fun setChipView(chipView: View) {
         tearDownChipView()
         this.chipView = chipView
-        val backgroundView: ChipBackgroundContainer? =
-            chipView.findViewById(R.id.ongoing_activity_chip_background)
+        val backgroundView: ChipBackgroundContainer? = chipView.getBackgroundView()
         backgroundView?.maxHeightFetcher = { statusBarWindowController.statusBarHeight }
         if (hasOngoingCall()) {
             updateChip()
@@ -247,6 +252,8 @@
                     timeView.setShouldHideText(true)
                     timeView.stop()
                 }
+
+                updateChipIcon(currentCallNotificationInfo, currentChipView)
                 updateChipClickListener()
             }
 
@@ -270,6 +277,38 @@
         }
     }
 
+    private fun updateChipIcon(callInfo: CallNotificationInfo, currentChipView: View) {
+        val backgroundView = currentChipView.getBackgroundView() ?: return
+        backgroundView.removeView(currentChipView.getIconView())
+
+        val iconView = callInfo.iconView ?: return
+        with(iconView) {
+            id = ICON_ID
+            imageTintList = Utils.getColorAttr(context, android.R.attr.colorPrimary)
+            // TODO(b/354930838): Update the content description to not include "phone".
+            contentDescription =
+                context.resources.getString(R.string.ongoing_phone_call_content_description)
+        }
+
+        val currentParent = iconView.parent as? ViewGroup
+        // If we're reinflating the view, we may need to detach the icon view from the
+        // old chip before we reattach it to the new one.
+        // See also: NotificationIconContainerViewBinder#bindIcons.
+        if (currentParent != null && currentParent != backgroundView) {
+            currentParent.removeView(iconView)
+            currentParent.removeTransientView(iconView)
+        }
+        backgroundView.addView(iconView, /* index= */ 0, generateIconLayoutParams())
+    }
+
+    private fun generateIconLayoutParams(): FrameLayout.LayoutParams {
+        return FrameLayout.LayoutParams(iconSize, iconSize)
+    }
+
+    private val iconSize: Int
+        get() =
+            context.resources.getDimensionPixelSize(R.dimen.ongoing_activity_chip_notif_icon_size)
+
     private fun updateChipClickListener() {
         if (Flags.statusBarScreenSharingChips()) {
             return
@@ -279,8 +318,7 @@
             return
         }
         val currentChipView = chipView
-        val backgroundView =
-            currentChipView?.findViewById<View>(R.id.ongoing_activity_chip_background)
+        val backgroundView = currentChipView?.getBackgroundView()
         val intent = callNotificationInfo?.intent
         if (currentChipView != null && backgroundView != null && intent != null) {
             currentChipView.setOnClickListener {
@@ -332,6 +370,14 @@
         return this.findViewById(R.id.ongoing_activity_chip_time)
     }
 
+    private fun View.getBackgroundView(): ChipBackgroundContainer? {
+        return this.findViewById(R.id.ongoing_activity_chip_background)
+    }
+
+    private fun View.getIconView(): View? {
+        return this.findViewById(ICON_ID)
+    }
+
     /**
      * If there's an active ongoing call, then we will force the status bar to always show, even if
      * the user is in immersive mode. However, we also want to give users the ability to swipe away
@@ -359,6 +405,8 @@
         val uid: Int,
         /** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */
         val isOngoing: Boolean,
+        /** The view that contains the icon to display in the chip. */
+        val iconView: StatusBarIconView?,
         /** True if the user has swiped away the status bar while in this phone call. */
         val statusBarSwipedAway: Boolean
     ) {
@@ -464,6 +512,10 @@
             }
         }
     }
+
+    companion object {
+        @IdRes private val ICON_ID = R.id.ongoing_activity_chip_icon
+    }
 }
 
 private fun isCallNotification(entry: NotificationEntry): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
index 99ed2d9..cd442cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
@@ -21,6 +21,7 @@
 import android.telephony.TelephonyManager.EXTRA_PLMN
 import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
 import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
+import android.telephony.TelephonyManager.EXTRA_SPN
 import com.android.systemui.log.table.Diffable
 import com.android.systemui.log.table.TableRowLogger
 
@@ -96,7 +97,8 @@
 
 fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? {
     val showSpn = getBooleanExtra(EXTRA_SHOW_SPN, false)
-    val spn = getStringExtra(EXTRA_DATA_SPN)
+    val spn = getStringExtra(EXTRA_SPN)
+    val dataSpn = getStringExtra(EXTRA_DATA_SPN)
     val showPlmn = getBooleanExtra(EXTRA_SHOW_PLMN, false)
     val plmn = getStringExtra(EXTRA_PLMN)
 
@@ -112,6 +114,12 @@
         }
         str.append(spn)
     }
+    if (showSpn && dataSpn != null) {
+        if (str.isNotEmpty()) {
+            str.append(separator)
+        }
+        str.append(dataSpn)
+    }
 
     return if (str.isNotEmpty()) NetworkNameModel.IntentDerived(str.toString()) else null
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
index 91bfdff..3b392c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
@@ -50,14 +50,14 @@
         Surface(
             color = tileColor,
             shape = RoundedCornerShape(16.dp),
-            modifier =
-                Modifier.combinedClickable(
-                    onClick = viewModel.onClick,
-                    onLongClick = viewModel.onLongClick
-                ),
         ) {
             Row(
-                modifier = Modifier.padding(20.dp),
+                modifier =
+                    Modifier.combinedClickable(
+                            onClick = viewModel.onClick,
+                            onLongClick = viewModel.onLongClick
+                        )
+                        .padding(20.dp),
                 verticalAlignment = Alignment.CenterVertically,
                 horizontalArrangement =
                     Arrangement.spacedBy(
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
index f3b9cc1..9ae6674 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
@@ -40,14 +40,11 @@
 public class ToastFactory implements Dumpable {
     // only one ToastPlugin can be connected at a time.
     private ToastPlugin mPlugin;
-    private final LayoutInflater mLayoutInflater;
 
     @Inject
     public ToastFactory(
-            LayoutInflater layoutInflater,
             PluginManager pluginManager,
             DumpManager dumpManager) {
-        mLayoutInflater = layoutInflater;
         dumpManager.registerDumpable("ToastFactory", this);
         pluginManager.addPluginListener(
                 new PluginListener<ToastPlugin>() {
@@ -70,11 +67,12 @@
      */
     public SystemUIToast createToast(Context context, CharSequence text, String packageName,
             int userId, int orientation) {
+        LayoutInflater layoutInflater = LayoutInflater.from(context);
         if (isPluginAvailable()) {
-            return new SystemUIToast(mLayoutInflater, context, text, mPlugin.createToast(text,
+            return new SystemUIToast(layoutInflater, context, text, mPlugin.createToast(text,
                     packageName, userId), packageName, userId, orientation);
         }
-        return new SystemUIToast(mLayoutInflater, context, text, packageName, userId,
+        return new SystemUIToast(layoutInflater, context, text, packageName, userId,
                 orientation);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 85a455d..bbfa32b 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -128,7 +128,7 @@
                 return;
             }
             Context displayContext = context.createDisplayContext(display);
-            mToast = mToastFactory.createToast(mContext /* sysuiContext */, text, packageName,
+            mToast = mToastFactory.createToast(displayContext /* sysuiContext */, text, packageName,
                     userHandle.getIdentifier(), mOrientation);
 
             if (mToast.getInAnimation() != null) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index fbfe41f..521aa5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -69,6 +69,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bluetooth.BroadcastDialogController
 import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.media.controls.MediaTestUtils
 import com.android.systemui.media.controls.domain.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
@@ -211,6 +212,8 @@
     @Mock private lateinit var activityIntentHelper: ActivityIntentHelper
     @Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
 
+    @Mock private lateinit var communalSceneInteractor: CommunalSceneInteractor
+
     @Mock private lateinit var recommendationViewHolder: RecommendationViewHolder
     @Mock private lateinit var smartspaceAction: SmartspaceAction
     private lateinit var smartspaceData: SmartspaceMediaData
@@ -271,6 +274,7 @@
                     logger,
                     keyguardStateController,
                     activityIntentHelper,
+                    communalSceneInteractor,
                     lockscreenUserManager,
                     broadcastDialogController,
                     globalSettings,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
index 9a6423d..7ab3e29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
@@ -61,7 +61,7 @@
     public void testCloseQsSideEffects() {
         enableSplitShade(true);
         mQsController.setExpandImmediate(true);
-        mQsController.setExpanded(true);
+        mQsController.setExpansionHeight(800);
         mQsController.closeQs();
 
         assertThat(mQsController.getExpanded()).isEqualTo(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index c4371fd..8f49ba3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -38,8 +38,10 @@
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
 import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
+import com.android.systemui.statusbar.notification.icon.IconPack
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -254,6 +256,139 @@
             .isGreaterThan(0)
     }
 
+    @Test
+    @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+    fun onEntryUpdated_notifIconsNotSet_noIconInChip() {
+        val notification = createOngoingCallNotifEntry()
+
+        notifCollectionListener.onEntryUpdated(notification)
+
+        assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_icon)).isNull()
+    }
+
+    @Test
+    @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+    fun onEntryUpdated_notifIconsSetToNull_noIconInChip() {
+        val notification = createOngoingCallNotifEntry()
+        notification.icons = IconPack.buildEmptyPack(/* fromSource= */ null)
+
+        notifCollectionListener.onEntryUpdated(notification)
+
+        assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_icon)).isNull()
+    }
+
+    @Test
+    @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+    fun onEntryUpdated_notifIconsIncluded_statusBarChipIconUsed() {
+        val notification = createOngoingCallNotifEntry()
+
+        val statusBarChipIconView =
+            StatusBarIconView(
+                context,
+                /* slot= */ "OngoingCallControllerTest",
+                notification.sbn,
+            )
+        notification.icons =
+            IconPack.buildPack(
+                /* statusBarIcon= */ mock(StatusBarIconView::class.java),
+                statusBarChipIconView,
+                /* shelfIcon= */ mock(StatusBarIconView::class.java),
+                /* aodIcon= */ mock(StatusBarIconView::class.java),
+                /* source= */ null,
+            )
+
+        notifCollectionListener.onEntryUpdated(notification)
+
+        assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_icon))
+            .isEqualTo(statusBarChipIconView)
+        assertThat(statusBarChipIconView.contentDescription)
+            .isEqualTo(context.resources.getString(R.string.ongoing_phone_call_content_description))
+    }
+
+    @Test
+    @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+    fun onEntryUpdated_newNotifIcon_newIconUsed() {
+        val notification = createOngoingCallNotifEntry()
+
+        val firstStatusBarChipIconView =
+            StatusBarIconView(
+                context,
+                /* slot= */ "OngoingCallControllerTest",
+                notification.sbn,
+            )
+        notification.icons =
+            IconPack.buildPack(
+                /* statusBarIcon= */ mock(StatusBarIconView::class.java),
+                firstStatusBarChipIconView,
+                /* shelfIcon= */ mock(StatusBarIconView::class.java),
+                /* aodIcon= */ mock(StatusBarIconView::class.java),
+                /* source= */ null,
+            )
+        notifCollectionListener.onEntryUpdated(notification)
+
+        assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_icon))
+            .isEqualTo(firstStatusBarChipIconView)
+
+        // WHEN the notification is updated with a new icon
+        val secondStatusBarChipIconView =
+            StatusBarIconView(
+                context,
+                /* slot= */ "OngoingCallControllerTestTheSecond",
+                notification.sbn,
+            )
+        notification.icons =
+            IconPack.buildPack(
+                /* statusBarIcon= */ mock(StatusBarIconView::class.java),
+                secondStatusBarChipIconView,
+                /* shelfIcon= */ mock(StatusBarIconView::class.java),
+                /* aodIcon= */ mock(StatusBarIconView::class.java),
+                /* source= */ null,
+            )
+        notifCollectionListener.onEntryUpdated(notification)
+
+        // THEN the new icon is used
+        assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_icon))
+            .isEqualTo(secondStatusBarChipIconView)
+    }
+
+    @Test
+    @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+    fun chipViewReinflated_iconViewMovedToNewChip() {
+        val notification = createOngoingCallNotifEntry()
+        val statusBarChipIconView =
+            StatusBarIconView(
+                context,
+                /* slot= */ "OngoingCallControllerTest",
+                notification.sbn,
+            )
+        notification.icons =
+            IconPack.buildPack(
+                /* statusBarIcon= */ mock(StatusBarIconView::class.java),
+                statusBarChipIconView,
+                /* shelfIcon= */ mock(StatusBarIconView::class.java),
+                /* aodIcon= */ mock(StatusBarIconView::class.java),
+                /* source= */ null,
+            )
+
+        notifCollectionListener.onEntryUpdated(notification)
+
+        assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_icon))
+            .isEqualTo(statusBarChipIconView)
+
+        // WHEN we get a new chip view
+        lateinit var newChipView: View
+        TestableLooper.get(this).runWithLooper {
+            newChipView =
+                LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null)
+        }
+        controller.setChipView(newChipView)
+
+        // THEN the icon is detached from the old view and attached to the new one
+        assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_icon)).isNull()
+        assertThat(newChipView.findViewById<View>(R.id.ongoing_activity_chip_icon))
+            .isEqualTo(statusBarChipIconView)
+    }
+
     /** Regression test for b/194731244. */
     @Test
     fun onEntryUpdated_calledManyTimes_uidObserverOnlyRegisteredOnce() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 8fd0b31..9d83d5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -59,6 +59,7 @@
 import android.telephony.TelephonyManager.ERI_OFF
 import android.telephony.TelephonyManager.ERI_ON
 import android.telephony.TelephonyManager.EXTRA_CARRIER_ID
+import android.telephony.TelephonyManager.EXTRA_DATA_SPN
 import android.telephony.TelephonyManager.EXTRA_PLMN
 import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
 import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
@@ -85,7 +86,6 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.configWithOverride
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
-import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
@@ -93,8 +93,6 @@
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.mockito.withArgCaptor
@@ -112,6 +110,8 @@
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -815,9 +815,11 @@
             val intent = spnIntent()
             val captor = argumentCaptor<BroadcastReceiver>()
             verify(context).registerReceiver(captor.capture(), any())
-            captor.value!!.onReceive(context, intent)
+            captor.lastValue.onReceive(context, intent)
 
-            assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+            // spnIntent() sets all values to true and test strings
+            assertThat(latest)
+                .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN$SEP$DATA_SPN"))
 
             job.cancel()
         }
@@ -831,17 +833,19 @@
             val intent = spnIntent()
             val captor = argumentCaptor<BroadcastReceiver>()
             verify(context).registerReceiver(captor.capture(), any())
-            captor.value!!.onReceive(context, intent)
+            captor.lastValue.onReceive(context, intent)
 
-            assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+            assertThat(latest)
+                .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN$SEP$DATA_SPN"))
 
             // WHEN an intent with a different subId is sent
             val wrongSubIntent = spnIntent(subId = 101)
 
-            captor.value!!.onReceive(context, wrongSubIntent)
+            captor.lastValue.onReceive(context, wrongSubIntent)
 
             // THEN the previous intent's name is still used
-            assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+            assertThat(latest)
+                .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN$SEP$DATA_SPN"))
 
             job.cancel()
         }
@@ -855,9 +859,10 @@
             val intent = spnIntent()
             val captor = argumentCaptor<BroadcastReceiver>()
             verify(context).registerReceiver(captor.capture(), any())
-            captor.value!!.onReceive(context, intent)
+            captor.lastValue.onReceive(context, intent)
 
-            assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+            assertThat(latest)
+                .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN$SEP$DATA_SPN"))
 
             val intentWithoutInfo =
                 spnIntent(
@@ -865,7 +870,7 @@
                     showPlmn = false,
                 )
 
-            captor.value!!.onReceive(context, intentWithoutInfo)
+            captor.lastValue.onReceive(context, intentWithoutInfo)
 
             assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
 
@@ -884,10 +889,88 @@
             val intent = spnIntent()
             val captor = argumentCaptor<BroadcastReceiver>()
             verify(context).registerReceiver(captor.capture(), any())
-            captor.value!!.onReceive(context, intent)
+            captor.lastValue.onReceive(context, intent)
 
             // The value is still there despite no active subscribers
-            assertThat(underTest.networkName.value).isEqualTo(intent.toNetworkNameModel(SEP))
+            assertThat(underTest.networkName.value)
+                .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN$SEP$DATA_SPN"))
+        }
+
+    @Test
+    fun networkName_allFieldsSet() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.networkName)
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+
+            val intent =
+                spnIntent(
+                    subId = SUB_1_ID,
+                    showSpn = true,
+                    spn = SPN,
+                    dataSpn = null,
+                    showPlmn = true,
+                    plmn = PLMN,
+                )
+            captor.lastValue.onReceive(context, intent)
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
+        }
+
+    @Test
+    fun networkName_showPlmn_plmnNotNull_showSpn_spnNull_dataSpnNotNull() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.networkName)
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+            val intent =
+                spnIntent(
+                    subId = SUB_1_ID,
+                    showSpn = true,
+                    spn = null,
+                    dataSpn = DATA_SPN,
+                    showPlmn = true,
+                    plmn = PLMN,
+                )
+            captor.lastValue.onReceive(context, intent)
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+        }
+
+    @Test
+    fun networkName_showPlmn_noShowSPN() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.networkName)
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+            val intent =
+                spnIntent(
+                    subId = SUB_1_ID,
+                    showSpn = false,
+                    spn = SPN,
+                    dataSpn = DATA_SPN,
+                    showPlmn = true,
+                    plmn = PLMN,
+                )
+            captor.lastValue.onReceive(context, intent)
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN"))
+        }
+
+    @Test
+    fun networkName_showPlmn_plmnNull_showSpn() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.networkName)
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+            val intent =
+                spnIntent(
+                    subId = SUB_1_ID,
+                    showSpn = true,
+                    spn = SPN,
+                    dataSpn = DATA_SPN,
+                    showPlmn = true,
+                    plmn = null,
+                )
+            captor.lastValue.onReceive(context, intent)
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$SPN$SEP$DATA_SPN"))
         }
 
     @Test
@@ -1128,14 +1211,16 @@
     private fun spnIntent(
         subId: Int = SUB_1_ID,
         showSpn: Boolean = true,
-        spn: String = SPN,
+        spn: String? = SPN,
+        dataSpn: String? = DATA_SPN,
         showPlmn: Boolean = true,
-        plmn: String = PLMN,
+        plmn: String? = PLMN,
     ): Intent =
         Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED).apply {
             putExtra(EXTRA_SUBSCRIPTION_INDEX, subId)
             putExtra(EXTRA_SHOW_SPN, showSpn)
             putExtra(EXTRA_SPN, spn)
+            putExtra(EXTRA_DATA_SPN, dataSpn)
             putExtra(EXTRA_SHOW_PLMN, showPlmn)
             putExtra(EXTRA_PLMN, plmn)
         }
@@ -1148,6 +1233,7 @@
         private const val SEP = "-"
 
         private const val SPN = "testSpn"
+        private const val DATA_SPN = "testDataSpn"
         private const val PLMN = "testPlmn"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index 8df37ce..666bdd6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -134,7 +134,6 @@
                 mNotificationManager,
                 mAccessibilityManager,
                 new ToastFactory(
-                        mLayoutInflater,
                         mPluginManager,
                         mDumpManager),
                 mToastLogger);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
index 5410882..bade91a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
@@ -28,11 +28,15 @@
     private val userGestureMap = mutableMapOf<Int, GestureEduModel>()
     private val _gestureEduModels = MutableStateFlow(GestureEduModel())
     private val gestureEduModelsFlow = _gestureEduModels.asStateFlow()
+    private var currentUser: Int = 0
 
     override fun setUser(userId: Int) {
         if (!userGestureMap.contains(userId)) {
             userGestureMap[userId] = GestureEduModel()
         }
+        // save data of current user to the map
+        userGestureMap[currentUser] = _gestureEduModels.value
+        // switch to data of new user
         _gestureEduModels.value = userGestureMap[userId]!!
     }
 
@@ -41,13 +45,15 @@
     }
 
     override suspend fun incrementSignalCount(gestureType: GestureType) {
+        val originalModel = _gestureEduModels.value
         _gestureEduModels.value =
-            GestureEduModel(
+            originalModel.copy(
                 signalCount = _gestureEduModels.value.signalCount + 1,
             )
     }
 
     override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
-        _gestureEduModels.value = GestureEduModel(lastShortcutTriggeredTime = clock.instant())
+        val originalModel = _gestureEduModels.value
+        _gestureEduModels.value = originalModel.copy(lastShortcutTriggeredTime = clock.instant())
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
index 5b2dc2b..a7b322b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.education.data.repository.contextualEducationRepository
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
 
@@ -25,6 +26,7 @@
     Kosmos.Fixture {
         ContextualEducationInteractor(
             backgroundScope = testScope.backgroundScope,
+            backgroundDispatcher = testDispatcher,
             repository = contextualEducationRepository,
             selectedUserInteractor = selectedUserInteractor
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
index 8f84e04..fb4e901 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
@@ -19,6 +19,14 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 
+var Kosmos.keyboardTouchpadEduInteractor by
+    Kosmos.Fixture {
+        KeyboardTouchpadEduInteractor(
+            backgroundScope = testScope.backgroundScope,
+            contextualEducationInteractor = contextualEducationInteractor
+        )
+    }
+
 var Kosmos.keyboardTouchpadEduStatsInteractor by
     Kosmos.Fixture {
         KeyboardTouchpadEduStatsInteractorImpl(
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index 311addb..efa1397 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -26,6 +26,7 @@
     },
     srcs: [
         ":services.accessibility-sources",
+        ":statslog-accessibility-java-gen",
         "//frameworks/base/packages/SettingsLib/RestrictedLockUtils:SettingsLibRestrictedLockUtilsSrc",
     ],
     libs: [
@@ -37,7 +38,6 @@
         "a11ychecker-protos-java-proto-lite",
         "com_android_server_accessibility_flags_lib",
         "//frameworks/base/packages/SystemUI/aconfig:com_android_systemui_flags_lib",
-
     ],
 }
 
@@ -81,3 +81,12 @@
         "java/**/a11ychecker/proto/*.proto",
     ],
 }
+
+genrule {
+    name: "statslog-accessibility-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module accessibility" +
+        " --javaPackage com.android.server.accessibility.a11ychecker" +
+        " --javaClass AccessibilityCheckerStatsLog --minApiLevel 34",
+    out: ["java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerStatsLog.java"],
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerStatsdLogger.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerStatsdLogger.java
new file mode 100644
index 0000000..1b3ec5a
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerStatsdLogger.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.a11ychecker;
+
+import android.util.Slog;
+
+import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckResultReported;
+
+import java.util.Set;
+
+
+/**
+ * Wraps the StatsdLogger for AccessibilityCheckResultReported.
+ *
+ * @hide
+ */
+public class AccessibilityCheckerStatsdLogger {
+    private static final int ATOM_ID = 910;
+    private static final String LOG_TAG = "AccessibilityCheckerStatsdLogger";
+
+    /**
+     * Writes results to statsd.
+     */
+    public static void logResults(Set<AccessibilityCheckResultReported> results) {
+        Slog.i(LOG_TAG, String.format("Writing %d AccessibilityCheckResultReported events",
+                results.size()));
+
+        for (AccessibilityCheckResultReported result : results) {
+            AccessibilityCheckerStatsLog.write(ATOM_ID,
+                    result.getPackageName(),
+                    result.getAppVersionCode(),
+                    result.getUiElementPath(),
+                    result.getActivityName(),
+                    result.getWindowTitle(),
+                    result.getSourceComponentName(),
+                    result.getSourceVersionCode(),
+                    result.getResultCheckClass().getNumber(),
+                    result.getResultType().getNumber(),
+                    result.getResultId());
+        }
+    }
+}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9d4310c..363c1d8 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -148,7 +148,7 @@
         "android.hardware.common-V2-java",
         "android.hardware.light-V2.0-java",
         "android.hardware.gnss-V2-java",
-        "android.hardware.vibrator-V2-java",
+        "android.hardware.vibrator-V3-java",
         "app-compat-annotations",
         "framework-tethering.stubs.module_lib",
         "keepanno-annotations",
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index d42a3dc..0815384 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -155,6 +155,15 @@
     private static final int DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD =
             PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
 
+    // Comma separated list of all packages exempt from user impact level threshold. If a package
+    // in the list is crash looping, all the mitigations including factory reset will be performed.
+    private static final String PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD =
+            "persist.device_config.configuration.packages_exempt_from_impact_level_threshold";
+
+    // Comma separated list of default packages exempt from user impact level threshold.
+    private static final String DEFAULT_PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD =
+            "com.android.systemui";
+
     private long mNumberOfNativeCrashPollsRemaining;
 
     private static final int DB_VERSION = 1;
@@ -201,6 +210,8 @@
     private final DeviceConfig.OnPropertiesChangedListener
             mOnPropertyChangedListener = this::onPropertyChanged;
 
+    private final Set<String> mPackagesExemptFromImpactLevelThreshold = new ArraySet<>();
+
     // The set of packages that have been synced with the ExplicitHealthCheckController
     @GuardedBy("mLock")
     private Set<String> mRequestedHealthCheckPackages = new ArraySet<>();
@@ -523,7 +534,7 @@
                               @FailureReasons int failureReason,
                               int currentObserverImpact,
                               int mitigationCount) {
-        if (currentObserverImpact < getUserImpactLevelLimit()) {
+        if (allowMitigations(currentObserverImpact, versionedPackage)) {
             synchronized (mLock) {
                 mLastMitigation = mSystemClock.uptimeMillis();
             }
@@ -531,6 +542,13 @@
         }
     }
 
+    private boolean allowMitigations(int currentObserverImpact,
+            VersionedPackage versionedPackage) {
+        return currentObserverImpact < getUserImpactLevelLimit()
+                || getPackagesExemptFromImpactLevelThreshold().contains(
+                versionedPackage.getPackageName());
+    }
+
     private long getMitigationWindowMs() {
         return SystemProperties.getLong(MITIGATION_WINDOW_MS, DEFAULT_MITIGATION_WINDOW_MS);
     }
@@ -662,6 +680,15 @@
                 DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD);
     }
 
+    private Set<String> getPackagesExemptFromImpactLevelThreshold() {
+        if (mPackagesExemptFromImpactLevelThreshold.isEmpty()) {
+            String packageNames = SystemProperties.get(PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD,
+                    DEFAULT_PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD);
+            return Set.of(packageNames.split("\\s*,\\s*"));
+        }
+        return mPackagesExemptFromImpactLevelThreshold;
+    }
+
     /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
     @Retention(SOURCE)
     @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 504c54a..ab63e24 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2864,6 +2864,11 @@
                         }
                     }
 
+                    if (newAdj == clientAdj && app.isolated) {
+                        // Make bound isolated processes have slightly worse score than their client
+                        newAdj = clientAdj + 1;
+                    }
+
                     if (adj >  newAdj) {
                         adj = newAdj;
                         if (state.setCurRawAdj(adj, dryRun)) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3d41f05..7a24e9df 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -496,19 +496,60 @@
     private AudioSystemThread mAudioSystemThread;
     /** @see AudioHandler */
     private AudioHandler mAudioHandler;
-    /** @see VolumeStreamState */
-    private VolumeStreamState[] mStreamStates;
+    /**
+     *  @see VolumeStreamState
+     *  Mapping which contains for each stream type its associated {@link VolumeStreamState}
+     **/
+    private SparseArray<VolumeStreamState> mStreamStates;
 
     /*package*/ int getVssVolumeForDevice(int stream, int device) {
-        return mStreamStates[stream].getIndex(device);
+        final VolumeStreamState streamState = mStreamStates.get(stream);
+        return streamState != null ? streamState.getIndex(device) : -1;
     }
 
-    /*package*/ VolumeStreamState getVssVolumeForStream(int stream) {
-        return mStreamStates[stream];
+    /**
+     * Returns the {@link VolumeStreamState} corresponding to the passed stream type. This can be
+     * {@code null} since not all possible stream types have a valid {@link VolumeStreamState} (e.g.
+     * {@link AudioSystem#STREAM_BLUETOOTH_SCO}) is deprecated and will return a {@code null} stream
+     * state).
+     *
+     * @param stream the stream type for querying the stream state
+     *
+     * @return the {@link VolumeStreamState} corresponding to the passed stream type or {@code null}
+     */
+    @Nullable
+    /*package*/ VolumeStreamState getVssForStream(int stream) {
+        return mStreamStates.get(stream);
+    }
+
+    /**
+     * Returns the {@link VolumeStreamState} corresponding to the passed stream type. In case
+     * there is no associated stream state for the given stream type we return the default stream
+     * state for {@link AudioSystem#STREAM_MUSIC} (or throw an {@link IllegalArgumentException} in
+     * the ramp up phase of the replaceStreamBtSco flag to ensure that this case will never happen).
+     *
+     * @param stream the stream type for querying the stream state
+     *
+     * @return the {@link VolumeStreamState} corresponding to the passed stream type
+     */
+    @NonNull
+    /*package*/ VolumeStreamState getVssForStreamOrDefault(int stream) {
+        VolumeStreamState streamState = mStreamStates.get(stream);
+        if (streamState == null) {
+            if (replaceStreamBtSco()) {
+                throw new IllegalArgumentException("No VolumeStreamState for stream " + stream);
+            } else {
+                Log.e(TAG, "No VolumeStreamState for stream " + stream
+                        + ". Returning default state for STREAM_MUSIC", new Exception());
+                streamState = mStreamStates.get(AudioSystem.STREAM_MUSIC);
+            }
+        }
+        return streamState;
     }
 
     /*package*/ int getMaxVssVolumeForStream(int stream) {
-        return mStreamStates[stream].getMaxIndex();
+        final VolumeStreamState streamState = mStreamStates.get(stream);
+        return streamState != null ? streamState.getMaxIndex() : -1;
     }
 
     private SettingsObserver mSettingsObserver;
@@ -550,13 +591,13 @@
         0   // STREAM_ASSISTANT
     };
 
-    /* mStreamVolumeAlias[] indicates for each stream if it uses the volume settings
+    /* sStreamVolumeAlias[] indicates for each stream if it uses the volume settings
      * of another stream: This avoids multiplying the volume settings for hidden
      * stream types that follow other stream behavior for volume settings
      * NOTE: do not create loops in aliases!
      * Some streams alias to different streams according to device category (phone or tablet) or
      * use case (in call vs off call...). See updateStreamVolumeAlias() for more details.
-     *  mStreamVolumeAlias contains STREAM_VOLUME_ALIAS_VOICE aliases for a voice capable device
+     *  sStreamVolumeAlias contains STREAM_VOLUME_ALIAS_VOICE aliases for a voice capable device
      *  (phone), STREAM_VOLUME_ALIAS_TELEVISION for a television or set-top box and
      *  STREAM_VOLUME_ALIAS_DEFAULT for other devices (e.g. tablets).*/
     private final int[] STREAM_VOLUME_ALIAS_VOICE = new int[] {
@@ -621,12 +662,12 @@
         AudioSystem.STREAM_MUSIC,           // STREAM_ACCESSIBILITY
         AudioSystem.STREAM_MUSIC            // STREAM_ASSISTANT
     };
-    protected static int[] mStreamVolumeAlias;
+    protected static SparseIntArray sStreamVolumeAlias;
     private static final int UNSET_INDEX = -1;
 
     /**
      * Map AudioSystem.STREAM_* constants to app ops.  This should be used
-     * after mapping through mStreamVolumeAlias.
+     * after mapping through sStreamVolumeAlias.
      */
     private static final int[] STREAM_VOLUME_OPS = new int[] {
         AppOpsManager.OP_AUDIO_VOICE_VOLUME,            // STREAM_VOICE_CALL
@@ -1416,7 +1457,7 @@
         mRecordMonitor = new RecordingActivityMonitor(mContext);
         mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true);
 
-        // must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[]
+        // must be called before readPersistedSettings() which needs a valid sStreamVolumeAlias[]
         // array initialized by updateStreamVolumeAlias()
         updateStreamVolumeAlias(false /*updateVolumes*/, TAG);
         readPersistedSettings();
@@ -1473,7 +1514,7 @@
         int numStreamTypes = AudioSystem.getNumStreamTypes();
         synchronized (VolumeStreamState.class) {
             for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
-                VolumeStreamState streamState = mStreamStates[streamType];
+                final VolumeStreamState streamState = getVssForStream(streamType);
                 if (streamState == null) {
                     continue;
                 }
@@ -2083,7 +2124,10 @@
         // keep track of any error during stream volume initialization
         int status = AudioSystem.AUDIO_STATUS_OK;
         for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
-            VolumeStreamState streamState = mStreamStates[streamType];
+            VolumeStreamState streamState = getVssForStream(streamType);
+            if (streamState == null) {
+                continue;
+            }
             final int res = AudioSystem.initStreamVolume(
                     streamType, MIN_STREAM_VOLUME[streamType], MAX_STREAM_VOLUME[streamType]);
             if (res != AudioSystem.AUDIO_STATUS_OK) {
@@ -2243,12 +2287,13 @@
             synchronized (VolumeStreamState.class) {
                 int numStreamTypes = AudioSystem.getNumStreamTypes();
                 for (int streamType = 0; streamType < numStreamTypes; streamType++) {
-                    if (mStreamVolumeAlias[streamType] >= 0) {
-                        mStreamStates[streamType]
-                                .setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]], TAG);
+                    int streamAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
+                    final VolumeStreamState streamState = getVssForStream(streamType);
+                    if (streamAlias != -1 && streamState != null) {
+                        streamState.setAllIndexes(getVssForStream(streamAlias), TAG);
                         // apply stream volume
-                        if (!mStreamStates[streamType].mIsMuted) {
-                            mStreamStates[streamType].applyAllVolumes();
+                        if (!streamState.mIsMuted) {
+                            streamState.applyAllVolumes();
                         }
                     }
                 }
@@ -2348,11 +2393,17 @@
         if (device == AudioSystem.DEVICE_OUT_SPEAKER_SAFE) {
             device = AudioSystem.DEVICE_OUT_SPEAKER;
         }
-        if (!mStreamStates[streamType].hasIndexForDevice(device)) {
+
+        final VolumeStreamState streamState = getVssForStream(streamType);
+        if (streamState == null) {
+            // nothing to update
+            return;
+        }
+
+        if (!streamState.hasIndexForDevice(device)) {
             // set the default value, if device is affected by a full/fix/abs volume rule, it
             // will taken into account in checkFixedVolumeDevices()
-            mStreamStates[streamType].setIndex(
-                    mStreamStates[mStreamVolumeAlias[streamType]]
+            streamState.setIndex(getVssForStreamOrDefault(sStreamVolumeAlias.get(streamType))
                             .getIndex(AudioSystem.DEVICE_OUT_DEFAULT),
                     device, caller, true /*hasModifyAudioSettings*/);
         }
@@ -2365,11 +2416,11 @@
         for (AudioDeviceAttributes deviceAttributes : devicesForAttributes) {
             if (deviceAttributes.getType() == AudioDeviceInfo.convertInternalDeviceToDeviceType(
                     device)) {
-                mStreamStates[streamType].checkFixedVolumeDevices();
+                streamState.checkFixedVolumeDevices();
 
                 // Unmute streams if required and device is full volume
                 if (isStreamMute(streamType) && mFullVolumeDevices.contains(device)) {
-                    mStreamStates[streamType].mute(false, "updateVolumeStates(" + caller);
+                    streamState.mute(false, "updateVolumeStates(" + caller);
                 }
             }
         }
@@ -2379,22 +2430,27 @@
     {
         int numStreamTypes = AudioSystem.getNumStreamTypes();
         for (int streamType = 0; streamType < numStreamTypes; streamType++) {
-            if (mStreamStates[streamType] != null) {
-                mStreamStates[streamType].checkFixedVolumeDevices();
+            final VolumeStreamState vss = getVssForStream(streamType);
+            if (vss != null) {
+                vss.checkFixedVolumeDevices();
             }
         }
     }
 
     private void checkAllFixedVolumeDevices(int streamType) {
-        mStreamStates[streamType].checkFixedVolumeDevices();
+        final VolumeStreamState vss = getVssForStream(streamType);
+        if (vss == null) {
+            return;
+        }
+        vss.checkFixedVolumeDevices();
     }
 
     private void checkMuteAffectedStreams() {
         // any stream with a min level > 0 is not muteable by definition
         // STREAM_VOICE_CALL and STREAM_BLUETOOTH_SCO can be muted by applications
         // that has the the MODIFY_PHONE_STATE permission.
-        for (int i = 0; i < mStreamStates.length; i++) {
-            final VolumeStreamState vss = mStreamStates[i];
+        for (int i = 0; i < mStreamStates.size(); i++) {
+            final VolumeStreamState vss = mStreamStates.valueAt(i);
             if (vss != null && vss.mIndexMin > 0
                     && (vss.mStreamType != AudioSystem.STREAM_VOICE_CALL
                     && vss.mStreamType != AudioSystem.STREAM_BLUETOOTH_SCO)) {
@@ -2406,13 +2462,14 @@
 
     private void createStreamStates() {
         int numStreamTypes = AudioSystem.getNumStreamTypes();
-        VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes];
+        mStreamStates = new SparseArray<>(numStreamTypes);
 
         for (int i = 0; i < numStreamTypes; i++) {
-            // a negative mStreamVolumeAlias value means the stream state type is not supported
-            if (mStreamVolumeAlias[i] >= 0) {
-                streams[i] =
-                        new VolumeStreamState(System.VOLUME_SETTINGS_INT[mStreamVolumeAlias[i]], i);
+            final int streamAlias = sStreamVolumeAlias.get(i, /*valueIfKeyNotFound=*/-1);
+            // a negative sStreamVolumeAlias value means the stream state type is not supported
+            if (streamAlias >= 0) {
+                mStreamStates.set(i,
+                        new VolumeStreamState(System.VOLUME_SETTINGS_INT[streamAlias], i));
             }
         }
 
@@ -2431,24 +2488,25 @@
      * For other volume groups not linked to any streams, default music stream index is considered.
      */
     private void updateDefaultVolumes() {
-        for (int stream = 0; stream < mStreamStates.length; stream++) {
-            int streamVolumeAlias = mStreamVolumeAlias[stream];
+        for (int stream = 0; stream < mStreamStates.size(); stream++) {
+            int streamType = mStreamStates.keyAt(stream);
+            int streamVolumeAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
             if (mUseVolumeGroupAliases) {
-                if (AudioSystem.DEFAULT_STREAM_VOLUME[stream] != UNSET_INDEX) {
+                if (AudioSystem.DEFAULT_STREAM_VOLUME[streamType] != UNSET_INDEX) {
                     // Already initialized through default property based mecanism.
                     continue;
                 }
                 streamVolumeAlias = AudioSystem.STREAM_MUSIC;
-                int defaultAliasVolume = getUiDefaultRescaledIndex(streamVolumeAlias, stream);
-                if ((defaultAliasVolume >= MIN_STREAM_VOLUME[stream])
-                        && (defaultAliasVolume <= MAX_STREAM_VOLUME[stream])) {
-                    AudioSystem.DEFAULT_STREAM_VOLUME[stream] = defaultAliasVolume;
+                int defaultAliasVolume = getUiDefaultRescaledIndex(streamVolumeAlias, streamType);
+                if ((defaultAliasVolume >= MIN_STREAM_VOLUME[streamType])
+                        && (defaultAliasVolume <= MAX_STREAM_VOLUME[streamType])) {
+                    AudioSystem.DEFAULT_STREAM_VOLUME[streamType] = defaultAliasVolume;
                     continue;
                 }
             }
-            if (streamVolumeAlias >= 0 && stream != streamVolumeAlias) {
-                AudioSystem.DEFAULT_STREAM_VOLUME[stream] =
-                        getUiDefaultRescaledIndex(streamVolumeAlias, stream);
+            if (streamVolumeAlias >= 0 && streamType != streamVolumeAlias) {
+                AudioSystem.DEFAULT_STREAM_VOLUME[streamType] =
+                        getUiDefaultRescaledIndex(streamVolumeAlias, streamType);
             }
         }
     }
@@ -2490,13 +2548,17 @@
                 continue;
             }
             StringBuilder alias = new StringBuilder();
-            if (mStreamVolumeAlias[i] != i) {
+            final int streamAlias = sStreamVolumeAlias.get(i, /*valueIfKeyNotFound*/-1);
+            if (streamAlias != i && streamAlias != -1) {
                 alias.append(" (aliased to: ")
-                        .append(AudioSystem.STREAM_NAMES[mStreamVolumeAlias[i]])
+                        .append(AudioSystem.STREAM_NAMES[streamAlias])
                         .append(")");
             }
             pw.println("- " + AudioSystem.STREAM_NAMES[i] + alias + ":");
-            mStreamStates[i].dump(pw);
+            final VolumeStreamState vss = getVssForStream(i);
+            if (vss != null) {
+                vss.dump(pw);
+            }
             pw.println("");
         }
         pw.print("\n- mute affected streams = 0x");
@@ -2505,6 +2567,13 @@
         pw.println(Integer.toHexString(mUserMutableStreams));
     }
 
+    private void initStreamVolumeAlias(int[] streamVolumeAlias) {
+        sStreamVolumeAlias = new SparseIntArray(streamVolumeAlias.length);
+        for (int i = 0; i < streamVolumeAlias.length; ++i) {
+            sStreamVolumeAlias.put(i, streamVolumeAlias[i]);
+        }
+    }
+
     private void updateStreamVolumeAlias(boolean updateVolumes, String caller) {
         int dtmfStreamAlias;
         final int a11yStreamAlias = sIndependentA11yVolume ?
@@ -2514,24 +2583,24 @@
                 AudioSystem.STREAM_ASSISTANT : AudioSystem.STREAM_MUSIC;
 
         if (mIsSingleVolume) {
-            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION.clone();
+            initStreamVolumeAlias(STREAM_VOLUME_ALIAS_TELEVISION);
             dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
         } else if (mUseVolumeGroupAliases) {
-            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_NONE.clone();
+            initStreamVolumeAlias(STREAM_VOLUME_ALIAS_NONE);
             dtmfStreamAlias = AudioSystem.STREAM_DTMF;
         } else {
             switch (mPlatformType) {
                 case AudioSystem.PLATFORM_VOICE:
-                    mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE.clone();
+                    initStreamVolumeAlias(STREAM_VOLUME_ALIAS_VOICE);
                     dtmfStreamAlias = AudioSystem.STREAM_RING;
                     break;
                 default:
-                    mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT.clone();
+                    initStreamVolumeAlias(STREAM_VOLUME_ALIAS_DEFAULT);
                     dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
             }
             if (!mNotifAliasRing) {
-                mStreamVolumeAlias[AudioSystem.STREAM_NOTIFICATION] =
-                        AudioSystem.STREAM_NOTIFICATION;
+                sStreamVolumeAlias.put(AudioSystem.STREAM_NOTIFICATION,
+                        AudioSystem.STREAM_NOTIFICATION);
             }
         }
 
@@ -2546,15 +2615,14 @@
             }
         }
 
-        mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias;
-        mStreamVolumeAlias[AudioSystem.STREAM_ACCESSIBILITY] = a11yStreamAlias;
-        mStreamVolumeAlias[AudioSystem.STREAM_ASSISTANT] = assistantStreamAlias;
+        sStreamVolumeAlias.put(AudioSystem.STREAM_DTMF, dtmfStreamAlias);
+        sStreamVolumeAlias.put(AudioSystem.STREAM_ACCESSIBILITY, a11yStreamAlias);
+        sStreamVolumeAlias.put(AudioSystem.STREAM_ASSISTANT, assistantStreamAlias);
 
         if (replaceStreamBtSco()) {
             // we do not support STREAM_BLUETOOTH_SCO, this will lead to having
-            // mStreanStates[STREAM_BLUETOOTH_SCO] = null
-            // TODO: replace arrays with SparseIntArrays to avoid null checks
-            mStreamVolumeAlias[AudioSystem.STREAM_BLUETOOTH_SCO] = -1;
+            // mStreanStates.get(STREAM_BLUETOOTH_SCO) == null
+            sStreamVolumeAlias.delete(AudioSystem.STREAM_BLUETOOTH_SCO);
         }
 
         if (updateVolumes && mStreamStates != null) {
@@ -2562,17 +2630,17 @@
 
             synchronized (mSettingsLock) {
                 synchronized (VolumeStreamState.class) {
-                    mStreamStates[AudioSystem.STREAM_DTMF]
-                            .setAllIndexes(mStreamStates[dtmfStreamAlias], caller);
-                    mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].setSettingName(
+                    getVssForStreamOrDefault(AudioSystem.STREAM_DTMF)
+                            .setAllIndexes(getVssForStreamOrDefault(dtmfStreamAlias), caller);
+                    getVssForStreamOrDefault(AudioSystem.STREAM_ACCESSIBILITY).setSettingName(
                             System.VOLUME_SETTINGS_INT[a11yStreamAlias]);
-                    mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].setAllIndexes(
-                            mStreamStates[a11yStreamAlias], caller);
+                    getVssForStreamOrDefault(AudioSystem.STREAM_ACCESSIBILITY).setAllIndexes(
+                            getVssForStreamOrDefault(a11yStreamAlias), caller);
                 }
             }
             if (sIndependentA11yVolume) {
                 // restore the a11y values from the settings
-                mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].readSettings();
+                getVssForStreamOrDefault(AudioSystem.STREAM_ACCESSIBILITY).readSettings();
             }
 
             // apply stream mute states according to new value of mRingerModeAffectedStreams
@@ -2582,13 +2650,13 @@
                     SENDMSG_QUEUE,
                     0,
                     0,
-                    mStreamStates[AudioSystem.STREAM_DTMF], 0);
+                    getVssForStreamOrDefault(AudioSystem.STREAM_DTMF), 0);
             sendMsg(mAudioHandler,
                     MSG_SET_ALL_VOLUMES,
                     SENDMSG_QUEUE,
                     0,
                     0,
-                    mStreamStates[AudioSystem.STREAM_ACCESSIBILITY], 0);
+                    getVssForStreamOrDefault(AudioSystem.STREAM_ACCESSIBILITY), 0);
         }
         dispatchStreamAliasingUpdate();
     }
@@ -3065,7 +3133,8 @@
     }
 
     private int getIndexRange(int streamType) {
-        return (mStreamStates[streamType].getMaxIndex() - mStreamStates[streamType].getMinIndex());
+        return (getVssForStreamOrDefault(streamType).getMaxIndex() - getVssForStreamOrDefault(
+                streamType).getMinIndex());
     }
 
     private int rescaleIndex(VolumeInfo volumeInfo, int dstStream) {
@@ -3073,11 +3142,12 @@
                 || volumeInfo.getMinVolumeIndex() == VolumeInfo.INDEX_NOT_SET
                 || volumeInfo.getMaxVolumeIndex() == VolumeInfo.INDEX_NOT_SET) {
             Log.e(TAG, "rescaleIndex: volumeInfo has invalid index or range");
-            return mStreamStates[dstStream].getMinIndex();
+            return getVssForStreamOrDefault(dstStream).getMinIndex();
         }
         return rescaleIndex(volumeInfo.getVolumeIndex(),
                 volumeInfo.getMinVolumeIndex(), volumeInfo.getMaxVolumeIndex(),
-                mStreamStates[dstStream].getMinIndex(), mStreamStates[dstStream].getMaxIndex());
+                getVssForStreamOrDefault(dstStream).getMinIndex(),
+                getVssForStreamOrDefault(dstStream).getMaxIndex());
     }
 
     private int rescaleIndex(int index, int srcStream, VolumeInfo dstVolumeInfo) {
@@ -3088,14 +3158,17 @@
             return index;
         }
         return rescaleIndex(index,
-                mStreamStates[srcStream].getMinIndex(), mStreamStates[srcStream].getMaxIndex(),
+                getVssForStreamOrDefault(srcStream).getMinIndex(),
+                getVssForStreamOrDefault(srcStream).getMaxIndex(),
                 dstMin, dstMax);
     }
 
     private int rescaleIndex(int index, int srcStream, int dstStream) {
         return rescaleIndex(index,
-                mStreamStates[srcStream].getMinIndex(), mStreamStates[srcStream].getMaxIndex(),
-                mStreamStates[dstStream].getMinIndex(), mStreamStates[dstStream].getMaxIndex());
+                getVssForStreamOrDefault(srcStream).getMinIndex(),
+                getVssForStreamOrDefault(srcStream).getMaxIndex(),
+                getVssForStreamOrDefault(dstStream).getMinIndex(),
+                getVssForStreamOrDefault(dstStream).getMaxIndex());
     }
 
     private int rescaleIndex(int index, int srcMin, int srcMax, int dstMin, int dstMax) {
@@ -3618,7 +3691,7 @@
         streamType = replaceBtScoStreamWithVoiceCall(streamType, "adjustSuggestedStreamVolume");
 
         ensureValidStreamType(streamType);
-        final int resolvedStream = mStreamVolumeAlias[streamType];
+        final int resolvedStream = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
 
         // Play sounds on STREAM_RING only.
         if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
@@ -3735,9 +3808,9 @@
         // use stream type alias here so that streams with same alias have the same behavior,
         // including with regard to silent mode control (e.g the use of STREAM_RING below and in
         // checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION)
-        int streamTypeAlias = mStreamVolumeAlias[streamType];
+        int streamTypeAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
 
-        VolumeStreamState streamState = mStreamStates[streamTypeAlias];
+        VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias);
 
         final int device = getDeviceForStream(streamTypeAlias);
 
@@ -3823,7 +3896,7 @@
         if (!volumeAdjustmentAllowedByDnd(streamTypeAlias, flags)) {
             adjustVolume = false;
         }
-        int oldIndex = mStreamStates[streamType].getIndex(device);
+        int oldIndex = getVssForStreamOrDefault(streamType).getIndex(device);
 
         // Check if the volume adjustment should be handled by an absolute volume controller instead
         if (isAbsoluteVolumeDevice(device) && (flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0) {
@@ -3879,7 +3952,7 @@
                         0);
             }
 
-            int newIndex = mStreamStates[streamType].getIndex(device);
+            int newIndex = getVssForStreamOrDefault(streamType).getIndex(device);
 
             int streamToDriveAbsVol = absVolumeIndexFix() ? getBluetoothContextualVolumeStream() :
                     AudioSystem.STREAM_MUSIC;
@@ -3906,7 +3979,7 @@
                             + newIndex + " stream=" + streamType);
                 }
                 mDeviceBroker.postSetLeAudioVolumeIndex(newIndex,
-                    mStreamStates[streamType].getMaxIndex(), streamType);
+                        getVssForStreamOrDefault(streamType).getMaxIndex(), streamType);
             }
 
             // Check if volume update should be send to Hearing Aid.
@@ -3922,7 +3995,7 @@
             }
         }
 
-        final int newIndex = mStreamStates[streamType].getIndex(device);
+        final int newIndex = getVssForStreamOrDefault(streamType).getIndex(device);
         if (adjustVolume) {
             synchronized (mHdmiClientLock) {
                 if (mHdmiManager != null) {
@@ -4004,22 +4077,22 @@
         synchronized (mSettingsLock) {
             synchronized (VolumeStreamState.class) {
                 List<Integer> streamsToMute = new ArrayList<>();
-                for (int stream = 0; stream < mStreamStates.length; stream++) {
-                    VolumeStreamState vss = mStreamStates[stream];
-                    if (vss != null && streamAlias == mStreamVolumeAlias[stream]
+                for (int stream = 0; stream < mStreamStates.size(); stream++) {
+                    final VolumeStreamState vss = mStreamStates.valueAt(stream);
+                    if (vss != null && streamAlias == sStreamVolumeAlias.get(vss.getStreamType())
                             && vss.isMutable()) {
                         if (!(mCameraSoundForced && (vss.getStreamType()
                                 == AudioSystem.STREAM_SYSTEM_ENFORCED))) {
                             boolean changed = vss.mute(state, /* apply= */ false,
                                     "muteAliasStreams");
                             if (changed) {
-                                streamsToMute.add(stream);
+                                streamsToMute.add(vss.getStreamType());
                             }
                         }
                     }
                 }
                 streamsToMute.forEach(streamToMute -> {
-                    mStreamStates[streamToMute].doMute();
+                    getVssForStreamOrDefault(streamToMute).doMute();
                     broadcastMuteSetting(streamToMute, state);
                 });
             }
@@ -4047,7 +4120,7 @@
         // vss.updateVolumeGroupIndex
         synchronized (mSettingsLock) {
             synchronized (VolumeStreamState.class) {
-                final VolumeStreamState streamState = mStreamStates[streamAlias];
+                final VolumeStreamState streamState = getVssForStreamOrDefault(streamAlias);
                 // if unmuting causes a change, it was muted
                 wasMuted = streamState.mute(false, "onUnmuteStreamOnSingleVolDevice");
                 if (wasMuted) {
@@ -4145,7 +4218,7 @@
      */
     /*package*/ void onSetStreamVolume(int streamType, int index, int flags, int device,
             String caller, boolean hasModifyAudioSettings, boolean canChangeMute) {
-        final int stream = mStreamVolumeAlias[streamType];
+        final int stream = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
         // setting volume on ui sounds stream type also controls silent mode
         if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
                 (stream == getUiSoundsStreamType())) {
@@ -4286,26 +4359,30 @@
         // that can interfere with the sending of the VOLUME_CHANGED_ACTION intent
         mAudioSystem.clearRoutingCache();
 
+        int streamType = replaceBtScoStreamWithVoiceCall(vi.getStreamType(), "setDeviceVolume");
+
+        final VolumeStreamState vss = getVssForStream(streamType);
+
         // log the current device that will be used when evaluating the sending of the
         // VOLUME_CHANGED_ACTION intent to see if the current device is the one being modified
-        final int currDev = getDeviceForStream(vi.getStreamType());
+        final int currDev = getDeviceForStream(streamType);
 
-        final boolean skipping = (currDev == ada.getInternalType());
+        final boolean skipping = (currDev == ada.getInternalType()) || (vss == null);
 
-        AudioService.sVolumeLogger.enqueue(new DeviceVolumeEvent(vi.getStreamType(), index, ada,
+        AudioService.sVolumeLogger.enqueue(new DeviceVolumeEvent(streamType, index, ada,
                 currDev, callingPackage, skipping));
 
         if (skipping) {
-            // setDeviceVolume was called on a device currently being used
+            // setDeviceVolume was called on a device currently being used or stream state is null
             return;
         }
 
         // TODO handle unmuting of current audio device
         // if a stream is not muted but the VolumeInfo is for muting, set the volume index
         // for the device to min volume
-        if (vi.hasMuteCommand() && vi.isMuted() && !isStreamMute(vi.getStreamType())) {
-            setStreamVolumeWithAttributionInt(vi.getStreamType(),
-                    mStreamStates[vi.getStreamType()].getMinIndex(),
+        if (vi.hasMuteCommand() && vi.isMuted() && !isStreamMute(streamType)) {
+            setStreamVolumeWithAttributionInt(streamType,
+                    vss.getMinIndex(),
                     /*flags*/ 0,
                     ada, callingPackage, null,
                     //TODO handle unmuting of current audio device
@@ -4319,22 +4396,22 @@
         if (vi.getMinVolumeIndex() == VolumeInfo.INDEX_NOT_SET
                 || vi.getMaxVolumeIndex() == VolumeInfo.INDEX_NOT_SET) {
             // assume index meant to be in stream type range, validate
-            if ((index * 10) < mStreamStates[vi.getStreamType()].getMinIndex()
-                    || (index * 10) > mStreamStates[vi.getStreamType()].getMaxIndex()) {
+            if ((index * 10) < vss.getMinIndex()
+                    || (index * 10) > vss.getMaxIndex()) {
                 throw new IllegalArgumentException("invalid volume index " + index
                         + " not between min/max for stream " + vi.getStreamType());
             }
         } else {
             // check if index needs to be rescaled
-            final int min = (mStreamStates[vi.getStreamType()].getMinIndex() + 5) / 10;
-            final int max = (mStreamStates[vi.getStreamType()].getMaxIndex() + 5) / 10;
+            final int min = (vss.getMinIndex() + 5) / 10;
+            final int max = (vss.getMaxIndex() + 5) / 10;
             if (vi.getMinVolumeIndex() != min || vi.getMaxVolumeIndex() != max) {
                 index = rescaleIndex(index,
                         /*srcMin*/ vi.getMinVolumeIndex(), /*srcMax*/ vi.getMaxVolumeIndex(),
                         /*dstMin*/ min, /*dstMax*/ max);
             }
         }
-        setStreamVolumeWithAttributionInt(vi.getStreamType(), index, /*flags*/ 0,
+        setStreamVolumeWithAttributionInt(streamType, index, /*flags*/ 0,
                 ada, callingPackage, null,
                 false /*canChangeMuteAndUpdateController*/);
     }
@@ -4762,7 +4839,7 @@
 
         if (AudioSystem.isLeAudioDeviceType(device)) {
             mDeviceBroker.postSetLeAudioVolumeIndex(index * 10,
-                    mStreamStates[streamType].getMaxIndex(), streamType);
+                    getVssForStreamOrDefault(streamType).getMaxIndex(), streamType);
         } else if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {
             mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType);
         } else {
@@ -4790,8 +4867,8 @@
         streamType = replaceBtScoStreamWithVoiceCall(streamType, "setStreamVolume");
 
         ensureValidStreamType(streamType);
-        int streamTypeAlias = mStreamVolumeAlias[streamType];
-        VolumeStreamState streamState = mStreamStates[streamTypeAlias];
+        int streamTypeAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound*/-1);
+        final VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias);
 
         if (!replaceStreamBtSco() && (streamType == AudioManager.STREAM_VOICE_CALL)
                 && isInCommunication() && mDeviceBroker.isBluetoothScoActive()) {
@@ -4857,7 +4934,7 @@
                         // ada is non-null when called from setDeviceVolume,
                         // which shouldn't update the mute state
                         canChangeMuteAndUpdateController /*canChangeMute*/);
-                index = mStreamStates[streamType].getIndex(device);
+                index = getVssForStreamOrDefault(streamType).getIndex(device);
             }
         }
 
@@ -4885,8 +4962,8 @@
                 Log.d(TAG, "setStreamVolume postSetLeAudioVolumeIndex index="
                         + index + " stream=" + streamType);
             }
-            mDeviceBroker.postSetLeAudioVolumeIndex(index, mStreamStates[streamType].getMaxIndex(),
-                    streamType);
+            mDeviceBroker.postSetLeAudioVolumeIndex(index,
+                    getVssForStreamOrDefault(streamType).getMaxIndex(), streamType);
         }
 
         if (device == AudioSystem.DEVICE_OUT_HEARING_AID
@@ -4916,7 +4993,7 @@
                         // ada is non-null when called from setDeviceVolume,
                         // which shouldn't update the mute state
                         canChangeMuteAndUpdateController /*canChangeMute*/);
-                index = mStreamStates[streamType].getIndex(device);
+                index = getVssForStreamOrDefault(streamType).getIndex(device);
             }
         }
 
@@ -5099,7 +5176,7 @@
     // UI update and Broadcast Intent
     protected void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags, int device)
     {
-        streamType = mStreamVolumeAlias[streamType];
+        streamType = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
 
         if (streamType == AudioSystem.STREAM_MUSIC && isFullVolumeDevice(device)) {
             flags &= ~AudioManager.FLAG_SHOW_UI;
@@ -5147,7 +5224,7 @@
         if (isFullVolumeDevice(device)) {
             return;
         }
-        VolumeStreamState streamState = mStreamStates[streamType];
+        final VolumeStreamState streamState = getVssForStreamOrDefault(streamType);
 
         if (streamState.setIndex(index, device, caller, hasModifyAudioSettings) || force) {
             // Post message to set system volume (it in turn will post a message
@@ -5171,7 +5248,7 @@
 
         synchronized (VolumeStreamState.class) {
             ensureValidStreamType(streamType);
-            return mStreamStates[streamType].mIsMuted;
+            return getVssForStreamOrDefault(streamType).mIsMuted;
         }
     }
 
@@ -5270,7 +5347,7 @@
             if (applyRequired) {
                 // Assumes only STREAM_MUSIC going through DEVICE_OUT_REMOTE_SUBMIX
                 checkAllFixedVolumeDevices(AudioSystem.STREAM_MUSIC);
-                mStreamStates[AudioSystem.STREAM_MUSIC].applyAllVolumes();
+                getVssForStreamOrDefault(AudioSystem.STREAM_MUSIC).applyAllVolumes();
             }
         }
     }
@@ -5351,15 +5428,16 @@
 
     private int getStreamVolume(int streamType, int device) {
         synchronized (VolumeStreamState.class) {
-            int index = mStreamStates[streamType].getIndex(device);
+            final VolumeStreamState vss = getVssForStreamOrDefault(streamType);
+            int index = vss.getIndex(device);
 
             // by convention getStreamVolume() returns 0 when a stream is muted.
-            if (mStreamStates[streamType].mIsMuted) {
+            if (vss.mIsMuted) {
                 index = 0;
             }
-            if (index != 0 && (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) &&
-                    isFixedVolumeDevice(device)) {
-                index = mStreamStates[streamType].getMaxIndex();
+            if (index != 0 && (sStreamVolumeAlias.get(streamType) == AudioSystem.STREAM_MUSIC)
+                    && isFixedVolumeDevice(device)) {
+                index = vss.getMaxIndex();
             }
             return (index + 5) / 10;
         }
@@ -5382,20 +5460,27 @@
             return getDefaultVolumeInfo();
         }
 
-        int streamType = vi.getStreamType();
+        int streamType = replaceBtScoStreamWithVoiceCall(vi.getStreamType(), "getStreamMaxVolume");
         final VolumeInfo.Builder vib = new VolumeInfo.Builder(vi);
-        vib.setMinVolumeIndex((mStreamStates[streamType].mIndexMin + 5) / 10);
-        vib.setMaxVolumeIndex((mStreamStates[streamType].mIndexMax + 5) / 10);
+        final VolumeStreamState vss = getVssForStream(streamType);
+        if (vss == null) {
+            Log.w(TAG,
+                    "getDeviceVolume unsupported stream type " + streamType + ". Return default");
+            return getDefaultVolumeInfo();
+        }
+
+        vib.setMinVolumeIndex((vss.mIndexMin + 5) / 10);
+        vib.setMaxVolumeIndex((vss.mIndexMax + 5) / 10);
         synchronized (VolumeStreamState.class) {
             final int index;
             if (isFixedVolumeDevice(ada.getInternalType())) {
-                index = (mStreamStates[streamType].mIndexMax + 5) / 10;
+                index = (vss.mIndexMax + 5) / 10;
             } else {
-                index = (mStreamStates[streamType].getIndex(ada.getInternalType()) + 5) / 10;
+                index = (vss.getIndex(ada.getInternalType()) + 5) / 10;
             }
             vib.setVolumeIndex(index);
             // only set as a mute command if stream muted
-            if (mStreamStates[streamType].mIsMuted) {
+            if (vss.mIsMuted) {
                 vib.setMuted(true);
             }
             return vib.build();
@@ -5406,7 +5491,7 @@
     public int getStreamMaxVolume(int streamType) {
         streamType = replaceBtScoStreamWithVoiceCall(streamType, "getStreamMaxVolume");
         ensureValidStreamType(streamType);
-        return (mStreamStates[streamType].getMaxIndex() + 5) / 10;
+        return (getVssForStreamOrDefault(streamType).getMaxIndex() + 5) / 10;
     }
 
     /** @see AudioManager#getStreamMinVolumeInt(int)
@@ -5419,7 +5504,7 @@
                  || callingHasAudioSettingsPermission()
                  || (mContext.checkCallingPermission(MODIFY_AUDIO_ROUTING)
                         == PackageManager.PERMISSION_GRANTED);
-        return (mStreamStates[streamType].getMinIndex(isPrivileged) + 5) / 10;
+        return (getVssForStreamOrDefault(streamType).getMinIndex(isPrivileged) + 5) / 10;
     }
 
     @android.annotation.EnforcePermission(QUERY_AUDIO_STATE)
@@ -5432,7 +5517,7 @@
         ensureValidStreamType(streamType);
 
         int device = getDeviceForStream(streamType);
-        return (mStreamStates[streamType].getIndex(device) + 5) / 10;
+        return (getVssForStreamOrDefault(streamType).getIndex(device) + 5) / 10;
     }
 
     /**
@@ -5502,9 +5587,10 @@
                     .boxed().toList());
         }
         ArrayList<Integer> res = new ArrayList(1);
-        for (int stream : mStreamVolumeAlias) {
-            if (stream >= 0 && !res.contains(stream)) {
-                res.add(stream);
+        for (int stream = 0; stream < sStreamVolumeAlias.size(); ++stream) {
+            final int streamAlias = sStreamVolumeAlias.valueAt(stream);
+            if (!res.contains(streamAlias)) {
+                res.add(streamAlias);
             }
         }
         return res;
@@ -5525,7 +5611,7 @@
         // verify parameters
         ensureValidStreamType(sourceStreamType);
 
-        return mStreamVolumeAlias[sourceStreamType];
+        return sStreamVolumeAlias.get(sourceStreamType, /*valueIfKeyNotFound=*/-1);
     }
 
     /**
@@ -5547,7 +5633,7 @@
      */
     public int getUiSoundsStreamType() {
         return mUseVolumeGroupAliases ? STREAM_VOLUME_ALIAS_VOICE[AudioSystem.STREAM_SYSTEM]
-                : mStreamVolumeAlias[AudioSystem.STREAM_SYSTEM];
+                : sStreamVolumeAlias.get(AudioSystem.STREAM_SYSTEM);
     }
 
     /**
@@ -5559,7 +5645,7 @@
         return mUseVolumeGroupAliases
                 ? STREAM_VOLUME_ALIAS_VOICE[aliasStreamType]
                         == STREAM_VOLUME_ALIAS_VOICE[AudioSystem.STREAM_SYSTEM]
-                : aliasStreamType == mStreamVolumeAlias[AudioSystem.STREAM_SYSTEM];
+                : aliasStreamType == sStreamVolumeAlias.get(AudioSystem.STREAM_SYSTEM);
     }
 
     /** @see AudioManager#setMicrophoneMute(boolean) */
@@ -5853,6 +5939,10 @@
                 forceUse, eventSource, 0);
 
         for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+            final VolumeStreamState vss = getVssForStream(streamType);
+            if (vss == null) {
+                continue;
+            }
             final boolean isMuted = isStreamMutedByRingerOrZenMode(streamType);
             final boolean muteAllowedBySco =
                     !((shouldRingSco || shouldRingBle) && streamType == AudioSystem.STREAM_RING);
@@ -5863,10 +5953,9 @@
             if (!shouldMute) {
                 // unmute
                 // ring and notifications volume should never be 0 when not silenced
-                if (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING
-                        || mStreamVolumeAlias[streamType] == AudioSystem.STREAM_NOTIFICATION) {
+                if (sStreamVolumeAlias.get(streamType) == AudioSystem.STREAM_RING
+                        || sStreamVolumeAlias.get(streamType) == AudioSystem.STREAM_NOTIFICATION) {
                     synchronized (VolumeStreamState.class) {
-                        final VolumeStreamState vss = mStreamStates[streamType];
                         for (int i = 0; i < vss.mIndexMap.size(); i++) {
                             int device = vss.mIndexMap.keyAt(i);
                             int value = vss.mIndexMap.valueAt(i);
@@ -5881,20 +5970,20 @@
                               SENDMSG_QUEUE,
                               device,
                               0,
-                              mStreamStates[streamType],
+                              vss,
                               PERSIST_DELAY);
                     }
                 }
                 sRingerAndZenModeMutedStreams &= ~(1 << streamType);
                 sMuteLogger.enqueue(new AudioServiceEvents.RingerZenMutedStreamsEvent(
                         sRingerAndZenModeMutedStreams, "muteRingerModeStreams"));
-                mStreamStates[streamType].mute(false, "muteRingerModeStreams");
+                vss.mute(false, "muteRingerModeStreams");
             } else {
                 // mute
                 sRingerAndZenModeMutedStreams |= (1 << streamType);
                 sMuteLogger.enqueue(new AudioServiceEvents.RingerZenMutedStreamsEvent(
                         sRingerAndZenModeMutedStreams, "muteRingerModeStreams"));
-                mStreamStates[streamType].mute(true, "muteRingerModeStreams");
+                vss.mute(true, "muteRingerModeStreams");
             }
         }
     }
@@ -6317,15 +6406,15 @@
 
                 final int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
                 final int device = getDeviceForStream(streamType);
-                final int streamAlias = mStreamVolumeAlias[streamType];
+                final int streamAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/
+                        -1);
 
                 if (DEBUG_MODE) {
                     Log.v(TAG, "onUpdateAudioMode: streamType=" + streamType
                             + ", streamAlias=" + streamAlias);
                 }
 
-                final int index = mStreamStates[streamAlias].getIndex(device);
-                final int maxIndex = mStreamStates[streamAlias].getMaxIndex();
+                final int index = getVssForStreamOrDefault(streamAlias).getIndex(device);
                 setStreamVolumeInt(streamAlias, index, device, true,
                         requesterPackage, true /*hasModifyAudioSettings*/);
 
@@ -6630,13 +6719,13 @@
         // restore volume settings
         int numStreamTypes = AudioSystem.getNumStreamTypes();
         for (int streamType = 0; streamType < numStreamTypes; streamType++) {
-            VolumeStreamState streamState = mStreamStates[streamType];
+            final VolumeStreamState streamState = getVssForStream(streamType);
 
             if (streamState == null) {
                 continue;
             }
 
-            if (userSwitch && mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) {
+            if (userSwitch && sStreamVolumeAlias.get(streamType) == AudioSystem.STREAM_MUSIC) {
                 continue;
             }
 
@@ -7204,7 +7293,7 @@
         } else {
             ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
         }
-        if (mStreamVolumeAlias[AudioSystem.STREAM_DTMF] == AudioSystem.STREAM_RING) {
+        if (sStreamVolumeAlias.get(AudioSystem.STREAM_DTMF) == AudioSystem.STREAM_RING) {
             ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF);
         } else {
             ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF);
@@ -7260,7 +7349,7 @@
     }
 
     private void ensureValidStreamType(int streamType) {
-        if (streamType < 0 || streamType >= mStreamStates.length) {
+        if (streamType < 0 || streamType >= AudioSystem.getNumStreamTypes()) {
             throw new IllegalArgumentException("Bad stream type " + streamType);
         }
     }
@@ -7525,9 +7614,10 @@
                 ? MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM]
                 : Math.min(idx + 1, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
         // update the VolumeStreamState for STREAM_ALARM and its aliases
-        for (int stream : mStreamVolumeAlias) {
-            if (stream >= 0 && mStreamVolumeAlias[stream] == AudioSystem.STREAM_ALARM) {
-                mStreamStates[stream].updateNoPermMinIndex(safeIndex);
+        for (int stream = 0; stream < sStreamVolumeAlias.size(); ++stream) {
+            final int streamAlias = sStreamVolumeAlias.valueAt(stream);
+            if (streamAlias == AudioSystem.STREAM_ALARM) {
+                getVssForStreamOrDefault(streamAlias).updateNoPermMinIndex(safeIndex);
             }
         }
     }
@@ -7642,21 +7732,21 @@
         stream = replaceBtScoStreamWithVoiceCall(stream, "getDeviceSetForStream");
         ensureValidStreamType(stream);
         synchronized (VolumeStreamState.class) {
-            return mStreamStates[stream].observeDevicesForStream_syncVSS(true);
+            return getVssForStreamOrDefault(stream).observeDevicesForStream_syncVSS(true);
         }
     }
 
     private void onObserveDevicesForAllStreams(int skipStream) {
         synchronized (mSettingsLock) {
             synchronized (VolumeStreamState.class) {
-                for (int stream = 0; stream < mStreamStates.length; stream++) {
-                    if (stream != skipStream && mStreamStates[stream] != null) {
+                for (int stream = 0; stream < mStreamStates.size(); stream++) {
+                    final VolumeStreamState vss = mStreamStates.valueAt(stream);
+                    if (vss != null && vss.getStreamType() != skipStream) {
                         Set<Integer> deviceSet =
-                                mStreamStates[stream].observeDevicesForStream_syncVSS(
-                                        false /*checkOthers*/);
+                                vss.observeDevicesForStream_syncVSS(false /*checkOthers*/);
                         for (Integer device : deviceSet) {
                             // Update volume states for devices routed for the stream
-                            updateVolumeStates(device, stream,
+                            updateVolumeStates(device, vss.getStreamType(),
                                     "AudioService#onObserveDevicesForAllStreams");
                         }
                     }
@@ -7689,7 +7779,7 @@
 
     private void onUpdateScoDeviceActive(boolean scoDeviceActive) {
         if (mScoDeviceActive.compareAndSet(!scoDeviceActive, scoDeviceActive)) {
-            getVssVolumeForStream(AudioSystem.STREAM_VOICE_CALL).updateIndexFactors();
+            getVssForStreamOrDefault(AudioSystem.STREAM_VOICE_CALL).updateIndexFactors();
         }
     }
 
@@ -8084,7 +8174,7 @@
     /** only public for mocking/spying, do not call outside of AudioService */
     @VisibleForTesting
     public void setMusicMute(boolean mute) {
-        mStreamStates[AudioSystem.STREAM_MUSIC].muteInternally(mute);
+        getVssForStreamOrDefault(AudioSystem.STREAM_MUSIC).muteInternally(mute);
     }
 
     private static final Set<Integer> DEVICE_MEDIA_UNMUTED_ON_PLUG_SET;
@@ -8115,8 +8205,8 @@
         if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
                 && !isStreamMutedByRingerOrZenMode(AudioSystem.STREAM_MUSIC)
                 && DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.contains(newDevice)
-                && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
-                && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
+                && getVssForStreamOrDefault(AudioSystem.STREAM_MUSIC).mIsMuted
+                && getVssForStreamOrDefault(AudioSystem.STREAM_MUSIC).getIndex(newDevice) != 0
                 && getDeviceSetForStreamDirect(AudioSystem.STREAM_MUSIC).contains(newDevice)) {
             if (DEBUG_VOL) {
                 Log.i(TAG, String.format("onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
@@ -8125,7 +8215,8 @@
             // Locking mSettingsLock to avoid inversion when calling vss.mute -> vss.doMute ->
             // vss.updateVolumeGroupIndex
             synchronized (mSettingsLock) {
-                mStreamStates[AudioSystem.STREAM_MUSIC].mute(false, "onAccessoryPlugMediaUnmute");
+                getVssForStreamOrDefault(AudioSystem.STREAM_MUSIC).mute(false,
+                        "onAccessoryPlugMediaUnmute");
             }
         }
     }
@@ -8296,8 +8387,8 @@
                 }
 
                 if (replaceStreamBtSco()) {
-                    mIndexMin = mStreamStates[mPublicStreamType].getMinIndex() / 10;
-                    mIndexMax = mStreamStates[mPublicStreamType].getMaxIndex() / 10;
+                    mIndexMin = getVssForStreamOrDefault(mPublicStreamType).getMinIndex() / 10;
+                    mIndexMax = getVssForStreamOrDefault(mPublicStreamType).getMaxIndex() / 10;
                 } else {
                     mIndexMin = MIN_STREAM_VOLUME[mPublicStreamType];
                     mIndexMax = MAX_STREAM_VOLUME[mPublicStreamType];
@@ -8334,7 +8425,7 @@
          */
         private boolean isVssMuteBijective(int stream) {
             return isStreamAffectedByMute(stream)
-                    && (getMinIndex() == (mStreamStates[stream].getMinIndex() + 5) / 10)
+                    && (getMinIndex() == (getVssForStreamOrDefault(stream).getMinIndex() + 5) / 10)
                     && (getMinIndex() == 0 || isCallStream(stream));
         }
 
@@ -8380,7 +8471,8 @@
                         return;
                     }
 
-                    float stepFactor = mStreamStates[mPublicStreamType].getIndexStepFactor();
+                    float stepFactor = getVssForStreamOrDefault(
+                            mPublicStreamType).getIndexStepFactor();
                     switch (direction) {
                         case AudioManager.ADJUST_TOGGLE_MUTE: {
                             // Note: If muted by volume 0, unmute will restore volume 0.
@@ -8476,7 +8568,7 @@
             // This allows RX path muting by the audio HAL only when explicitly muted but not when
             // index is just set to 0 to repect BT requirements
             if (mHasValidStreamType && isVssMuteBijective(mPublicStreamType)
-                    && mStreamStates[mPublicStreamType].isFullyMuted()) {
+                    && getVssForStreamOrDefault(mPublicStreamType).isFullyMuted()) {
                 index = 0;
             } else if (isStreamBluetoothSco(mPublicStreamType) && index == 0) {
                 index = 1;
@@ -8484,7 +8576,7 @@
 
             if (replaceStreamBtSco()) {
                 index = (int) (mIndexMin + (index - mIndexMin)
-                        / mStreamStates[mPublicStreamType].getIndexStepFactor());
+                        / getVssForStreamOrDefault(mPublicStreamType).getIndexStepFactor());
             }
 
             if (DEBUG_VOL) {
@@ -8517,7 +8609,7 @@
         }
 
         private boolean isValidStream(int stream) {
-            return (stream != AudioSystem.STREAM_DEFAULT) && (stream < mStreamStates.length);
+            return (stream != AudioSystem.STREAM_DEFAULT) && getVssForStream(stream) != null;
         }
 
         public boolean isMusic() {
@@ -8535,10 +8627,10 @@
                     if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
                         for (int stream : getLegacyStreamTypes()) {
                             if (isValidStream(stream)) {
-                                boolean streamMuted = mStreamStates[stream].mIsMuted;
+                                final VolumeStreamState vss = getVssForStreamOrDefault(stream);
+                                boolean streamMuted = vss.mIsMuted;
                                 int deviceForStream = getDeviceForStream(stream);
-                                int indexForStream =
-                                        (mStreamStates[stream].getIndex(deviceForStream) + 5) / 10;
+                                int indexForStream = (vss.getIndex(deviceForStream) + 5) / 10;
                                 if (device == deviceForStream) {
                                     if (indexForStream == index && (isMuted() == streamMuted)
                                             && isVssMuteBijective(stream)) {
@@ -8548,19 +8640,17 @@
                                     if (vgsVssSyncMuteOrder()) {
                                         if ((isMuted() != streamMuted) && isVssMuteBijective(
                                                 stream)) {
-                                            mStreamStates[stream].mute(isMuted(),
-                                                    "VGS.applyAllVolumes#1");
+                                            vss.mute(isMuted(), "VGS.applyAllVolumes#1");
                                         }
                                     }
                                     if (indexForStream != index) {
-                                        mStreamStates[stream].setIndex(index * 10, device, caller,
-                                                true /*hasModifyAudioSettings*/);
+                                        vss.setIndex(index * 10, device,
+                                                caller, true /*hasModifyAudioSettings*/);
                                     }
                                     if (!vgsVssSyncMuteOrder()) {
                                         if ((isMuted() != streamMuted) && isVssMuteBijective(
                                                 stream)) {
-                                            mStreamStates[stream].mute(isMuted(),
-                                                    "VGS.applyAllVolumes#1");
+                                            vss.mute(isMuted(), "VGS.applyAllVolumes#1");
                                         }
                                     }
                                 }
@@ -8584,11 +8674,12 @@
                 boolean forceDeviceSync = userSwitch && (mIndexMap.indexOfKey(deviceForVolume) < 0);
                 for (int stream : getLegacyStreamTypes()) {
                     if (isValidStream(stream)) {
-                        boolean streamMuted = mStreamStates[stream].mIsMuted;
-                        int defaultStreamIndex = (mStreamStates[stream].getIndex(
-                                        AudioSystem.DEVICE_OUT_DEFAULT) + 5) / 10;
+                        final VolumeStreamState vss = getVssForStreamOrDefault(stream);
+                        boolean streamMuted = vss.mIsMuted;
+                        int defaultStreamIndex = (vss.getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)
+                                / 10;
                         if (forceDeviceSync) {
-                            mStreamStates[stream].setIndex(index * 10, deviceForVolume, caller,
+                            vss.setIndex(index * 10, deviceForVolume, caller,
                                     true /*hasModifyAudioSettings*/);
                         }
                         if (defaultStreamIndex == index && (isMuted() == streamMuted)
@@ -8597,12 +8688,11 @@
                             continue;
                         }
                         if (defaultStreamIndex != index) {
-                            mStreamStates[stream].setIndex(
-                                    index * 10, AudioSystem.DEVICE_OUT_DEFAULT, caller,
+                            vss.setIndex(index * 10, AudioSystem.DEVICE_OUT_DEFAULT, caller,
                                     true /*hasModifyAudioSettings*/);
                         }
                         if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) {
-                            mStreamStates[stream].mute(isMuted(), "VGS.applyAllVolumes#2");
+                            vss.mute(isMuted(), "VGS.applyAllVolumes#2");
                         }
                     }
                 }
@@ -8950,7 +9040,7 @@
                 postObserveDevicesForAllStreams(mStreamType);
             }
             // log base stream changes to the event log
-            if (mStreamVolumeAlias[mStreamType] == mStreamType) {
+            if (sStreamVolumeAlias.get(mStreamType, /*valueIfKeyNotFound=*/-1) == mStreamType) {
                 EventLogTags.writeStreamDevicesChanged(mStreamType, prevDevices, devices);
             }
             // send STREAM_DEVICES_CHANGED_ACTION on the message handler so it is scheduled after
@@ -9202,10 +9292,11 @@
                     isCurrentDevice = (device == getDeviceForStream(mStreamType));
                     final int numStreamTypes = AudioSystem.getNumStreamTypes();
                     for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
-                        final VolumeStreamState aliasStreamState = mStreamStates[streamType];
-                        if (streamType != mStreamType &&
-                                mStreamVolumeAlias[streamType] == mStreamType &&
-                                (changed || !aliasStreamState.hasIndexForDevice(device))) {
+                        final VolumeStreamState aliasStreamState = getVssForStream(streamType);
+                        if (aliasStreamState != null && streamType != mStreamType
+                                && sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound*/-1)
+                                == mStreamType && (changed || !aliasStreamState.hasIndexForDevice(
+                                device))) {
                             final int scaledIndex =
                                     rescaleIndex(aliasIndex, mStreamType, streamType);
                             boolean changedAlias = aliasStreamState.setIndex(scaledIndex, device,
@@ -9240,7 +9331,7 @@
                 oldIndex = (oldIndex + 5) / 10;
                 index = (index + 5) / 10;
                 // log base stream changes to the event log
-                if (mStreamVolumeAlias[mStreamType] == mStreamType) {
+                if (sStreamVolumeAlias.get(mStreamType, /*valueIfKeyNotFound=*/-1) == mStreamType) {
                     if (caller == null) {
                         Log.w(TAG, "No caller for volume_changed event", new Throwable());
                     }
@@ -9252,7 +9343,9 @@
                 if ((index != oldIndex) && isCurrentDevice) {
                     // for single volume devices, only send the volume change broadcast
                     // on the alias stream
-                    if (!mIsSingleVolume || (mStreamVolumeAlias[mStreamType] == mStreamType)) {
+                    final int streamAlias = sStreamVolumeAlias.get(
+                            mStreamType, /*valueIfKeyNotFound=*/-1);
+                    if (!mIsSingleVolume || streamAlias == mStreamType) {
                         mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
                         mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE,
                                 oldIndex);
@@ -9267,9 +9360,9 @@
                                     mStreamType);
                         }
                         mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
-                                mStreamVolumeAlias[mStreamType]);
+                                streamAlias);
 
-                        if (mStreamType == mStreamVolumeAlias[mStreamType]) {
+                        if (mStreamType == streamAlias) {
                             String aliasStreamIndexesString = "";
                             if (!aliasStreamIndexes.isEmpty()) {
                                 aliasStreamIndexesString =
@@ -9527,7 +9620,7 @@
         public void checkFixedVolumeDevices() {
             synchronized (VolumeStreamState.class) {
                 // ignore settings for fixed volume devices: volume should always be at max or 0
-                if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) {
+                if (sStreamVolumeAlias.get(mStreamType) == AudioSystem.STREAM_MUSIC) {
                     for (int i = 0; i < mIndexMap.size(); i++) {
                         int device = mIndexMap.keyAt(i);
                         int index = mIndexMap.valueAt(i);
@@ -9673,7 +9766,11 @@
     }
 
     private void onSetVolumeIndexOnDevice(@NonNull DeviceVolumeUpdate update) {
-        final VolumeStreamState streamState = mStreamStates[update.mStreamType];
+        final VolumeStreamState streamState = getVssForStream(update.mStreamType);
+        if (streamState == null) {
+            Log.w(TAG, "Invalid onSetVolumeIndexOnDevice for stream type " + update.mStreamType);
+            return;
+        }
         if (update.hasVolumeIndex()) {
             int index = update.getVolumeIndex();
             if (mSoundDoseHelper.checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) {
@@ -9704,8 +9801,10 @@
             // Apply change to all streams using this one as alias
             int numStreamTypes = AudioSystem.getNumStreamTypes();
             for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
-                if (streamType != streamState.mStreamType &&
-                        mStreamVolumeAlias[streamType] == streamState.mStreamType) {
+                final VolumeStreamState vss = getVssForStream(streamType);
+                if (vss != null && streamType != streamState.mStreamType
+                        && sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1)
+                                == streamState.mStreamType) {
                     // Make sure volume is also maxed out on A2DP device for aliased stream
                     // that may have a different device selected
                     int streamDevice = getDeviceForStream(streamType);
@@ -9713,9 +9812,9 @@
                             && (isAbsoluteVolumeDevice(device)
                                 || isA2dpAbsoluteVolumeDevice(device)
                                 || AudioSystem.isLeAudioDeviceType(device))) {
-                        mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
+                        vss.applyDeviceVolume_syncVSS(device);
                     }
-                    mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
+                    vss.applyDeviceVolume_syncVSS(streamDevice);
                 }
             }
         }
@@ -9749,9 +9848,11 @@
             // Apply change to all streams using this one as alias
             int numStreamTypes = AudioSystem.getNumStreamTypes();
             for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
-                if (streamType != streamState.mStreamType &&
-                        mStreamVolumeAlias[streamType] == streamState.mStreamType) {
-                    mStreamStates[streamType].applyAllVolumes();
+                final VolumeStreamState vss = getVssForStream(streamType);
+                if (vss != null && streamType != streamState.mStreamType
+                        && sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1)
+                                == streamState.mStreamType) {
+                    vss.applyAllVolumes();
                 }
             }
         }
@@ -10201,7 +10302,7 @@
         }
         sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
                     AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
-                    mStreamStates[AudioSystem.STREAM_MUSIC], 0);
+                    getVssForStreamOrDefault(AudioSystem.STREAM_MUSIC), 0);
     }
 
     /**
@@ -10321,7 +10422,7 @@
                         SENDMSG_QUEUE,
                         0,
                         0,
-                        mStreamStates[AudioSystem.STREAM_MUSIC], 0);
+                        getVssForStreamOrDefault(AudioSystem.STREAM_MUSIC), 0);
             } else if (action.equals(Intent.ACTION_USER_BACKGROUND)) {
                 // Disable audio recording for the background user/profile
                 int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
@@ -11488,13 +11589,15 @@
                 if (cameraSoundForcedChanged) {
                     if (!mIsSingleVolume) {
                         synchronized (VolumeStreamState.class) {
-                            VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED];
+                            final VolumeStreamState s = getVssForStreamOrDefault(
+                                    AudioSystem.STREAM_SYSTEM_ENFORCED);
                             if (cameraSoundForced) {
                                 s.setAllIndexesToMax();
                                 mRingerModeAffectedStreams &=
                                         ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
                             } else {
-                                s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM], TAG);
+                                s.setAllIndexes(getVssForStreamOrDefault(AudioSystem.STREAM_SYSTEM),
+                                        TAG);
                                 mRingerModeAffectedStreams |=
                                         (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
                             }
@@ -11511,7 +11614,7 @@
                             SENDMSG_QUEUE,
                             0,
                             0,
-                            mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED], 0);
+                            getVssForStreamOrDefault(AudioSystem.STREAM_SYSTEM_ENFORCED), 0);
 
                 }
             }
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index ded93e6..dc79ab2 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -633,7 +633,7 @@
     }
 
     /*package*/ void enforceSafeMediaVolume(String caller) {
-        AudioService.VolumeStreamState streamState = mAudioService.getVssVolumeForStream(
+        AudioService.VolumeStreamState streamState = mAudioService.getVssForStreamOrDefault(
                 AudioSystem.STREAM_MUSIC);
 
         for (int i = 0; i < mSafeMediaVolumeDevices.size(); ++i)  {
@@ -665,7 +665,7 @@
     @GuardedBy("mSafeMediaVolumeStateLock")
     private boolean checkSafeMediaVolume_l(int streamType, int index, int device) {
         return (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)
-                    && (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC)
+                    && (AudioService.sStreamVolumeAlias.get(streamType) == AudioSystem.STREAM_MUSIC)
                     && safeDevicesContains(device)
                     && (index > safeMediaVolumeIndex(device));
     }
@@ -908,7 +908,7 @@
                 return;
             }
 
-            if (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC
+            if (AudioService.sStreamVolumeAlias.get(streamType) == AudioSystem.STREAM_MUSIC
                     && safeDevicesContains(device)) {
                 float attenuationDb = -AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
                         (newIndex + 5) / 10, device);
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index 222c5a8..dc611fc 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -332,6 +332,16 @@
         }
 
         /**
+         * Sets the {@link BrightnessReason} using the int-based reason enum. This is a convenience
+         * function so we don't have to type out the constructor syntax everywhere.
+         *
+         * @param brightnessReason The int-based brightness enum.
+         */
+        public Builder setBrightnessReason(int brightnessReason) {
+            return setBrightnessReason(new BrightnessReason(brightnessReason));
+        }
+
+        /**
          * Gets the {@link com.android.server.display.brightness.strategy.DisplayBrightnessStrategy}
          * name
          */
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
index 9bf10a7..9a0ee03 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display.brightness;
 
+import android.annotation.Nullable;
 import android.util.Slog;
 
 import java.util.Objects;
@@ -66,6 +67,16 @@
     // Any number of MODIFIER_*
     private int mModifier;
 
+    // Tag used to identify the source of the brightness (usually a specific activity/window).
+    private CharSequence mTag;
+
+    public BrightnessReason() {
+    }
+
+    public BrightnessReason(int reason) {
+        setReason(reason);
+    }
+
     /**
      * A utility to clone a BrightnessReason from another BrightnessReason event
      *
@@ -74,6 +85,7 @@
     public void set(BrightnessReason other) {
         setReason(other == null ? REASON_UNKNOWN : other.mReason);
         setModifier(other == null ? 0 : other.mModifier);
+        setTag(other == null ? null : other.mTag);
     }
 
     /**
@@ -85,19 +97,20 @@
         setModifier(modifier | this.mModifier);
     }
 
-
     @Override
     public boolean equals(Object obj) {
         if (!(obj instanceof BrightnessReason)) {
             return false;
         }
         BrightnessReason other = (BrightnessReason) obj;
-        return other.mReason == mReason && other.mModifier == mModifier;
+        return other.mReason == mReason
+                && other.mModifier == mModifier
+                && Objects.equals(other.mTag != null ? other.mTag.toString() : null, mTag);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mReason, mModifier);
+        return Objects.hash(mReason, mModifier, mTag);
     }
 
     @Override
@@ -115,6 +128,11 @@
     public String toString(int adjustments) {
         final StringBuilder sb = new StringBuilder();
         sb.append(reasonToString(mReason));
+
+        if (mTag != null) {
+            sb.append("(").append(mTag).append(")");
+        }
+
         sb.append(" [");
         if ((adjustments & ADJUSTMENT_AUTO_TEMP) != 0) {
             sb.append(" temp_adj");
@@ -149,8 +167,23 @@
         return sb.toString();
     }
 
+    public void setTag(@Nullable CharSequence tag) {
+        mTag = tag;
+    }
+
     /**
-     * A utility to set the reason of the BrightnessReason object
+     * Gets the tag to identify who requested the brightness.
+     */
+    @Nullable public CharSequence getTag() {
+        return mTag;
+    }
+
+    public int getReason() {
+        return mReason;
+    }
+
+    /**
+     * Sets the reason of the BrightnessReason object
      *
      * @param reason The value to which the reason is to be updated.
      */
@@ -162,16 +195,12 @@
         }
     }
 
-    public int getReason() {
-        return mReason;
-    }
-
     public int getModifier() {
         return mModifier;
     }
 
     /**
-     * A utility to set the modified of the current BrightnessReason object
+     * Sets the modifier bitflags of the current BrightnessReason object
      *
      * @param modifier The value to which the modifier is to be updated
      */
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
index 40a495c..3fc15d1 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
@@ -16,9 +16,10 @@
 
 package com.android.server.display.brightness.strategy;
 
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
-import com.android.server.display.brightness.BrightnessUtils;
 import com.android.server.display.brightness.StrategyExecutionRequest;
 import com.android.server.display.brightness.StrategySelectionNotifyRequest;
 
@@ -33,9 +34,14 @@
             StrategyExecutionRequest strategyExecutionRequest) {
         // Todo(b/241308599): Introduce a validator class and add validations before setting
         // the brightness
-        return BrightnessUtils.constructDisplayBrightnessState(BrightnessReason.REASON_OVERRIDE,
-                strategyExecutionRequest.getDisplayPowerRequest().screenBrightnessOverride,
-                getName());
+        DisplayPowerRequest dpr = strategyExecutionRequest.getDisplayPowerRequest();
+        BrightnessReason reason = new BrightnessReason(BrightnessReason.REASON_OVERRIDE);
+        reason.setTag(dpr.screenBrightnessOverrideTag);
+        return new DisplayBrightnessState.Builder()
+                .setBrightness(dpr.screenBrightnessOverride)
+                .setBrightnessReason(reason)
+                .setDisplayBrightnessStrategyName(getName())
+                .build();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index bb2efa1..36a9c80 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1355,8 +1355,7 @@
             int patternRepeatIndex = -1;
             int amplitudeCount = -1;
 
-            if (effect instanceof VibrationEffect.Composed) {
-                VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+            if (effect instanceof VibrationEffect.Composed composed) {
                 int segmentCount = composed.getSegments().size();
                 pattern = new long[segmentCount];
                 amplitudes = new int[segmentCount];
@@ -1381,6 +1380,8 @@
                     }
                     pattern[amplitudeCount++] = segment.getDuration();
                 }
+            } else {
+                Slog.w(TAG, "Input devices don't support effect " + effect);
             }
 
             if (amplitudeCount < 0) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 98c718e..64b6dbe 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -170,6 +170,7 @@
 import com.android.internal.inputmethod.UnbindReason;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.AccessibilityManagerInternal;
@@ -677,12 +678,10 @@
                         DirectBootAwareness.AUTO);
                 InputMethodSettingsRepository.put(userId, settings);
 
-                if (mConcurrentMultiUserModeEnabled || userId == mCurrentUserId) {
-                    postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */, userId);
-                    // If the locale is changed, needs to reset the default ime
-                    resetDefaultImeLocked(mContext, userId);
-                    updateFromSettingsLocked(true, userId);
-                }
+                postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */, userId);
+                // If the locale is changed, needs to reset the default ime
+                resetDefaultImeLocked(mContext, userId);
+                updateFromSettingsLocked(true, userId);
             }
         }
     }
@@ -784,14 +783,7 @@
                     int change = isPackageDisappearing(imi.getPackageName());
                     if (change == PACKAGE_PERMANENT_CHANGE) {
                         Slog.i(TAG, "Input method uninstalled, disabling: " + imi.getComponent());
-                        if (isCurrentUser) {
-                            setInputMethodEnabledLocked(imi.getId(), false, userId);
-                        } else {
-                            settings.buildAndPutEnabledInputMethodsStrRemovingId(
-                                    new StringBuilder(),
-                                    settings.getEnabledInputMethodsAndSubtypeList(),
-                                    imi.getId());
-                        }
+                        setInputMethodEnabledLocked(imi.getId(), false, userId);
                     } else if (change == PACKAGE_UPDATING) {
                         Slog.i(TAG, "Input method reinstalling, clearing additional subtypes: "
                                 + imi.getComponent());
@@ -5699,24 +5691,12 @@
     @GuardedBy("ImfLock.class")
     private boolean switchToInputMethodLocked(@NonNull String imeId, int subtypeId,
             @UserIdInt int userId) {
-        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-        if (mConcurrentMultiUserModeEnabled || userId == mCurrentUserId) {
-            if (!settings.getMethodMap().containsKey(imeId)
-                    || !settings.getEnabledInputMethodList()
-                    .contains(settings.getMethodMap().get(imeId))) {
-                return false; // IME is not found or not enabled.
-            }
-            setInputMethodLocked(imeId, subtypeId, userId);
-            return true;
-        }
-        if (!settings.getMethodMap().containsKey(imeId)
-                || !settings.getEnabledInputMethodList().contains(
-                settings.getMethodMap().get(imeId))) {
+        final var settings = InputMethodSettingsRepository.get(userId);
+        final var enabledImes = settings.getEnabledInputMethodList();
+        if (!CollectionUtils.any(enabledImes, imi -> imi.getId().equals(imeId))) {
             return false; // IME is not found or not enabled.
         }
-        settings.putSelectedInputMethod(imeId);
-        // For non-current user, only reset subtypeId (instead of setting the given one).
-        settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+        setInputMethodLocked(imeId, subtypeId, userId);
         return true;
     }
 
@@ -6706,36 +6686,8 @@
     private boolean handleShellCommandEnableDisableInputMethodInternalLocked(
             @UserIdInt int userId, String imeId, boolean enabled, PrintWriter out,
             PrintWriter error) {
-        boolean failedToEnableUnknownIme = false;
-        boolean previouslyEnabled = false;
         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-        if (userId == mCurrentUserId) {
-            if (enabled && !settings.getMethodMap().containsKey(imeId)) {
-                failedToEnableUnknownIme = true;
-            } else {
-                previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled, userId);
-            }
-        } else {
-            if (enabled) {
-                if (!settings.getMethodMap().containsKey(imeId)) {
-                    failedToEnableUnknownIme = true;
-                } else {
-                    final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
-                    final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
-                            enabledImeIdsStr, imeId);
-                    previouslyEnabled = TextUtils.equals(enabledImeIdsStr, newEnabledImeIdsStr);
-                    if (!previouslyEnabled) {
-                        settings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
-                    }
-                }
-            } else {
-                previouslyEnabled =
-                        settings.buildAndPutEnabledInputMethodsStrRemovingId(
-                                new StringBuilder(),
-                                settings.getEnabledInputMethodsAndSubtypeList(), imeId);
-            }
-        }
-        if (failedToEnableUnknownIme) {
+        if (enabled && !settings.getMethodMap().containsKey(imeId)) {
             error.print("Unknown input method ");
             error.print(imeId);
             error.println(" cannot be enabled for user #" + userId);
@@ -6744,6 +6696,8 @@
                     + " failed due to its unrecognized IME ID.");
             return false;
         }
+
+        final boolean previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled, userId);
         out.print("Input method ");
         out.print(imeId);
         out.print(": ");
@@ -6824,67 +6778,47 @@
                     final String nextIme;
                     final List<InputMethodInfo> nextEnabledImes;
                     final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-                    if (userId == mCurrentUserId) {
-                        final var userData = getUserData(userId);
-                        if (Flags.refactorInsetsController()) {
-                            if (userData.mImeBindingState != null
-                                    && userData.mImeBindingState.mFocusedWindowClient != null
-                                    && userData.mImeBindingState.mFocusedWindowClient.mClient
-                                    != null) {
-                                userData.mImeBindingState.mFocusedWindowClient.mClient
-                                        .setImeVisibility(false,
-                                        null /* TODO(b329229469) initialize statsToken here? */);
-                            } else {
-                                // TODO(b329229469): ImeTracker?
-                            }
+                    final var userData = getUserData(userId);
+                    if (Flags.refactorInsetsController()) {
+                        if (userData.mImeBindingState != null
+                                && userData.mImeBindingState.mFocusedWindowClient != null
+                                && userData.mImeBindingState.mFocusedWindowClient.mClient
+                                != null) {
+                            userData.mImeBindingState.mFocusedWindowClient.mClient
+                                    .setImeVisibility(false,
+                                    null /* TODO(b329229469) initialize statsToken here? */);
                         } else {
-                            hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
-                                    0 /* flags */,
-                                    SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND, userId);
+                            // TODO(b329229469): ImeTracker?
                         }
-                        final var bindingController = userData.mBindingController;
-                        bindingController.unbindCurrentMethod();
-
-                        // Enable default IMEs, disable others
-                        var toDisable = settings.getEnabledInputMethodList();
-                        var defaultEnabled = InputMethodInfoUtils.getDefaultEnabledImes(
-                                mContext, settings.getMethodList());
-                        toDisable.removeAll(defaultEnabled);
-                        for (InputMethodInfo info : toDisable) {
-                            setInputMethodEnabledLocked(info.getId(), false, userId);
-                        }
-                        for (InputMethodInfo info : defaultEnabled) {
-                            setInputMethodEnabledLocked(info.getId(), true, userId);
-                        }
-                        // Choose new default IME, reset to none if no IME available.
-                        if (!chooseNewDefaultIMELocked(userId)) {
-                            resetSelectedInputMethodAndSubtypeLocked(null, userId);
-                        }
-                        updateInputMethodsFromSettingsLocked(true /* enabledMayChange */, userId);
-                        InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
-                                getPackageManagerForUser(mContext, settings.getUserId()),
-                                settings.getEnabledInputMethodList());
-                        nextIme = settings.getSelectedInputMethod();
-                        nextEnabledImes = settings.getEnabledInputMethodList();
                     } else {
-                        nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
-                                settings.getMethodList());
-                        nextIme = InputMethodInfoUtils.getMostApplicableDefaultIME(
-                                nextEnabledImes).getId();
-
-                        // Reset enabled IMEs.
-                        final String[] nextEnabledImeIds = new String[nextEnabledImes.size()];
-                        for (int i = 0; i < nextEnabledImeIds.length; ++i) {
-                            nextEnabledImeIds[i] = nextEnabledImes.get(i).getId();
-                        }
-                        settings.putEnabledInputMethodsStr(InputMethodUtils.concatEnabledImeIds(
-                                "", nextEnabledImeIds));
-
-                        // Reset selected IME.
-                        settings.putSelectedInputMethod(nextIme);
-                        settings.putSelectedDefaultDeviceInputMethod(null);
-                        settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+                        hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
+                                0 /* flags */,
+                                SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND, userId);
                     }
+                    final var bindingController = userData.mBindingController;
+                    bindingController.unbindCurrentMethod();
+
+                    // Enable default IMEs, disable others
+                    var toDisable = settings.getEnabledInputMethodList();
+                    var defaultEnabled = InputMethodInfoUtils.getDefaultEnabledImes(
+                            mContext, settings.getMethodList());
+                    toDisable.removeAll(defaultEnabled);
+                    for (InputMethodInfo info : toDisable) {
+                        setInputMethodEnabledLocked(info.getId(), false, userId);
+                    }
+                    for (InputMethodInfo info : defaultEnabled) {
+                        setInputMethodEnabledLocked(info.getId(), true, userId);
+                    }
+                    // Choose new default IME, reset to none if no IME available.
+                    if (!chooseNewDefaultIMELocked(userId)) {
+                        resetSelectedInputMethodAndSubtypeLocked(null, userId);
+                    }
+                    updateInputMethodsFromSettingsLocked(true /* enabledMayChange */, userId);
+                    InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
+                            getPackageManagerForUser(mContext, settings.getUserId()),
+                            settings.getEnabledInputMethodList());
+                    nextIme = settings.getSelectedInputMethod();
+                    nextEnabledImes = settings.getEnabledInputMethodList();
                     out.println("Reset current and enabled IMEs for user #" + userId);
                     out.println("  Selected: " + nextIme);
                     nextEnabledImes.forEach(ime -> out.println("   Enabled: " + ime.getId()));
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index d9f3622..9acf030 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -292,7 +292,7 @@
                         == PackageManager.PERMISSION_GRANTED;
         final boolean hasModifyAudioRoutingPermission =
                 checkCallerHasModifyAudioRoutingPermission(pid, uid);
-
+        boolean hasMediaContentControlPermission = checkMediaContentControlPermission(uid, pid);
         boolean hasMediaRoutingControlPermission =
                 checkMediaRoutingControlPermission(uid, pid, packageName);
 
@@ -307,6 +307,7 @@
                         userId,
                         hasConfigureWifiDisplayPermission,
                         hasModifyAudioRoutingPermission,
+                        hasMediaContentControlPermission,
                         hasMediaRoutingControlPermission);
             }
         } finally {
@@ -1133,6 +1134,7 @@
             int userId,
             boolean hasConfigureWifiDisplayPermission,
             boolean hasModifyAudioRoutingPermission,
+            boolean hasMediaContentControlPermission,
             boolean hasMediaRoutingControlPermission) {
         final IBinder binder = router.asBinder();
         if (mAllRouterRecords.get(binder) != null) {
@@ -1151,6 +1153,7 @@
                         packageName,
                         hasConfigureWifiDisplayPermission,
                         hasModifyAudioRoutingPermission,
+                        hasMediaContentControlPermission,
                         hasMediaRoutingControlPermission);
         try {
             binder.linkToDeath(routerRecord, 0);
@@ -2067,9 +2070,10 @@
         public final int mPid;
         public final boolean mHasConfigureWifiDisplayPermission;
         public final boolean mHasModifyAudioRoutingPermission;
+        public final boolean mHasMediaContentControlPermission;
+        public final boolean mHasMediaRoutingControl;
         public final AtomicBoolean mHasBluetoothRoutingPermission;
         public final int mRouterId;
-        public final boolean mHasMediaRoutingControl;
         public @ScanningState int mScanningState = SCANNING_STATE_NOT_SCANNING;
 
         public RouteDiscoveryPreference mDiscoveryPreference;
@@ -2083,6 +2087,7 @@
                 String packageName,
                 boolean hasConfigureWifiDisplayPermission,
                 boolean hasModifyAudioRoutingPermission,
+                boolean hasMediaContentControlPermission,
                 boolean hasMediaRoutingControl) {
             mUserRecord = userRecord;
             mPackageName = packageName;
@@ -2093,9 +2098,10 @@
             mPid = pid;
             mHasConfigureWifiDisplayPermission = hasConfigureWifiDisplayPermission;
             mHasModifyAudioRoutingPermission = hasModifyAudioRoutingPermission;
+            mHasMediaContentControlPermission = hasMediaContentControlPermission;
+            mHasMediaRoutingControl = hasMediaRoutingControl;
             mHasBluetoothRoutingPermission =
                     new AtomicBoolean(checkCallerHasBluetoothPermissions(mPid, mUid));
-            mHasMediaRoutingControl = hasMediaRoutingControl;
             mRouterId = mNextRouterOrManagerId.getAndIncrement();
         }
 
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 77bdc45..72594b3 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -430,8 +430,8 @@
         return mDisplayPowerRequest.policy;
     }
 
-    boolean updateLocked(float screenBrightnessOverride, boolean useProximitySensor,
-            boolean boostScreenBrightness, int dozeScreenState,
+    boolean updateLocked(float screenBrightnessOverride, CharSequence overrideTag,
+            boolean useProximitySensor, boolean boostScreenBrightness, int dozeScreenState,
             @Display.StateReason int dozeScreenStateReason,
             float dozeScreenBrightness, boolean overrideDrawWakeLock,
             PowerSaveState powerSaverState, boolean quiescent,
@@ -441,6 +441,7 @@
         mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(quiescent, dozeAfterScreenOff,
                 bootCompleted, screenBrightnessBoostInProgress, brightWhenDozing);
         mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
+        mDisplayPowerRequest.screenBrightnessOverrideTag = overrideTag;
         mDisplayPowerRequest.useProximitySensor = useProximitySensor;
         mDisplayPowerRequest.boostScreenBrightness = boostScreenBrightness;
 
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 6fe1ccd..10faf14 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -629,6 +629,9 @@
     private float mScreenBrightnessOverrideFromWindowManager =
             PowerManager.BRIGHTNESS_INVALID_FLOAT;
 
+    // Tag identifying the window/activity that requested the brightness override.
+    private CharSequence mScreenBrightnessOverrideFromWmTag = null;
+
     // The window manager has determined the user to be inactive via other means.
     // Set this to false to disable.
     private boolean mUserInactiveOverrideFromWindowManager;
@@ -3623,16 +3626,18 @@
 
                 // Determine appropriate screen brightness.
                 final float screenBrightnessOverride;
+                CharSequence overrideTag = null;
                 if (!mBootCompleted) {
                     // Keep the brightness steady during boot. This requires the
                     // bootloader brightness and the default brightness to be identical.
                     screenBrightnessOverride = mScreenBrightnessDefault;
                 } else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) {
                     screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager;
+                    overrideTag = mScreenBrightnessOverrideFromWmTag;
                 } else {
                     screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
                 }
-                boolean ready = powerGroup.updateLocked(screenBrightnessOverride,
+                boolean ready = powerGroup.updateLocked(screenBrightnessOverride, overrideTag,
                         shouldUseProximitySensorLocked(), shouldBoostScreenBrightness(),
                         mDozeScreenStateOverrideFromDreamManager,
                         mDozeScreenStateOverrideReasonFromDreamManager,
@@ -4417,11 +4422,13 @@
         }
     }
 
-    private void setScreenBrightnessOverrideFromWindowManagerInternal(float brightness) {
+    private void setScreenBrightnessOverrideFromWindowManagerInternal(
+            float brightness, CharSequence tag) {
         synchronized (mLock) {
             if (!BrightnessSynchronizer.floatEquals(mScreenBrightnessOverrideFromWindowManager,
                     brightness)) {
                 mScreenBrightnessOverrideFromWindowManager = brightness;
+                mScreenBrightnessOverrideFromWmTag = tag;
                 mDirty |= DIRTY_SETTINGS;
                 updatePowerStateLocked();
             }
@@ -4760,6 +4767,8 @@
             pw.println("  mStayOnWhilePluggedInSetting=" + mStayOnWhilePluggedInSetting);
             pw.println("  mScreenBrightnessOverrideFromWindowManager="
                     + mScreenBrightnessOverrideFromWindowManager);
+            pw.println("  mScreenBrightnessOverrideFromWmTag="
+                    + mScreenBrightnessOverrideFromWmTag);
             pw.println("  mUserActivityTimeoutOverrideFromWindowManager="
                     + mUserActivityTimeoutOverrideFromWindowManager);
             pw.println("  mUserInactiveOverrideFromWindowManager="
@@ -7074,12 +7083,14 @@
     @VisibleForTesting
     final class LocalService extends PowerManagerInternal {
         @Override
-        public void setScreenBrightnessOverrideFromWindowManager(float screenBrightness) {
+        public void setScreenBrightnessOverrideFromWindowManager(
+                float screenBrightness, CharSequence tag) {
             if (screenBrightness < PowerManager.BRIGHTNESS_MIN
                     || screenBrightness > PowerManager.BRIGHTNESS_MAX) {
                 screenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+                tag = null;
             }
-            setScreenBrightnessOverrideFromWindowManagerInternal(screenBrightness);
+            setScreenBrightnessOverrideFromWindowManagerInternal(screenBrightness, tag);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/power/stats/GnssPowerCalculator.java b/services/core/java/com/android/server/power/stats/GnssPowerCalculator.java
index ab22e3e..1003a81 100644
--- a/services/core/java/com/android/server/power/stats/GnssPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/GnssPowerCalculator.java
@@ -126,7 +126,7 @@
         long totalTime = 0;
         double totalPower = 0;
         for (int i = 0; i < GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS; i++) {
-            long timePerLevel = stats.getGpsSignalQualityTime(i, rawRealtimeUs, statsType);
+            long timePerLevel = stats.getGpsSignalQualityTime(i, rawRealtimeUs, statsType) / 1000;
             totalTime += timePerLevel;
             totalPower += mAveragePowerPerSignalQuality[i] * timePerLevel;
         }
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
index 0f13492..39954b8 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
@@ -307,7 +307,10 @@
                 }
             }
             if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
-                builder.addConsumedPowerForCustomComponent(powerComponentId, powerAllProcStates);
+                if (batteryUsageStatsBuilder.isSupportedCustomPowerComponent(powerComponentId)) {
+                    builder.addConsumedPowerForCustomComponent(powerComponentId,
+                            powerAllProcStates);
+                }
             } else {
                 builder.addConsumedPower(powerComponentId, powerAllProcStates,
                         BatteryConsumer.POWER_MODEL_UNDEFINED);
@@ -319,7 +322,9 @@
                 batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder(
                         BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
         if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
-            allAppsScope.addConsumedPowerForCustomComponent(powerComponentId, powerAllApps);
+            if (batteryUsageStatsBuilder.isSupportedCustomPowerComponent(powerComponentId)) {
+                allAppsScope.addConsumedPowerForCustomComponent(powerComponentId, powerAllApps);
+            }
         } else {
             BatteryConsumer.Key key = allAppsScope.getKey(powerComponentId,
                     BatteryConsumer.PROCESS_STATE_ANY, screenState, powerState);
diff --git a/services/core/java/com/android/server/vibrator/AbstractComposedVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractComposedVibratorStep.java
new file mode 100644
index 0000000..b263159
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/AbstractComposedVibratorStep.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+
+import java.util.List;
+
+/**
+ * Represent a step on a single vibrator that plays one or more segments from a
+ * {@link VibrationEffect.Composed} effect.
+ */
+abstract class AbstractComposedVibratorStep extends AbstractVibratorStep {
+    public final VibrationEffect.Composed effect;
+    public final int segmentIndex;
+
+    /**
+     * @param conductor          The {@link VibrationStepConductor} for these steps.
+     * @param startTime          The time to schedule this step in the conductor.
+     * @param controller         The vibrator that is playing the effect.
+     * @param effect             The effect being played in this step.
+     * @param index              The index of the next segment to be played by this step
+     * @param pendingVibratorOffDeadline The time the vibrator is expected to complete any
+     *                           previous vibration and turn off. This is used to allow this step to
+     *                           be triggered when the completion callback is received, and can
+     *                           be used to play effects back-to-back.
+     */
+    AbstractComposedVibratorStep(VibrationStepConductor conductor, long startTime,
+            VibratorController controller, VibrationEffect.Composed effect, int index,
+            long pendingVibratorOffDeadline) {
+        super(conductor, startTime, controller, pendingVibratorOffDeadline);
+        this.effect = effect;
+        this.segmentIndex = index;
+    }
+
+    /**
+     * Return the {@link VibrationStepConductor#nextVibrateStep} with start and off timings
+     * calculated from {@link #getVibratorOnDuration()} based on the current
+     * {@link SystemClock#uptimeMillis()} and jumping all played segments from the effect.
+     */
+    protected List<Step> nextSteps(int segmentsPlayed) {
+        // Schedule next steps to run right away.
+        long nextStartTime = SystemClock.uptimeMillis();
+        if (mVibratorOnResult > 0) {
+            // Vibrator was turned on by this step, with mVibratorOnResult as the duration.
+            // Schedule next steps for right after the vibration finishes.
+            nextStartTime += mVibratorOnResult;
+        }
+        return nextSteps(nextStartTime, segmentsPlayed);
+    }
+
+    /**
+     * Return the {@link VibrationStepConductor#nextVibrateStep} with given start time,
+     * which might be calculated independently, and jumping all played segments from the effect.
+     *
+     * <p>This should be used when the vibrator on/off state is not responsible for the step
+     * execution timing, e.g. while playing the vibrator amplitudes.
+     */
+    protected List<Step> nextSteps(long nextStartTime, int segmentsPlayed) {
+        int nextSegmentIndex = segmentIndex + segmentsPlayed;
+        int effectSize = effect.getSegments().size();
+        int repeatIndex = effect.getRepeatIndex();
+        if (nextSegmentIndex >= effectSize && repeatIndex >= 0) {
+            // Count the loops that were played.
+            int loopSize = effectSize - repeatIndex;
+            int loopSegmentsPlayed = nextSegmentIndex - repeatIndex;
+            getVibration().stats.reportRepetition(loopSegmentsPlayed / loopSize);
+            nextSegmentIndex = repeatIndex + ((nextSegmentIndex - effectSize) % loopSize);
+        }
+        Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
+                nextSegmentIndex, mPendingVibratorOffDeadline);
+        return List.of(nextStep);
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
index 90b6f95..42203b1 100644
--- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -16,21 +16,16 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.os.SystemClock;
-import android.os.VibrationEffect;
 import android.util.Slog;
 
 import java.util.Arrays;
 import java.util.List;
 
-/**
- * Represent a step on a single vibrator that plays one or more segments from a
- * {@link VibrationEffect.Composed} effect.
- */
+/** Represent a step on a single vibrator that plays a command on {@link VibratorController}. */
 abstract class AbstractVibratorStep extends Step {
     public final VibratorController controller;
-    public final VibrationEffect.Composed effect;
-    public final int segmentIndex;
 
     long mVibratorOnResult;
     long mPendingVibratorOffDeadline;
@@ -41,20 +36,15 @@
      * @param startTime          The time to schedule this step in the
      *                           {@link VibrationStepConductor}.
      * @param controller         The vibrator that is playing the effect.
-     * @param effect             The effect being played in this step.
-     * @param index              The index of the next segment to be played by this step
      * @param pendingVibratorOffDeadline The time the vibrator is expected to complete any
      *                           previous vibration and turn off. This is used to allow this step to
      *                           be triggered when the completion callback is received, and can
      *                           be used to play effects back-to-back.
      */
     AbstractVibratorStep(VibrationStepConductor conductor, long startTime,
-            VibratorController controller, VibrationEffect.Composed effect, int index,
-            long pendingVibratorOffDeadline) {
+            VibratorController controller, long pendingVibratorOffDeadline) {
         super(conductor, startTime);
         this.controller = controller;
-        this.effect = effect;
-        this.segmentIndex = index;
         mPendingVibratorOffDeadline = pendingVibratorOffDeadline;
     }
 
@@ -88,6 +78,7 @@
         return shouldAcceptCallback;
     }
 
+    @NonNull
     @Override
     public List<Step> cancel() {
         return Arrays.asList(new CompleteEffectVibratorStep(conductor, SystemClock.uptimeMillis(),
@@ -138,43 +129,4 @@
         controller.setAmplitude(amplitude);
         getVibration().stats.reportSetAmplitude();
     }
-
-    /**
-     * Return the {@link VibrationStepConductor#nextVibrateStep} with start and off timings
-     * calculated from {@link #getVibratorOnDuration()} based on the current
-     * {@link SystemClock#uptimeMillis()} and jumping all played segments from the effect.
-     */
-    protected List<Step> nextSteps(int segmentsPlayed) {
-        // Schedule next steps to run right away.
-        long nextStartTime = SystemClock.uptimeMillis();
-        if (mVibratorOnResult > 0) {
-            // Vibrator was turned on by this step, with mVibratorOnResult as the duration.
-            // Schedule next steps for right after the vibration finishes.
-            nextStartTime += mVibratorOnResult;
-        }
-        return nextSteps(nextStartTime, segmentsPlayed);
-    }
-
-    /**
-     * Return the {@link VibrationStepConductor#nextVibrateStep} with given start time,
-     * which might be calculated independently, and jumping all played segments from the effect.
-     *
-     * <p>This should be used when the vibrator on/off state is not responsible for the step
-     * execution timing, e.g. while playing the vibrator amplitudes.
-     */
-    protected List<Step> nextSteps(long nextStartTime, int segmentsPlayed) {
-        int nextSegmentIndex = segmentIndex + segmentsPlayed;
-        int effectSize = effect.getSegments().size();
-        int repeatIndex = effect.getRepeatIndex();
-        if (nextSegmentIndex >= effectSize && repeatIndex >= 0) {
-            // Count the loops that were played.
-            int loopSize = effectSize - repeatIndex;
-            int loopSegmentsPlayed = nextSegmentIndex - repeatIndex;
-            getVibration().stats.reportRepetition(loopSegmentsPlayed / loopSize);
-            nextSegmentIndex = repeatIndex + ((nextSegmentIndex - effectSize) % loopSize);
-        }
-        Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
-                nextSegmentIndex, mPendingVibratorOffDeadline);
-        return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep);
-    }
 }
diff --git a/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
index 48dd992..7f9c349 100644
--- a/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.VibrationEffect;
@@ -35,8 +36,7 @@
 
     CompleteEffectVibratorStep(VibrationStepConductor conductor, long startTime, boolean cancelled,
             VibratorController controller, long pendingVibratorOffDeadline) {
-        super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
-                pendingVibratorOffDeadline);
+        super(conductor, startTime, controller, pendingVibratorOffDeadline);
         mCancelled = cancelled;
     }
 
@@ -47,6 +47,7 @@
         return mCancelled;
     }
 
+    @NonNull
     @Override
     public List<Step> cancel() {
         if (mCancelled) {
@@ -57,6 +58,7 @@
         return super.cancel();
     }
 
+    @NonNull
     @Override
     public List<Step> play() {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "CompleteEffectVibratorStep");
diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
index 940bd08..e495af5 100644
--- a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.os.Trace;
 import android.os.VibrationEffect;
 import android.os.vibrator.PrimitiveSegment;
@@ -31,7 +32,7 @@
  * <p>This step will use the maximum supported number of consecutive segments of type
  * {@link PrimitiveSegment} starting at the current index.
  */
-final class ComposePrimitivesVibratorStep extends AbstractVibratorStep {
+final class ComposePrimitivesVibratorStep extends AbstractComposedVibratorStep {
     /**
      * Default limit to the number of primitives in a composition, if none is defined by the HAL,
      * to prevent repeating effects from generating an infinite list.
@@ -47,6 +48,7 @@
                 index, pendingVibratorOffDeadline);
     }
 
+    @NonNull
     @Override
     public List<Step> play() {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePrimitivesStep");
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
index 5d572be6..e8952fa 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.os.Trace;
 import android.os.VibrationEffect;
 import android.os.vibrator.RampSegment;
@@ -31,7 +32,7 @@
  * <p>This step will use the maximum supported number of consecutive segments of type
  * {@link RampSegment}, starting at the current index.
  */
-final class ComposePwleVibratorStep extends AbstractVibratorStep {
+final class ComposePwleVibratorStep extends AbstractComposedVibratorStep {
     /**
      * Default limit to the number of PWLE segments, if none is defined by the HAL, to prevent
      * repeating effects from generating an infinite list.
@@ -47,6 +48,7 @@
                 index, pendingVibratorOffDeadline);
     }
 
+    @NonNull
     @Override
     public List<Step> play() {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleStep");
diff --git a/services/core/java/com/android/server/vibrator/DeviceAdapter.java b/services/core/java/com/android/server/vibrator/DeviceAdapter.java
index 98309cd..bd4fc07 100644
--- a/services/core/java/com/android/server/vibrator/DeviceAdapter.java
+++ b/services/core/java/com/android/server/vibrator/DeviceAdapter.java
@@ -21,7 +21,6 @@
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
 import android.os.vibrator.VibrationEffectSegment;
-import android.util.Slog;
 import android.util.SparseArray;
 
 import java.util.ArrayList;
@@ -82,9 +81,8 @@
     @NonNull
     @Override
     public VibrationEffect adaptToVibrator(int vibratorId, @NonNull VibrationEffect effect) {
-        if (!(effect instanceof VibrationEffect.Composed)) {
+        if (!(effect instanceof VibrationEffect.Composed composed)) {
             // Segments adapters can only apply to Composed effects.
-            Slog.wtf(TAG, "Error adapting unsupported vibration effect: " + effect);
             return effect;
         }
 
@@ -95,7 +93,6 @@
         }
 
         VibratorInfo info = controller.getVibratorInfo();
-        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
         List<VibrationEffectSegment> newSegments = new ArrayList<>(composed.getSegments());
         int newRepeatIndex = composed.getRepeatIndex();
 
diff --git a/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java
index c9683d9..6456371 100644
--- a/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java
+++ b/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.os.Trace;
 import android.util.Slog;
 
@@ -43,6 +44,7 @@
         return true;
     }
 
+    @NonNull
     @Override
     public List<Step> play() {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishSequentialEffectStep");
@@ -61,6 +63,7 @@
         }
     }
 
+    @NonNull
     @Override
     public List<Step> cancel() {
         cancelImmediately();
diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
index 8094e7c5..4b23216 100644
--- a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.os.Trace;
 import android.os.VibrationEffect;
 import android.os.vibrator.PrebakedSegment;
@@ -31,7 +32,7 @@
  * <p>This step automatically falls back by replacing the prebaked segment with
  * {@link VibrationSettings#getFallbackEffect(int)}, if available.
  */
-final class PerformPrebakedVibratorStep extends AbstractVibratorStep {
+final class PerformPrebakedVibratorStep extends AbstractComposedVibratorStep {
 
     PerformPrebakedVibratorStep(VibrationStepConductor conductor, long startTime,
             VibratorController controller, VibrationEffect.Composed effect, int index,
@@ -42,6 +43,7 @@
                 index, pendingVibratorOffDeadline);
     }
 
+    @NonNull
     @Override
     public List<Step> play() {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformPrebakedVibratorStep");
diff --git a/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java
new file mode 100644
index 0000000..8f36118
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.NonNull;
+import android.os.Trace;
+import android.os.VibrationEffect;
+
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator on with a vendor-specific vibration from a
+ * {@link VibrationEffect.VendorEffect} effect.
+ */
+final class PerformVendorEffectVibratorStep extends AbstractVibratorStep {
+    /**
+     * Timeout to ensure vendor vibrations are not unbounded if vibrator callbacks are lost.
+     */
+    static final long VENDOR_EFFECT_MAX_DURATION_MS = 60_000; // 1 min
+
+    public final VibrationEffect.VendorEffect effect;
+
+    PerformVendorEffectVibratorStep(VibrationStepConductor conductor, long startTime,
+            VibratorController controller, VibrationEffect.VendorEffect effect,
+            long pendingVibratorOffDeadline) {
+        // This step should wait for the last vibration to finish (with the timeout) and for the
+        // intended step start time (to respect the effect delays).
+        super(conductor, Math.max(startTime, pendingVibratorOffDeadline), controller,
+                pendingVibratorOffDeadline);
+        this.effect = effect;
+    }
+
+    @NonNull
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformVendorEffectVibratorStep");
+        try {
+            long vibratorOnResult = controller.on(effect, getVibration().id);
+            vibratorOnResult = Math.min(vibratorOnResult, VENDOR_EFFECT_MAX_DURATION_MS);
+            handleVibratorOnResult(vibratorOnResult);
+            return List.of(new CompleteEffectVibratorStep(conductor, startTime,
+                    /* cancelled= */ false, controller, mPendingVibratorOffDeadline));
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
index f40c994..901f9c3 100644
--- a/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.util.Slog;
@@ -31,8 +32,7 @@
     RampOffVibratorStep(VibrationStepConductor conductor, long startTime, float amplitudeTarget,
             float amplitudeDelta, VibratorController controller,
             long pendingVibratorOffDeadline) {
-        super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
-                pendingVibratorOffDeadline);
+        super(conductor, startTime, controller, pendingVibratorOffDeadline);
         mAmplitudeTarget = amplitudeTarget;
         mAmplitudeDelta = amplitudeDelta;
     }
@@ -42,12 +42,14 @@
         return true;
     }
 
+    @NonNull
     @Override
     public List<Step> cancel() {
         return Arrays.asList(new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(),
                 controller, /* isCleanUp= */ true));
     }
 
+    @NonNull
     @Override
     public List<Step> play() {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "RampOffVibratorStep");
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
index e13ec6c..8478e77 100644
--- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.VibrationEffect;
@@ -32,7 +33,7 @@
  * <p>This step ignores vibration completion callbacks and control the vibrator on/off state
  * and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}.
  */
-final class SetAmplitudeVibratorStep extends AbstractVibratorStep {
+final class SetAmplitudeVibratorStep extends AbstractComposedVibratorStep {
     /**
      * The repeating waveform keeps the vibrator ON all the time. Use a minimum duration to
      * prevent short patterns from turning the vibrator ON too frequently.
@@ -69,6 +70,7 @@
         return shouldAcceptCallback;
     }
 
+    @NonNull
     @Override
     public List<Step> play() {
         // TODO: consider separating the "on" steps at the start into a separate Step.
diff --git a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
index c197271..3ceba57 100644
--- a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
+++ b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.vibrator.IVibratorManager;
 import android.os.CombinedVibration;
@@ -74,6 +75,7 @@
         return mVibratorsOnMaxDuration;
     }
 
+    @NonNull
     @Override
     public List<Step> play() {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartSequentialEffectStep");
@@ -111,6 +113,7 @@
         return nextSteps;
     }
 
+    @NonNull
     @Override
     public List<Step> cancel() {
         return VibrationStepConductor.EMPTY_STEP_LIST;
@@ -173,13 +176,12 @@
         for (int i = 0; i < vibratorCount; i++) {
             steps[i] = conductor.nextVibrateStep(vibrationStartTime,
                     conductor.getVibrators().get(effectMapping.vibratorIdAt(i)),
-                    effectMapping.effectAt(i),
-                    /* segmentIndex= */ 0, /* vibratorOffTimeout= */ 0);
+                    effectMapping.effectAt(i));
         }
 
         if (steps.length == 1) {
             // No need to prepare and trigger sync effects on a single vibrator.
-            return startVibrating(steps[0], nextSteps);
+            return startVibrating(steps[0], effectMapping.effectAt(0), nextSteps);
         }
 
         // This synchronization of vibrators should be executed one at a time, even if we are
@@ -196,8 +198,8 @@
                 effectMapping.getRequiredSyncCapabilities(),
                 effectMapping.getVibratorIds());
 
-        for (AbstractVibratorStep step : steps) {
-            long duration = startVibrating(step, nextSteps);
+        for (int i = 0; i < vibratorCount; i++) {
+            long duration = startVibrating(steps[i], effectMapping.effectAt(i), nextSteps);
             if (duration < 0) {
                 // One vibrator has failed, fail this entire sync attempt.
                 hasFailed = true;
@@ -231,7 +233,12 @@
         return hasFailed ? -1 : maxDuration;
     }
 
-    private long startVibrating(AbstractVibratorStep step, List<Step> nextSteps) {
+    private long startVibrating(@Nullable AbstractVibratorStep step, VibrationEffect effect,
+            List<Step> nextSteps) {
+        if (step == null) {
+            // Failed to create a step for VibrationEffect.
+            return -1;
+        }
         nextSteps.addAll(step.play());
         long stepDuration = step.getVibratorOnDuration();
         if (stepDuration < 0) {
@@ -239,7 +246,7 @@
             return stepDuration;
         }
         // Return the longest estimation for the entire effect.
-        return Math.max(stepDuration, step.effect.getDuration());
+        return Math.max(stepDuration, effect.getDuration());
     }
 
     /**
@@ -249,28 +256,20 @@
      * play all of the effects in sync.
      */
     final class DeviceEffectMap {
-        private final SparseArray<VibrationEffect.Composed> mVibratorEffects;
+        private final SparseArray<VibrationEffect> mVibratorEffects;
         private final int[] mVibratorIds;
         private final long mRequiredSyncCapabilities;
 
         DeviceEffectMap(CombinedVibration.Mono mono) {
             SparseArray<VibratorController> vibrators = conductor.getVibrators();
             VibrationEffect effect = mono.getEffect();
-            if (effect instanceof VibrationEffect.Composed) {
-                mVibratorEffects = new SparseArray<>(vibrators.size());
-                mVibratorIds = new int[vibrators.size()];
+            mVibratorEffects = new SparseArray<>(vibrators.size());
+            mVibratorIds = new int[vibrators.size()];
 
-                VibrationEffect.Composed composedEffect = (VibrationEffect.Composed) effect;
-                for (int i = 0; i < vibrators.size(); i++) {
-                    int vibratorId = vibrators.keyAt(i);
-                    mVibratorEffects.put(vibratorId, composedEffect);
-                    mVibratorIds[i] = vibratorId;
-                }
-            } else {
-                Slog.wtf(VibrationThread.TAG,
-                        "Unable to map device vibrators to unexpected effect: " + effect);
-                mVibratorEffects = new SparseArray<>();
-                mVibratorIds = new int[0];
+            for (int i = 0; i < vibrators.size(); i++) {
+                int vibratorId = vibrators.keyAt(i);
+                mVibratorEffects.put(vibratorId, effect);
+                mVibratorIds[i] = vibratorId;
             }
             mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
         }
@@ -282,13 +281,7 @@
             for (int i = 0; i < stereoEffects.size(); i++) {
                 int vibratorId = stereoEffects.keyAt(i);
                 if (vibrators.contains(vibratorId)) {
-                    VibrationEffect effect = stereoEffects.valueAt(i);
-                    if (effect instanceof VibrationEffect.Composed) {
-                        mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
-                    } else {
-                        Slog.wtf(VibrationThread.TAG,
-                                "Unable to map device vibrators to unexpected effect: " + effect);
-                    }
+                    mVibratorEffects.put(vibratorId, stereoEffects.valueAt(i));
                 }
             }
             mVibratorIds = new int[mVibratorEffects.size()];
@@ -326,7 +319,7 @@
         }
 
         /** Return the {@link VibrationEffect} at given index. */
-        public VibrationEffect.Composed effectAt(int index) {
+        public VibrationEffect effectAt(int index) {
             return mVibratorEffects.valueAt(index);
         }
 
@@ -338,16 +331,24 @@
          * IVibratorManager.CAP_PREPARE_* and IVibratorManager.CAP_MIXED_TRIGGER_* capabilities.
          */
         private long calculateRequiredSyncCapabilities(
-                SparseArray<VibrationEffect.Composed> effects) {
+                SparseArray<VibrationEffect> effects) {
             long prepareCap = 0;
             for (int i = 0; i < effects.size(); i++) {
-                VibrationEffectSegment firstSegment = effects.valueAt(i).getSegments().get(0);
-                if (firstSegment instanceof StepSegment) {
-                    prepareCap |= IVibratorManager.CAP_PREPARE_ON;
-                } else if (firstSegment instanceof PrebakedSegment) {
+                VibrationEffect effect = effects.valueAt(i);
+                if (effect instanceof VibrationEffect.VendorEffect) {
                     prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM;
-                } else if (firstSegment instanceof PrimitiveSegment) {
-                    prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE;
+                } else if (effect instanceof VibrationEffect.Composed composed) {
+                    VibrationEffectSegment firstSegment = composed.getSegments().get(0);
+                    if (firstSegment instanceof StepSegment) {
+                        prepareCap |= IVibratorManager.CAP_PREPARE_ON;
+                    } else if (firstSegment instanceof PrebakedSegment) {
+                        prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM;
+                    } else if (firstSegment instanceof PrimitiveSegment) {
+                        prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE;
+                    }
+                } else {
+                    Slog.wtf(VibrationThread.TAG,
+                            "Unable to check sync capabilities to unexpected effect: " + effect);
                 }
             }
             int triggerCap = 0;
diff --git a/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java b/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
index 065ce11..87dc269 100644
--- a/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.os.SystemClock;
 import android.os.Trace;
 
@@ -36,7 +37,7 @@
 
     TurnOffVibratorStep(VibrationStepConductor conductor, long startTime,
             VibratorController controller, boolean isCleanUp) {
-        super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1, startTime);
+        super(conductor, startTime, controller, startTime);
         mIsCleanUp = isCleanUp;
     }
 
@@ -45,6 +46,7 @@
         return mIsCleanUp;
     }
 
+    @NonNull
     @Override
     public List<Step> cancel() {
         return Arrays.asList(new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(),
@@ -56,6 +58,7 @@
         stopVibrating();
     }
 
+    @NonNull
     @Override
     public List<Step> play() {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "TurnOffVibratorStep");
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 689b495..5c567da 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -393,13 +393,14 @@
 
         private void dumpEffect(
                 ProtoOutputStream proto, long fieldId, VibrationEffect effect) {
-            final long token = proto.start(fieldId);
-            VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
-            for (VibrationEffectSegment segment : composed.getSegments()) {
-                dumpEffect(proto, VibrationEffectProto.SEGMENTS, segment);
+            if (effect instanceof VibrationEffect.Composed composed) {
+                final long token = proto.start(fieldId);
+                for (VibrationEffectSegment segment : composed.getSegments()) {
+                    dumpEffect(proto, VibrationEffectProto.SEGMENTS, segment);
+                }
+                proto.write(VibrationEffectProto.REPEAT, composed.getRepeatIndex());
+                proto.end(token);
             }
-            proto.write(VibrationEffectProto.REPEAT, composed.getRepeatIndex());
-            proto.end(token);
         }
 
         private void dumpEffect(ProtoOutputStream proto, long fieldId,
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index d9ca710..3933759 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -25,14 +25,12 @@
 import android.os.Vibrator;
 import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
-import android.os.vibrator.VibrationEffectSegment;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Locale;
 
 /** Controls vibration scaling. */
@@ -136,12 +134,6 @@
      */
     @NonNull
     public VibrationEffect scale(@NonNull VibrationEffect effect, int usageHint) {
-        if (!(effect instanceof VibrationEffect.Composed)) {
-            // This only scales composed vibration effects.
-            Slog.wtf(TAG, "Error scaling unsupported vibration effect: " + effect);
-            return effect;
-        }
-
         int newEffectStrength = getEffectStrength(usageHint);
         ScaleLevel scaleLevel = mScaleLevels.get(getScaleLevel(usageHint));
         float adaptiveScale = getAdaptiveHapticsScale(usageHint);
@@ -154,26 +146,10 @@
             scaleLevel = SCALE_LEVEL_NONE;
         }
 
-        VibrationEffect.Composed composedEffect = (VibrationEffect.Composed) effect;
-        ArrayList<VibrationEffectSegment> segments =
-                new ArrayList<>(composedEffect.getSegments());
-        int segmentCount = segments.size();
-        for (int i = 0; i < segmentCount; i++) {
-            segments.set(i,
-                    segments.get(i).resolve(mDefaultVibrationAmplitude)
-                            .applyEffectStrength(newEffectStrength)
-                            .scale(scaleLevel.factor)
-                            .scaleLinearly(adaptiveScale));
-        }
-        if (segments.equals(composedEffect.getSegments())) {
-            // No segment was updated, return original effect.
-            return effect;
-        }
-        VibrationEffect.Composed scaled =
-                new VibrationEffect.Composed(segments, composedEffect.getRepeatIndex());
-        // Make sure we validate what was scaled, since we're using the constructor directly
-        scaled.validate();
-        return scaled;
+        return effect.resolve(mDefaultVibrationAmplitude)
+                .applyEffectStrength(newEffectStrength)
+                .scale(scaleLevel.factor)
+                .scaleLinearly(adaptiveScale);
     }
 
     /**
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index f3e226e..8c9a92d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -123,6 +123,24 @@
 
     @Nullable
     AbstractVibratorStep nextVibrateStep(long startTime, VibratorController controller,
+            VibrationEffect effect) {
+        if (Build.IS_DEBUGGABLE) {
+            expectIsVibrationThread(true);
+        }
+        if (effect instanceof VibrationEffect.VendorEffect vendorEffect) {
+            return new PerformVendorEffectVibratorStep(this, startTime, controller, vendorEffect,
+                    /* pendingVibratorOffDeadline= */ 0);
+        }
+        if (effect instanceof VibrationEffect.Composed composed) {
+            return nextVibrateStep(startTime, controller, composed, /* segmentIndex= */ 0,
+                    /* pendingVibratorOffDeadline= */ 0);
+        }
+        Slog.wtf(TAG, "Unable to create next step for unexpected effect: " + effect);
+        return null;
+    }
+
+    @NonNull
+    AbstractVibratorStep nextVibrateStep(long startTime, VibratorController controller,
             VibrationEffect.Composed effect, int segmentIndex, long pendingVibratorOffDeadline) {
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(true);
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index 988e8fe..8cc157c 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -20,8 +20,10 @@
 import android.hardware.vibrator.IVibrator;
 import android.os.Binder;
 import android.os.IVibratorStateListener;
+import android.os.Parcel;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.VibrationEffect;
 import android.os.VibratorInfo;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
@@ -262,6 +264,35 @@
     }
 
     /**
+     * Plays vendor vibration effect, using {@code vibrationId} for completion callback to
+     * {@link OnVibrationCompleteListener}.
+     *
+     * <p>This will affect the state of {@link #isVibrating()}.
+     *
+     * @return The positive duration of the vibration started, if successful, zero if the vibrator
+     * do not support the input or a negative number if the operation failed.
+     */
+    public long on(VibrationEffect.VendorEffect vendorEffect, long vibrationId) {
+        synchronized (mLock) {
+            Parcel vendorData = Parcel.obtain();
+            try {
+                vendorEffect.getVendorData().writeToParcel(vendorData, /* flags= */ 0);
+                vendorData.setDataPosition(0);
+                long duration = mNativeWrapper.performVendorEffect(vendorData,
+                        vendorEffect.getEffectStrength(), vendorEffect.getLinearScale(),
+                        vibrationId);
+                if (duration > 0) {
+                    mCurrentAmplitude = -1;
+                    notifyListenerOnVibrating(true);
+                }
+                return duration;
+            } finally {
+                vendorData.recycle();
+            }
+        }
+    }
+
+    /**
      * Plays predefined vibration effect, using {@code vibrationId} for completion callback to
      * {@link OnVibrationCompleteListener}.
      *
@@ -427,6 +458,9 @@
         private static native long performEffect(long nativePtr, long effect, long strength,
                 long vibrationId);
 
+        private static native long performVendorEffect(long nativePtr, Parcel vendorData,
+                long strength, float scale, long vibrationId);
+
         private static native long performComposedEffect(long nativePtr, PrimitiveSegment[] effect,
                 long vibrationId);
 
@@ -482,6 +516,12 @@
             return performEffect(mNativePtr, effect, strength, vibrationId);
         }
 
+        /** Turns vibrator on to perform a vendor-specific effect. */
+        public long performVendorEffect(Parcel vendorData, long strength, float scale,
+                long vibrationId) {
+            return performVendorEffect(mNativePtr, vendorData, strength, scale, vibrationId);
+        }
+
         /** Turns vibrator on to perform effect composed of give primitives effect. */
         public long compose(PrimitiveSegment[] primitives, long vibrationId) {
             return performComposedEffect(mNativePtr, primitives, vibrationId);
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index bff175f..48c4a68 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -540,6 +540,11 @@
             Slog.e(TAG, "token must not be null");
             return null;
         }
+        if (effect.hasVendorEffects()
+                && !hasPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)) {
+            Slog.w(TAG, "vibrate; no permission for vendor effects");
+            return null;
+        }
         enforceUpdateAppOpsStatsPermission(uid);
         if (!isEffectValid(effect)) {
             return null;
@@ -1304,12 +1309,13 @@
     }
 
     private void fillVibrationFallbacks(HalVibration vib, VibrationEffect effect) {
-        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+        if (!(effect instanceof VibrationEffect.Composed composed)) {
+            return;
+        }
         int segmentCount = composed.getSegments().size();
         for (int i = 0; i < segmentCount; i++) {
             VibrationEffectSegment segment = composed.getSegments().get(i);
-            if (segment instanceof PrebakedSegment) {
-                PrebakedSegment prebaked = (PrebakedSegment) segment;
+            if (segment instanceof PrebakedSegment prebaked) {
                 VibrationEffect fallback = mVibrationSettings.getFallbackEffect(
                         prebaked.getEffectId());
                 if (prebaked.shouldFallback() && fallback != null) {
@@ -1392,12 +1398,11 @@
 
     @Nullable
     private static PrebakedSegment extractPrebakedSegment(VibrationEffect effect) {
-        if (effect instanceof VibrationEffect.Composed) {
-            VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+        if (effect instanceof VibrationEffect.Composed composed) {
             if (composed.getSegments().size() == 1) {
                 VibrationEffectSegment segment = composed.getSegments().get(0);
-                if (segment instanceof PrebakedSegment) {
-                    return (PrebakedSegment) segment;
+                if (segment instanceof PrebakedSegment prebaked) {
+                    return prebaked;
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index a8dcaa8..023dd79 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1124,8 +1124,8 @@
         }
 
         try {
-            mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(),
-                    EnterPipRequestedItem.obtain(r.token));
+            final EnterPipRequestedItem item = new EnterPipRequestedItem(r.token);
+            mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(), item);
             return true;
         } catch (Exception e) {
             Slog.w(TAG, "Failed to send enter pip requested item: "
@@ -1140,8 +1140,8 @@
     void onPictureInPictureUiStateChanged(@NonNull ActivityRecord r,
             PictureInPictureUiState pipState) {
         try {
-            mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(),
-                    PipStateTransactionItem.obtain(r.token, pipState));
+            final PipStateTransactionItem item = new PipStateTransactionItem(r.token, pipState);
+            mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(), item);
         } catch (Exception e) {
             Slog.w(TAG, "Failed to send pip state transaction item: "
                     + r.intent.getComponent(), e);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 516fc65..4da7e53 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1467,8 +1467,9 @@
                     + "display, activityRecord=%s, displayId=%d, config=%s", this, displayId,
                     config);
 
-            mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                    MoveToDisplayItem.obtain(token, displayId, config, activityWindowInfo));
+            final MoveToDisplayItem item =
+                    new MoveToDisplayItem(token, displayId, config, activityWindowInfo);
+            mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
         } catch (RemoteException e) {
             // If process died, whatever.
         }
@@ -1485,8 +1486,9 @@
             ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending new config to %s, "
                     + "config: %s", this, config);
 
-            mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                    ActivityConfigurationChangeItem.obtain(token, config, activityWindowInfo));
+            final ActivityConfigurationChangeItem item =
+                    new ActivityConfigurationChangeItem(token, config, activityWindowInfo);
+            mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
         } catch (RemoteException e) {
             // If process died, whatever.
         }
@@ -1506,8 +1508,9 @@
             ProtoLog.v(WM_DEBUG_STATES, "Sending position change to %s, onTop: %b",
                     this, onTop);
 
-            mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                    TopResumedActivityChangeItem.obtain(token, onTop));
+            final TopResumedActivityChangeItem item =
+                    new TopResumedActivityChangeItem(token, onTop);
+            mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
         } catch (RemoteException e) {
             // If process died, whatever.
             Slog.w(TAG, "Failed to send top-resumed=" + onTop + " to " + this, e);
@@ -2810,9 +2813,9 @@
         }
         try {
             mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
-            mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                    TransferSplashScreenViewStateItem.obtain(token, parcelable,
-                            windowAnimationLeash));
+            final TransferSplashScreenViewStateItem item =
+                    new TransferSplashScreenViewStateItem(token, parcelable, windowAnimationLeash);
+            mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
             scheduleTransferSplashScreenTimeout();
         } catch (Exception e) {
             Slog.w(TAG, "onCopySplashScreenComplete fail: " + this);
@@ -4078,8 +4081,8 @@
 
             try {
                 if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this);
-                mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                        DestroyActivityItem.obtain(token, finishing));
+                final DestroyActivityItem item = new DestroyActivityItem(token, finishing);
+                mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
             } catch (Exception e) {
                 // We can just ignore exceptions here...  if the process has crashed, our death
                 // notification will clean things up.
@@ -4993,8 +4996,8 @@
             try {
                 final ArrayList<ResultInfo> list = new ArrayList<>();
                 list.add(new ResultInfo(resultWho, requestCode, resultCode, data, callerToken));
-                mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                        ActivityResultItem.obtain(token, list));
+                final ActivityResultItem item = new ActivityResultItem(token, list);
+                mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
                 return;
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending result to " + this, e);
@@ -5005,9 +5008,9 @@
         if (forceSendForMediaProjection && attachedToProcess() && isState(STARTED, PAUSING, PAUSED,
                 STOPPING, STOPPED)) {
             // Build result to be returned immediately.
-            final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
-                    token, List.of(new ResultInfo(resultWho, requestCode, resultCode, data,
-                            callerToken)));
+            final List<ResultInfo> infos = List.of(
+                    new ResultInfo(resultWho, requestCode, resultCode, data, callerToken));
+            final ActivityResultItem activityResultItem = new ActivityResultItem(token, infos);
             // When the activity result is delivered, the activity will transition to RESUMED.
             // Since the activity is only resumed so the result can be immediately delivered,
             // return it to its original lifecycle state.
@@ -5050,13 +5053,13 @@
     private ActivityLifecycleItem getLifecycleItemForCurrentStateForResult() {
         switch (mState) {
             case STARTED:
-                return StartActivityItem.obtain(token, null);
+                return new StartActivityItem(token, null);
             case PAUSING:
             case PAUSED:
-                return PauseActivityItem.obtain(token);
+                return new PauseActivityItem(token);
             case STOPPING:
             case STOPPED:
-                return StopActivityItem.obtain(token);
+                return new StopActivityItem(token);
             default:
                 // Do not send a result immediately if the activity is in state INITIALIZING,
                 // RESTARTING_PROCESS, FINISHING, DESTROYING, or DESTROYED.
@@ -5111,8 +5114,9 @@
                 // Making sure the client state is RESUMED after transaction completed and doing
                 // so only if activity is currently RESUMED. Otherwise, client may have extra
                 // life-cycle calls to RESUMED (and PAUSED later).
-                mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                        NewIntentItem.obtain(token, ar, mState == RESUMED));
+                final NewIntentItem item =
+                        new NewIntentItem(token, ar, mState == RESUMED /* resume */);
+                mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
                 unsent = false;
             } catch (RemoteException e) {
                 Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
@@ -6344,9 +6348,9 @@
             EventLogTags.writeWmPauseActivity(mUserId, System.identityHashCode(this),
                     shortComponentName, "userLeaving=false", "make-active");
             try {
-                mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                        PauseActivityItem.obtain(token, finishing, false /* userLeaving */,
-                                false /* dontReport */, mAutoEnteringPip));
+                final PauseActivityItem item = new PauseActivityItem(token, finishing,
+                        false /* userLeaving */, false /* dontReport */, mAutoEnteringPip);
+                mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
             }
@@ -6358,7 +6362,7 @@
 
             try {
                 mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                        StartActivityItem.obtain(token, takeSceneTransitionInfo()));
+                        new StartActivityItem(token, takeSceneTransitionInfo()));
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending start: " + intent.getComponent(), e);
             }
@@ -6655,7 +6659,7 @@
             EventLogTags.writeWmStopActivity(
                     mUserId, System.identityHashCode(this), shortComponentName);
             mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                    StopActivityItem.obtain(token));
+                    new StopActivityItem(token));
 
             mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT);
         } catch (Exception e) {
@@ -9990,17 +9994,17 @@
         try {
             ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
                     (andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
-            final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(token,
+            final ClientTransactionItem callbackItem = new ActivityRelaunchItem(token,
                     pendingResults, pendingNewIntents, configChangeFlags,
                     new MergedConfiguration(getProcessGlobalConfiguration(),
                             getMergedOverrideConfiguration()),
                     preserveWindow, getActivityWindowInfo());
             final ActivityLifecycleItem lifecycleItem;
             if (andResume) {
-                lifecycleItem = ResumeActivityItem.obtain(token, isTransitionForward(),
+                lifecycleItem = new ResumeActivityItem(token, isTransitionForward(),
                         shouldSendCompatFakeFocus());
             } else {
-                lifecycleItem = PauseActivityItem.obtain(token);
+                lifecycleItem = new PauseActivityItem(token);
             }
             mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
                     app.getThread(), callbackItem, lifecycleItem);
@@ -10092,7 +10096,7 @@
         // {@link ActivityTaskManagerService.activityStopped}).
         try {
             mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                    StopActivityItem.obtain(token));
+                    new StopActivityItem(token));
         } catch (RemoteException e) {
             Slog.w(TAG, "Exception thrown during restart " + this, e);
         }
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
index bc82271..dcc325e 100644
--- a/services/core/java/com/android/server/wm/ActivityRefresher.java
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -84,9 +84,9 @@
         ProtoLog.v(WM_DEBUG_STATES,
                 "Refreshing activity for freeform camera compatibility treatment, "
                         + "activityRecord=%s", activity);
-        final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(
-                activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
-        final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
+        final RefreshCallbackItem refreshCallbackItem =
+                new RefreshCallbackItem(activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
+        final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(
                 activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
         try {
             activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e81b440..b0d8925 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -929,7 +929,7 @@
                 final boolean isTransitionForward = r.isTransitionForward();
                 final IBinder fragmentToken = r.getTaskFragment().getFragmentToken();
                 final int deviceId = getDeviceIdForDisplayId(r.getDisplayId());
-                final LaunchActivityItem launchActivityItem = LaunchActivityItem.obtain(r.token,
+                final LaunchActivityItem launchActivityItem = new LaunchActivityItem(r.token,
                         r.intent, System.identityHashCode(r), r.info,
                         procConfig, overrideConfig, deviceId,
                         r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,
@@ -942,12 +942,12 @@
                 // Set desired final state.
                 final ActivityLifecycleItem lifecycleItem;
                 if (andResume) {
-                    lifecycleItem = ResumeActivityItem.obtain(r.token, isTransitionForward,
+                    lifecycleItem = new ResumeActivityItem(r.token, isTransitionForward,
                             r.shouldSendCompatFakeFocus());
                 } else if (r.isVisibleRequested()) {
-                    lifecycleItem = PauseActivityItem.obtain(r.token);
+                    lifecycleItem = new PauseActivityItem(r.token);
                 } else {
-                    lifecycleItem = StopActivityItem.obtain(r.token);
+                    lifecycleItem = new StopActivityItem(r.token);
                 }
 
                 // Schedule transaction.
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 7c31177..2d1eb41 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -28,7 +28,13 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
 
+
+/**
+ * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is
+ * black layers of varying opacity at various Z-levels which create the effect of a Dim.
+ */
 class Dimmer {
 
     /**
@@ -122,8 +128,10 @@
         /**
          * Set the parameters to prepare the dim to be relative parented to the dimming container
          */
-        void prepareReparent(@NonNull WindowContainer<?> relativeParent, int relativeLayer) {
+        void prepareReparent(@NonNull WindowContainer<?> geometryParent,
+                @NonNull WindowContainer<?> relativeParent, int relativeLayer) {
             mAnimationHelper.setRequestedRelativeParent(relativeParent, relativeLayer);
+            mAnimationHelper.setRequestedGeometryParent(geometryParent);
         }
 
         /**
@@ -138,7 +146,8 @@
          * Whether anyone is currently requesting the dim
          */
         boolean isDimming() {
-            return mLastRequestedDimContainer != null;
+            return mLastRequestedDimContainer != null
+                    && (mHostContainer.isVisibleRequested() || !Flags.useTasksDimOnly());
         }
 
         private SurfaceControl makeDimLayer() {
@@ -208,13 +217,15 @@
      * the child of the host should call adjustRelativeLayer and {@link Dimmer#adjustAppearance} to
      * continue dimming. Indeed, this method won't be able to keep dimming or get a new DimState
      * without also adjusting the appearance.
+     * @param geometryParent    The container that defines the geometry of the dim
      * @param dimmingContainer      The container which to dim above. Should be a child of the host.
      * @param relativeLayer  The position of the dim wrt the container
      */
-    public void adjustRelativeLayer(@NonNull WindowContainer<?> dimmingContainer,
+    public void adjustPosition(@NonNull WindowContainer<?> geometryParent,
+                                    @NonNull WindowContainer<?> dimmingContainer,
                                     int relativeLayer) {
         if (mDimState != null) {
-            mDimState.prepareReparent(dimmingContainer, relativeLayer);
+            mDimState.prepareReparent(geometryParent, dimmingContainer, relativeLayer);
         }
     }
 
@@ -236,7 +247,9 @@
             return false;
         } else {
             // Someone is dimming, show the requested changes
-            mDimState.adjustSurfaceLayout(t);
+            if (!Flags.useTasksDimOnly()) {
+                mDimState.adjustSurfaceLayout(t);
+            }
             final WindowState ws = mDimState.mLastRequestedDimContainer.asWindowState();
             if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null
                     && ws.mActivityRecord.mStartingData != null) {
@@ -263,6 +276,7 @@
         return mDimState != null ? mDimState.mDimSurface : null;
     }
 
+    @Deprecated
     Rect getDimBounds() {
         return mDimState != null ? mDimState.mDimBounds : null;
     }
diff --git a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
index df1549e..3dba57f 100644
--- a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
+++ b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
@@ -26,6 +26,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl;
@@ -48,6 +49,7 @@
         private float mAlpha = -1f;
         private int mBlurRadius = -1;
         private WindowContainer<?> mDimmingContainer = null;
+        private WindowContainer<?> mGeometryParent = null;
         private int mRelativeLayer = -1;
         private static final float EPSILON = 0.0001f;
 
@@ -103,6 +105,11 @@
         mRequestedProperties.mRelativeLayer = relativeLayer;
     }
 
+    // Sets the requested layer to reparent the dim to without applying it immediately
+    void setRequestedGeometryParent(WindowContainer<?> geometryParent) {
+        mRequestedProperties.mGeometryParent = geometryParent;
+    }
+
     // Sets a requested change without applying it immediately
     void setRequestedAppearance(float alpha, int blurRadius) {
         mRequestedProperties.mAlpha = alpha;
@@ -129,7 +136,9 @@
         }
 
         dim.ensureVisible(t);
-        relativeReparent(dim.mDimSurface,
+        reparent(dim.mDimSurface,
+                mRequestedProperties.mGeometryParent != mCurrentProperties.mGeometryParent
+                        ? mRequestedProperties.mGeometryParent.getSurfaceControl() : null,
                 mRequestedProperties.mDimmingContainer.getSurfaceControl(),
                 mRequestedProperties.mRelativeLayer, t);
 
@@ -214,11 +223,17 @@
     }
 
     /**
-     * Change the relative parent of this dim layer
+     * Change the geometry and relative parent of this dim layer
      */
-    void relativeReparent(@NonNull SurfaceControl dimLayer, @NonNull SurfaceControl relativeParent,
-                          int relativePosition, @NonNull SurfaceControl.Transaction t) {
+    void reparent(@NonNull SurfaceControl dimLayer,
+                  @Nullable SurfaceControl newGeometryParent,
+                  @NonNull SurfaceControl relativeParent,
+                  int relativePosition,
+                  @NonNull SurfaceControl.Transaction t) {
         try {
+            if (newGeometryParent != null) {
+                t.reparent(dimLayer, newGeometryParent);
+            }
             t.setRelativeLayer(dimLayer, relativeParent, relativePosition);
         } catch (NullPointerException e) {
             Log.w(TAG, "Tried to change parent of dim " + dimLayer + " after remove", e);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index f2ccbc4..bded98c 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -183,6 +183,7 @@
 
     private Object mLastWindowFreezeSource = null;
     private float mScreenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+    private CharSequence mScreenBrightnessOverrideTag;
     private long mUserActivityTimeout = -1;
     private boolean mUpdateRotation = false;
     // Only set while traversing the default display based on its content.
@@ -770,6 +771,7 @@
         }
 
         mScreenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mScreenBrightnessOverrideTag = null;
         mUserActivityTimeout = -1;
         mObscureApplicationContentOnSecondaryDisplays = false;
         mSustainedPerformanceModeCurrent = false;
@@ -881,11 +883,15 @@
             final float brightnessOverride = mScreenBrightnessOverride < PowerManager.BRIGHTNESS_MIN
                     || mScreenBrightnessOverride > PowerManager.BRIGHTNESS_MAX
                     ? PowerManager.BRIGHTNESS_INVALID_FLOAT : mScreenBrightnessOverride;
+            CharSequence overrideTag = null;
+            if (brightnessOverride != PowerManager.BRIGHTNESS_INVALID_FLOAT) {
+                overrideTag = mScreenBrightnessOverrideTag;
+            }
             int brightnessFloatAsIntBits = Float.floatToIntBits(brightnessOverride);
             // Post these on a handler such that we don't call into power manager service while
             // holding the window manager lock to avoid lock contention with power manager lock.
             mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, brightnessFloatAsIntBits,
-                    0).sendToTarget();
+                    0, overrideTag).sendToTarget();
             mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget();
         }
 
@@ -1040,6 +1046,7 @@
             if (!syswin && w.mAttrs.screenBrightness >= 0
                     && Float.isNaN(mScreenBrightnessOverride)) {
                 mScreenBrightnessOverride = w.mAttrs.screenBrightness;
+                mScreenBrightnessOverrideTag = w.getWindowTag();
             }
 
             // This function assumes that the contents of the default display are processed first
@@ -1112,7 +1119,7 @@
             switch (msg.what) {
                 case SET_SCREEN_BRIGHTNESS_OVERRIDE:
                     mWmService.mPowerManagerInternal.setScreenBrightnessOverrideFromWindowManager(
-                            Float.intBitsToFloat(msg.arg1));
+                            Float.intBitsToFloat(msg.arg1), (CharSequence) msg.obj);
                     break;
                 case SET_USER_ACTIVITY_TIMEOUT:
                     mWmService.mPowerManagerInternal.
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 259ca83..74adeb9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3201,6 +3201,14 @@
         return "Task=" + mTaskId;
     }
 
+    WindowContainer<?> getDimmerParent() {
+        if (!inMultiWindowMode() && isTranslucentForTransition()) {
+            return getRootDisplayArea();
+        }
+        return this;
+    }
+
+    @Deprecated
     @Override
     Dimmer getDimmer() {
         // If the window is in multi-window mode, we want to dim at the Task level to ensure the dim
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 29e82f7..c83b280 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1610,18 +1610,15 @@
                         if (DEBUG_RESULTS) {
                             Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a);
                         }
-                        final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
-                                next.token, a);
-                        mAtmService.getLifecycleManager().scheduleTransactionItem(
-                                appThread, activityResultItem);
+                        final ActivityResultItem item = new ActivityResultItem(next.token, a);
+                        mAtmService.getLifecycleManager().scheduleTransactionItem(appThread, item);
                     }
                 }
 
                 if (next.newIntents != null) {
-                    final NewIntentItem newIntentItem = NewIntentItem.obtain(
-                            next.token, next.newIntents, true /* resume */);
-                    mAtmService.getLifecycleManager().scheduleTransactionItem(
-                            appThread, newIntentItem);
+                    final NewIntentItem item =
+                            new NewIntentItem(next.token, next.newIntents, true /* resume */);
+                    mAtmService.getLifecycleManager().scheduleTransactionItem(appThread, item);
                 }
 
                 // Well the app will no longer be stopped.
@@ -1635,7 +1632,7 @@
                 final int topProcessState = mAtmService.mTopProcessState;
                 next.app.setPendingUiCleanAndForceProcessStateUpTo(topProcessState);
                 next.abortAndClearOptionsAnimation();
-                final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
+                final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(
                         next.token, topProcessState, dc.isNextTransitionForward(),
                         next.shouldSendCompatFakeFocus());
                 mAtmService.getLifecycleManager().scheduleTransactionItem(
@@ -1881,9 +1878,9 @@
             EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
                     prev.shortComponentName, "userLeaving=" + userLeaving, reason);
 
-            mAtmService.getLifecycleManager().scheduleTransactionItem(prev.app.getThread(),
-                    PauseActivityItem.obtain(prev.token, prev.finishing, userLeaving,
-                            pauseImmediately, autoEnteringPip));
+            final PauseActivityItem item = new PauseActivityItem(prev.token, prev.finishing,
+                    userLeaving, pauseImmediately, autoEnteringPip);
+            mAtmService.getLifecycleManager().scheduleTransactionItem(prev.app.getThread(), item);
         } catch (Exception e) {
             // Ignore exception, if process died other code will cleanup.
             Slog.w(TAG, "Exception thrown during pause", e);
@@ -3105,6 +3102,7 @@
         return forAllWindows(getDimBehindWindow, true);
     }
 
+    @Deprecated
     @Override
     Dimmer getDimmer() {
         // If this is in an embedded TaskFragment and we want the dim applies on the TaskFragment.
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index cf5a1e6..358adc3 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -463,14 +463,26 @@
 
     boolean canApplyDim(@NonNull Task task) {
         if (mTransientLaunches == null) return true;
-        final Dimmer dimmer = task.getDimmer();
-        if (dimmer == null) {
-            return false;
-        }
-        if (dimmer.hostIsTask()) {
+        if (Flags.useTasksDimOnly()) {
+            WindowContainer<?> dimmerParent = task.getDimmerParent();
+            if (dimmerParent == null) {
+                return false;
+            }
             // Always allow to dim if the host only affects its task.
-            return true;
+            if (dimmerParent.asTask() == task) {
+                return true;
+            }
+        } else {
+            final Dimmer dimmer = task.getDimmer();
+            if (dimmer == null) {
+                return false;
+            }
+            if (dimmer.hostIsTask()) {
+                // Always allow to dim if the host only affects its task.
+                return true;
+            }
         }
+
         // The dimmer host of a translucent task can be a display, then it is not in transient-hide.
         for (int i = mTransientLaunches.size() - 1; i >= 0; --i) {
             // The transient task is usually the task of recents/home activity.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index eb1a80b..64f9c01 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -107,11 +107,10 @@
 import android.window.IWindowContainerToken;
 import android.window.WindowContainerToken;
 
-import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
-import com.android.internal.protolog.common.LogLevel;
 import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.common.LogLevel;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.wm.SurfaceAnimator.Animatable;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
@@ -3765,6 +3764,7 @@
         }, true /* traverseTopToBottom */);
     }
 
+    @Deprecated
     Dimmer getDimmer() {
         if (mParent == null) {
             return null;
diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java
index cd785e5..87e120c 100644
--- a/services/core/java/com/android/server/wm/WindowContextListenerController.java
+++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java
@@ -330,8 +330,8 @@
             mLastReportedConfig.setTo(config);
             mLastReportedDisplay = displayId;
 
-            mWpc.scheduleClientTransactionItem(WindowContextInfoChangeItem.obtain(
-                    mClientToken, config, displayId));
+            mWpc.scheduleClientTransactionItem(
+                    new WindowContextInfoChangeItem(mClientToken, config, displayId));
             mHasPendingConfiguration = false;
         }
 
@@ -356,7 +356,7 @@
                 }
             }
             mDeathRecipient.unlinkToDeath();
-            mWpc.scheduleClientTransactionItem(WindowContextWindowRemovalItem.obtain(mClientToken));
+            mWpc.scheduleClientTransactionItem(new WindowContextWindowRemovalItem(mClientToken));
             unregister();
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 984caf1..2bae0a8 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -437,7 +437,7 @@
             final ConfigurationChangeItem configurationChangeItem;
             synchronized (mLastReportedConfiguration) {
                 onConfigurationChangePreScheduled(mLastReportedConfiguration);
-                configurationChangeItem = ConfigurationChangeItem.obtain(
+                configurationChangeItem = new ConfigurationChangeItem(
                         mLastReportedConfiguration, mLastTopActivityDeviceId);
             }
             // Schedule immediately to make sure the app component (e.g. receiver, service) can get
@@ -1721,8 +1721,8 @@
         }
 
         onConfigurationChangePreScheduled(config);
-        scheduleClientTransactionItem(thread, ConfigurationChangeItem.obtain(
-                config, mLastTopActivityDeviceId));
+        scheduleClientTransactionItem(
+                thread, new ConfigurationChangeItem(config, mLastTopActivityDeviceId));
     }
 
     private void onConfigurationChangePreScheduled(@NonNull Configuration config) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 240978a..164994c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -103,6 +103,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
@@ -5195,9 +5196,10 @@
     }
 
     private void applyDims() {
+        Task task = getTask();
         if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind())
                 && mWinAnimator.getShown()
-                && !mHidden && mTransitionController.canApplyDim(getTask())) {
+                && !mHidden && mTransitionController.canApplyDim(task)) {
             // Only show the Dimmer when the following is satisfied:
             // 1. The window has the flag FLAG_DIM_BEHIND or blur behind is requested
             // 2. The WindowToken is not hidden so dims aren't shown when the window is exiting.
@@ -5210,10 +5212,31 @@
             // If the window is visible from surface flinger perspective (mWinAnimator.getShown())
             // but not window manager visible (!isVisibleNow()), it can still be the parent of the
             // dim, but can not create a new surface or continue a dim alone.
-            if (isVisibleNow()) {
-                getDimmer().adjustAppearance(this, dimAmount, blurRadius);
+            Dimmer dimmer;
+            WindowContainer<?> geometryParent = task;
+            if (Flags.useTasksDimOnly()) {
+                if (task != null) {
+                    geometryParent = task.getDimmerParent();
+                    dimmer = task.mDimmer;
+                } else {
+                    RootDisplayArea displayArea = getRootDisplayArea();
+                    geometryParent = displayArea;
+                    dimmer = displayArea != null ? displayArea.getDimmer() : null;
+                }
+                if (dimmer == null) {
+                    ProtoLog.e(WM_DEBUG_DIMMER, "WindowState %s does not have task or"
+                            + " display area for dimming", this);
+                    return;
+                }
+            } else {
+                dimmer = getDimmer();
             }
-            getDimmer().adjustRelativeLayer(this, -1 /* relativeLayer */);
+
+            if (isVisibleNow()) {
+                dimmer.adjustAppearance(this, dimAmount, blurRadius);
+            }
+            dimmer.adjustPosition(geometryParent,
+                    this /* relativeParent */, -1 /* relativeLayer */);
         }
     }
 
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 3cd5f76..9fa1a53 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -193,7 +193,7 @@
         "android.hardware.thermal-V2-ndk",
         "android.hardware.tv.input@1.0",
         "android.hardware.tv.input-V2-ndk",
-        "android.hardware.vibrator-V2-ndk",
+        "android.hardware.vibrator-V3-ndk",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
         "android.hardware.vibrator@1.2",
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index 2804a10..f12930a 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -17,7 +17,10 @@
 #define LOG_TAG "VibratorController"
 
 #include <aidl/android/hardware/vibrator/IVibrator.h>
+#include <android/binder_parcel.h>
+#include <android/binder_parcel_jni.h>
 #include <android/hardware/vibrator/1.3/IVibrator.h>
+#include <android/persistable_bundle_aidl.h>
 #include <nativehelper/JNIHelp.h>
 #include <utils/Log.h>
 #include <utils/misc.h>
@@ -32,6 +35,8 @@
 namespace V1_3 = android::hardware::vibrator::V1_3;
 namespace Aidl = aidl::android::hardware::vibrator;
 
+using aidl::android::os::PersistableBundle;
+
 namespace android {
 
 static JavaVM* sJvm = nullptr;
@@ -95,7 +100,7 @@
         return nullptr;
     }
     auto result = manager->getVibrator(vibratorId);
-    return result.isOk() ? std::move(result.value()) : nullptr;
+    return result.isOk() ? result.value() : nullptr;
 }
 
 class VibratorControllerWrapper {
@@ -192,6 +197,29 @@
     return effect;
 }
 
+static Aidl::VendorEffect vendorEffectFromJavaParcel(JNIEnv* env, jobject vendorData,
+                                                     jlong strength, jfloat scale) {
+    PersistableBundle bundle;
+    if (AParcel* parcel = AParcel_fromJavaParcel(env, vendorData); parcel != nullptr) {
+        if (binder_status_t status = bundle.readFromParcel(parcel); status == STATUS_OK) {
+            AParcel_delete(parcel);
+        } else {
+            jniThrowExceptionFmt(env, "android/os/BadParcelableException",
+                                 "Failed to readFromParcel, status %d (%s)", status,
+                                 strerror(-status));
+        }
+    } else {
+        jniThrowExceptionFmt(env, "android/os/BadParcelableException",
+                             "Failed to AParcel_fromJavaParcel, for nullptr");
+    }
+
+    Aidl::VendorEffect effect;
+    effect.vendorData = bundle;
+    effect.strength = static_cast<Aidl::EffectStrength>(strength);
+    effect.scale = static_cast<float>(scale);
+    return effect;
+}
+
 static void destroyNativeWrapper(void* ptr) {
     VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
     if (wrapper) {
@@ -289,6 +317,23 @@
     return result.isOk() ? result.value().count() : (result.isUnsupported() ? 0 : -1);
 }
 
+static jlong vibratorPerformVendorEffect(JNIEnv* env, jclass /* clazz */, jlong ptr,
+                                         jobject vendorData, jlong strength, jfloat scale,
+                                         jlong vibrationId) {
+    VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
+    if (wrapper == nullptr) {
+        ALOGE("vibratorPerformVendorEffect failed because native wrapper was not initialized");
+        return -1;
+    }
+    Aidl::VendorEffect effect = vendorEffectFromJavaParcel(env, vendorData, strength, scale);
+    auto callback = wrapper->createCallback(vibrationId);
+    auto performVendorEffectFn = [&effect, &callback](vibrator::HalWrapper* hal) {
+        return hal->performVendorEffect(effect, callback);
+    };
+    auto result = wrapper->halCall<void>(performVendorEffectFn, "performVendorEffect");
+    return result.isOk() ? std::numeric_limits<int64_t>::max() : (result.isUnsupported() ? 0 : -1);
+}
+
 static jlong vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong ptr,
                                            jobjectArray composition, jlong vibrationId) {
     VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
@@ -466,6 +511,7 @@
         {"off", "(J)V", (void*)vibratorOff},
         {"setAmplitude", "(JF)V", (void*)vibratorSetAmplitude},
         {"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect},
+        {"performVendorEffect", "(JLandroid/os/Parcel;JFJ)J", (void*)vibratorPerformVendorEffect},
         {"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;J)J",
          (void*)vibratorPerformComposedEffect},
         {"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJ)J",
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index a738acb..598e273 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -63,7 +63,7 @@
     libs: [
         "android.hardware.power-V1-java",
         "android.hardware.tv.cec-V1.0-java",
-        "android.hardware.vibrator-V2-java",
+        "android.hardware.vibrator-V3-java",
         "android.hidl.manager-V1.0-java",
         "android.test.mock",
         "android.test.base",
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
index 990c383..04b79b4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertEquals;
 
-
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -43,17 +42,34 @@
         assertEquals(mBrightnessReason.getReason(), BrightnessReason.REASON_UNKNOWN);
         assertEquals(mBrightnessReason.getModifier(), 0);
 
+        CharSequence tag = "my tag";
         mBrightnessReason.set(
                 getReason(BrightnessReason.REASON_BOOST, BrightnessReason.MODIFIER_THROTTLED));
+        mBrightnessReason.setTag(tag);
+
         assertEquals(mBrightnessReason.getReason(), BrightnessReason.REASON_BOOST);
         assertEquals(mBrightnessReason.getModifier(), BrightnessReason.MODIFIER_THROTTLED);
+        assertEquals(mBrightnessReason.getTag().toString(), tag);
     }
 
     @Test
-    public void toStringGeneratesExpectedString() {
-        String actualString = mBrightnessReason.toString();
-        String expectedString = "doze [ low_pwr ]";
-        assertEquals(actualString, expectedString);
+    public void toStringGeneratedExpectedString() {
+        assertEquals("doze [ low_pwr ]", mBrightnessReason.toString());
+    }
+
+    @Test
+    public void overrideTagString() {
+        // Should not print out the tag for "doze"
+        mBrightnessReason.setTag("my/tag");
+        assertEquals("doze(my/tag) [ low_pwr ]", mBrightnessReason.toString());
+
+        // Should print out tag for "override"
+        mBrightnessReason.setReason(BrightnessReason.REASON_OVERRIDE);
+        assertEquals("override(my/tag) [ low_pwr ]", mBrightnessReason.toString());
+
+        // Should not print anything if no tag.
+        mBrightnessReason.setTag(null);
+        assertEquals("override [ low_pwr ]", mBrightnessReason.toString());
     }
 
     @Test
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
index 94b8d68..39def75 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
@@ -256,7 +256,9 @@
                 .setBrightnessFactor(brightnessFactor)
                 .build();
 
+        CharSequence tag = "my/tag";
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* overrideTag= */ tag,
                 /* useProximitySensor= */ false,
                 /* boostScreenBrightness= */ false,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -274,6 +276,7 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DIM);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
+        assertThat(displayPowerRequest.screenBrightnessOverrideTag.toString()).isEqualTo(tag);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(false);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(false);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -297,6 +300,7 @@
         mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_DOZE);
 
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* overrideTag= */ null,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -336,6 +340,7 @@
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
 
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* overrideTag= */ null,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -374,6 +379,7 @@
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
 
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* overrideTag= */ null,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -412,6 +418,7 @@
         mPowerGroup.sleepLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* overrideTag= */ null,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -451,6 +458,7 @@
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
         mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_SCREEN_BRIGHT);
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* overrideTag= */ null,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -488,6 +496,7 @@
                 .build();
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* overrideTag= */ null,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -526,6 +535,7 @@
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         mPowerGroup.setUserActivitySummaryLocked(USER_ACTIVITY_SCREEN_BRIGHT);
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* overrideTag= */ null,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -563,6 +573,7 @@
                 .build();
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* overrideTag= */ null,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index b9e99dd..a888dad 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -94,7 +94,7 @@
     libs: [
         "android.hardware.power-V1-java",
         "android.hardware.tv.cec-V1.0-java",
-        "android.hardware.vibrator-V2-java",
+        "android.hardware.vibrator-V3-java",
         "android.hidl.manager-V1.0-java",
         "android.test.mock",
         "android.test.base",
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java
index 7aa208b..5de323b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java
@@ -207,4 +207,28 @@
         assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
                 .isEqualTo("Mon,Wed,Fri-Sat,10:00 AM-4:00 PM");
     }
+
+    @Test
+    public void getShortDaysSummary_onlyDays() {
+        ScheduleInfo scheduleInfo = new ScheduleInfo();
+        scheduleInfo.startHour = 10;
+        scheduleInfo.endHour = 16;
+        scheduleInfo.days = new int[] {Calendar.MONDAY, Calendar.TUESDAY,
+                Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY};
+
+        assertThat(SystemZenRules.getShortDaysSummary(mContext, scheduleInfo))
+                .isEqualTo("Mon-Fri");
+    }
+
+    @Test
+    public void getTimeSummary_onlyTime() {
+        ScheduleInfo scheduleInfo = new ScheduleInfo();
+        scheduleInfo.startHour = 11;
+        scheduleInfo.endHour = 15;
+        scheduleInfo.days = new int[] {Calendar.MONDAY, Calendar.TUESDAY,
+                Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY};
+
+        assertThat(SystemZenRules.getTimeSummary(mContext, scheduleInfo))
+                .isEqualTo("11:00 AM-3:00 PM");
+    }
 }
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index da21cd3..757bcd8 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -16,7 +16,7 @@
     ],
 
     libs: [
-        "android.hardware.vibrator-V2-java",
+        "android.hardware.vibrator-V3-java",
         "android.test.mock",
         "android.test.base",
         "android.test.runner",
@@ -36,7 +36,6 @@
         "platform-test-annotations",
         "service-permission.stubs.system_server",
         "services.core",
-        "flag-junit",
     ],
     jni_libs: ["libdexmakerjvmtiagent"],
     platform_apis: true,
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
index 3013ed0..59d5577 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
@@ -25,6 +25,7 @@
 import android.hardware.vibrator.IVibrator;
 import android.os.CombinedVibration;
 import android.os.Handler;
+import android.os.PersistableBundle;
 import android.os.VibrationEffect;
 import android.os.test.TestLooper;
 import android.os.vibrator.PrebakedSegment;
@@ -32,6 +33,7 @@
 import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
@@ -103,6 +105,17 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void testVendorEffect_returnsOriginalSegment() {
+        PersistableBundle vendorData = new PersistableBundle();
+        vendorData.putInt("key", 1);
+        VibrationEffect effect = VibrationEffect.createVendorEffect(vendorData);
+
+        assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_ID, effect)).isEqualTo(effect);
+        assertThat(mAdapter.adaptToVibrator(PWLE_VIBRATOR_ID, effect)).isEqualTo(effect);
+    }
+
+    @Test
     public void testStepAndRampSegments_withoutPwleCapability_convertsRampsToSteps() {
         VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
                 // Step(amplitude, frequencyHz, duration)
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
index b264435..9ebeaa8 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -37,6 +37,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.os.ExternalVibrationScale;
 import android.os.Handler;
+import android.os.PersistableBundle;
 import android.os.PowerManagerInternal;
 import android.os.UserHandle;
 import android.os.VibrationAttributes;
@@ -232,6 +233,34 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void scale_withVendorEffect_setsEffectStrengthBasedOnSettings() {
+        setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        PersistableBundle vendorData = new PersistableBundle();
+        vendorData.putString("key", "value");
+        VibrationEffect effect = VibrationEffect.createVendorEffect(vendorData);
+
+        VibrationEffect.VendorEffect scaled =
+                (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
+        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
+
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                VIBRATION_INTENSITY_MEDIUM);
+        scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
+        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
+        scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
+        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
+
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
+        // Vibration setting being bypassed will use default setting.
+        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
+    }
+
+    @Test
     public void scale_withOneShotAndWaveform_resolvesAmplitude() {
         // No scale, default amplitude still resolved
         setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_LOW);
@@ -365,6 +394,30 @@
         assertTrue(scaled.getAmplitude() > 0.5);
     }
 
+    @Test
+    @RequiresFlagsEnabled({
+            android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED,
+            android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS,
+    })
+    public void scale_adaptiveHapticsOnVendorEffect_setsLinearScaleParameter() {
+        setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
+
+        mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
+
+        PersistableBundle vendorData = new PersistableBundle();
+        vendorData.putInt("key", 1);
+        VibrationEffect effect = VibrationEffect.createVendorEffect(vendorData);
+
+        VibrationEffect.VendorEffect scaled =
+                (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_RINGTONE);
+        assertEquals(scaled.getLinearScale(), 0.5f);
+
+        mVibrationScaler.removeAdaptiveHapticsScale(USAGE_RINGTONE);
+
+        scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_RINGTONE);
+        assertEquals(scaled.getLinearScale(), 1.0f);
+    }
+
     private void setDefaultIntensity(@VibrationAttributes.Usage int usage,
             @Vibrator.VibrationIntensity int intensity) {
         when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index d7004e7..3bd56de 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -48,6 +48,7 @@
 import android.os.CombinedVibration;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.PersistableBundle;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.SystemClock;
@@ -560,8 +561,37 @@
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
         Thread cancellingThread =
                 new Thread(() -> mVibrationConductor.notifyCancelled(
-                        new Vibration.EndInfo(
-                                Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+                        /* immediate= */ false));
+        cancellingThread.start();
+
+        waitForCompletion(/* timeout= */ 50);
+        cancellingThread.join();
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void vibrate_singleVibratorVendorEffectCancel_cancelsVibrationImmediately()
+            throws Exception {
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+        // Set long vendor effect duration to check it gets cancelled quickly.
+        mVibratorProviders.get(VIBRATOR_ID).setVendorEffectDuration(10 * TEST_TIMEOUT_MILLIS);
+
+        VibrationEffect effect = VibrationEffect.createVendorEffect(createTestVendorData());
+        long vibrationId = startThreadAndDispatcher(effect);
+
+        assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
+                TEST_TIMEOUT_MILLIS));
+        assertTrue(mThread.isRunningVibrationId(vibrationId));
+
+        // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
+        // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
+        Thread cancellingThread =
+                new Thread(() -> mVibrationConductor.notifyCancelled(
+                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
                         /* immediate= */ false));
         cancellingThread.start();
 
@@ -588,8 +618,7 @@
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
         Thread cancellingThread =
                 new Thread(() -> mVibrationConductor.notifyCancelled(
-                        new Vibration.EndInfo(
-                                Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
                         /* immediate= */ false));
         cancellingThread.start();
 
@@ -654,6 +683,27 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void vibrate_singleVibratorVendorEffect_runsVibration() {
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+
+        VibrationEffect effect = VibrationEffect.createVendorEffect(createTestVendorData());
+        long vibrationId = startThreadAndDispatcher(effect);
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID),
+                eq(PerformVendorEffectVibratorStep.VENDOR_EFFECT_MAX_DURATION_MS));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibrationId))
+                .containsExactly(effect)
+                .inOrder();
+    }
+
+    @Test
     public void vibrate_singleVibratorComposed_runsVibration() {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
@@ -1437,16 +1487,48 @@
                 .combine();
         long vibrationId = startThreadAndDispatcher(effect);
 
-        assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(),
-                TEST_TIMEOUT_MILLIS));
+        assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(), TEST_TIMEOUT_MILLIS));
         assertTrue(mThread.isRunningVibrationId(vibrationId));
 
         // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
         Thread cancellingThread = new Thread(
                 () -> mVibrationConductor.notifyCancelled(
-                        new Vibration.EndInfo(
-                                Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                        /* immediate= */ false));
+        cancellingThread.start();
+
+        waitForCompletion(/* timeout= */ 50);
+        cancellingThread.join();
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+        assertFalse(mControllers.get(1).isVibrating());
+        assertFalse(mControllers.get(2).isVibrating());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void vibrate_multipleVendorEffectCancel_cancelsVibrationImmediately() throws Exception {
+        mockVibrators(1, 2);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+        mVibratorProviders.get(1).setVendorEffectDuration(10 * TEST_TIMEOUT_MILLIS);
+        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+        mVibratorProviders.get(2).setVendorEffectDuration(10 * TEST_TIMEOUT_MILLIS);
+
+        CombinedVibration effect = CombinedVibration.startParallel()
+                .addVibrator(1, VibrationEffect.createVendorEffect(createTestVendorData()))
+                .addVibrator(2, VibrationEffect.createVendorEffect(createTestVendorData()))
+                .combine();
+        long vibrationId = startThreadAndDispatcher(effect);
+
+        assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(), TEST_TIMEOUT_MILLIS));
+        assertTrue(mThread.isRunningVibrationId(vibrationId));
+
+        // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
+        // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
+        Thread cancellingThread = new Thread(
+                () -> mVibrationConductor.notifyCancelled(
+                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
                         /* immediate= */ false));
         cancellingThread.start();
 
@@ -1614,6 +1696,25 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void vibrate_vendorEffectWithRampDown_doesNotAddRampDown() {
+        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+
+        VibrationEffect effect = VibrationEffect.createVendorEffect(createTestVendorData());
+        long vibrationId = startThreadAndDispatcher(effect);
+        waitForCompletion();
+
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibrationId))
+                .containsExactly(effect)
+                .inOrder();
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()).isEmpty();
+    }
+
+    @Test
     public void vibrate_composedWithRampDown_doesNotAddRampDown() {
         when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
@@ -1831,6 +1932,16 @@
         return array;
     }
 
+    private static PersistableBundle createTestVendorData() {
+        PersistableBundle vendorData = new PersistableBundle();
+        vendorData.putInt("id", 1);
+        vendorData.putDouble("scale", 0.5);
+        vendorData.putBoolean("loop", false);
+        vendorData.putLongArray("amplitudes", new long[] { 0, 255, 128 });
+        vendorData.putString("label", "vibration");
+        return vendorData;
+    }
+
     private VibrationEffectSegment expectedOneShot(long millis) {
         return new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
                 /* frequencyHz= */ 0, (int) millis);
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 5ae5677b9b5..1f4a469 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.vibrator;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -67,6 +69,7 @@
 import android.os.IExternalVibrationController;
 import android.os.IVibratorStateListener;
 import android.os.Looper;
+import android.os.PersistableBundle;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
@@ -1573,6 +1576,50 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void vibrate_vendorEffectsWithoutPermission_doesNotVibrate() throws Exception {
+        // Deny permission to vibrate with vendor effects
+        denyPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS);
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_TICK);
+        VibratorManagerService service = createSystemReadyService();
+
+        PersistableBundle vendorData = new PersistableBundle();
+        vendorData.putString("key", "value");
+        VibrationEffect vendorEffect = VibrationEffect.createVendorEffect(vendorData);
+        VibrationEffect tickEffect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK);
+
+        vibrateAndWaitUntilFinished(service, vendorEffect, RINGTONE_ATTRS);
+        vibrateAndWaitUntilFinished(service, tickEffect, RINGTONE_ATTRS);
+
+        // No vendor effect played, but predefined TICK plays successfully.
+        assertThat(fakeVibrator.getAllVendorEffects()).isEmpty();
+        assertThat(fakeVibrator.getAllEffectSegments()).hasSize(1);
+        assertThat(fakeVibrator.getAllEffectSegments().get(0)).isInstanceOf(PrebakedSegment.class);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void vibrate_vendorEffectsWithPermission_successful() throws Exception {
+        // Deny permission to vibrate with vendor effects
+        grantPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS);
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+        VibratorManagerService service = createSystemReadyService();
+
+        PersistableBundle vendorData = new PersistableBundle();
+        vendorData.putString("key", "value");
+        VibrationEffect vendorEffect = VibrationEffect.createVendorEffect(vendorData);
+
+        vibrateAndWaitUntilFinished(service, vendorEffect, RINGTONE_ATTRS);
+
+        assertThat(fakeVibrator.getAllVendorEffects()).containsExactly(vendorEffect);
+    }
+
+    @Test
     public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception {
         int defaultNotificationIntensity =
                 mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION);
@@ -1714,6 +1761,39 @@
     }
 
     @Test
+    @RequiresFlagsEnabled({
+            android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED,
+            android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS,
+    })
+    public void vibrate_withIntensitySettingsAndAdaptiveHaptics_appliesSettingsToVendorEffects()
+            throws Exception {
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_LOW);
+
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+        VibratorManagerService service = createSystemReadyService();
+
+        SparseArray<Float> vibrationScales = new SparseArray<>();
+        vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+        mVibratorControlService.setVibrationParams(
+                VibrationParamGenerator.generateVibrationParams(vibrationScales),
+                mFakeVibratorController);
+
+        PersistableBundle vendorData = new PersistableBundle();
+        vendorData.putString("key", "value");
+        VibrationEffect vendorEffect = VibrationEffect.createVendorEffect(vendorData);
+        vibrateAndWaitUntilFinished(service, vendorEffect, NOTIFICATION_ATTRS);
+
+        assertThat(fakeVibrator.getAllVendorEffects()).hasSize(1);
+        VibrationEffect.VendorEffect scaled = fakeVibrator.getAllVendorEffects().get(0);
+        assertThat(scaled.getEffectStrength()).isEqualTo(VibrationEffect.EFFECT_STRENGTH_STRONG);
+        assertThat(scaled.getLinearScale()).isEqualTo(0.4f);
+    }
+
+    @Test
     public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
         mockVibrators(1, 2);
         VibratorManagerService service = createSystemReadyService();
@@ -2729,7 +2809,9 @@
             CombinedVibration effect, VibrationAttributes attrs) {
         HalVibration vib = service.vibrateWithPermissionCheck(UID, deviceId, PACKAGE_NAME, effect,
                 attrs, "some reason", service);
-        mPendingVibrations.add(vib);
+        if (vib != null) {
+            mPendingVibrations.add(vib);
+        }
         return vib;
     }
 
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 2ddb47b..96c3e97 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -17,8 +17,11 @@
 package com.android.server.vibrator;
 
 import android.annotation.Nullable;
+import android.hardware.vibrator.IVibrator;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Parcel;
+import android.os.PersistableBundle;
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
 import android.os.vibrator.PrebakedSegment;
@@ -45,6 +48,7 @@
 
     private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>();
     private final Map<Long, List<VibrationEffectSegment>> mEffectSegments = new TreeMap<>();
+    private final Map<Long, List<VibrationEffect.VendorEffect>> mVendorEffects = new TreeMap<>();
     private final Map<Long, List<Integer>> mBraking = new HashMap<>();
     private final List<Float> mAmplitudes = new ArrayList<>();
     private final List<Boolean> mExternalControlStates = new ArrayList<>();
@@ -69,11 +73,16 @@
     private float mFrequencyResolution = Float.NaN;
     private float mQFactor = Float.NaN;
     private float[] mMaxAmplitudes;
+    private long mVendorEffectDuration = EFFECT_DURATION;
 
     void recordEffectSegment(long vibrationId, VibrationEffectSegment segment) {
         mEffectSegments.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(segment);
     }
 
+    void recordVendorEffect(long vibrationId, VibrationEffect.VendorEffect vendorEffect) {
+        mVendorEffects.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(vendorEffect);
+    }
+
     void recordBraking(long vibrationId, int braking) {
         mBraking.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(braking);
     }
@@ -130,6 +139,21 @@
         }
 
         @Override
+        public long performVendorEffect(Parcel vendorData, long strength, float scale,
+                long vibrationId) {
+            if ((mCapabilities & IVibrator.CAP_PERFORM_VENDOR_EFFECTS) == 0) {
+                return 0;
+            }
+            PersistableBundle bundle = PersistableBundle.CREATOR.createFromParcel(vendorData);
+            recordVendorEffect(vibrationId,
+                    new VibrationEffect.VendorEffect(bundle, (int) strength, scale));
+            applyLatency(mOnLatency);
+            scheduleListener(mVendorEffectDuration, vibrationId);
+            // HAL has unknown duration for vendor effects.
+            return Long.MAX_VALUE;
+        }
+
+        @Override
         public long compose(PrimitiveSegment[] primitives, long vibrationId) {
             if (mSupportedPrimitives == null) {
                 return 0;
@@ -328,6 +352,11 @@
         mMaxAmplitudes = maxAmplitudes;
     }
 
+    /** Set the duration of vendor effects in fake vibrator hardware. */
+    public void setVendorEffectDuration(long durationMs) {
+        mVendorEffectDuration = durationMs;
+    }
+
     /**
      * Return the amplitudes set by this controller, including zeroes for each time the vibrator was
      * turned off.
@@ -366,6 +395,29 @@
         }
         return result;
     }
+
+    /** Return list of {@link VibrationEffect.VendorEffect} played by this controller, in order. */
+    public List<VibrationEffect.VendorEffect> getVendorEffects(long vibrationId) {
+        if (mVendorEffects.containsKey(vibrationId)) {
+            return new ArrayList<>(mVendorEffects.get(vibrationId));
+        } else {
+            return new ArrayList<>();
+        }
+    }
+
+    /**
+     * Returns a list of all vibrations' effect segments, for external-use where vibration IDs
+     * aren't exposed.
+     */
+    public List<VibrationEffect.VendorEffect> getAllVendorEffects() {
+        // Returns segments in order of vibrationId, which increases over time. TreeMap gives order.
+        ArrayList<VibrationEffect.VendorEffect> result = new ArrayList<>();
+        for (List<VibrationEffect.VendorEffect> subList : mVendorEffects.values()) {
+            result.addAll(subList);
+        }
+        return result;
+    }
+
     /** Return list of states set for external control to the fake vibrator hardware. */
     public List<Boolean> getExternalControlStates() {
         return mExternalControlStates;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 421bbae..faaa80f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -523,9 +523,8 @@
         assertEquals(newConfig.uiMode, activityConfig.uiMode);
 
         // The configuration change is still sent to the activity, even if it doesn't relaunch.
-        final ActivityConfigurationChangeItem expected =
-                ActivityConfigurationChangeItem.obtain(activity.token, activityConfig,
-                        activity.getActivityWindowInfo());
+        final ActivityConfigurationChangeItem expected = new ActivityConfigurationChangeItem(
+                activity.token, activityConfig, activity.getActivityWindowInfo());
         verify(mClientLifecycleManager).scheduleTransactionItem(
                 eq(activity.app.getThread()), eq(expected));
     }
@@ -596,9 +595,8 @@
 
         final Configuration currentConfig = activity.getConfiguration();
         assertEquals(expectedOrientation, currentConfig.orientation);
-        final ActivityConfigurationChangeItem expected =
-                ActivityConfigurationChangeItem.obtain(activity.token, currentConfig,
-                        activity.getActivityWindowInfo());
+        final ActivityConfigurationChangeItem expected = new ActivityConfigurationChangeItem(
+                activity.token, currentConfig, activity.getActivityWindowInfo());
         verify(mClientLifecycleManager).scheduleTransactionItem(activity.app.getThread(), expected);
         verify(displayRotation).onSetRequestedOrientation();
     }
@@ -817,9 +815,8 @@
 
             activity.ensureActivityConfiguration(true /* ignoreVisibility */);
 
-            final ActivityConfigurationChangeItem expected =
-                    ActivityConfigurationChangeItem.obtain(activity.token,
-                            activity.getConfiguration(), activity.getActivityWindowInfo());
+            final ActivityConfigurationChangeItem expected = new ActivityConfigurationChangeItem(
+                    activity.token, activity.getConfiguration(), activity.getActivityWindowInfo());
             verify(mClientLifecycleManager).scheduleTransactionItem(
                     activity.app.getThread(), expected);
         } finally {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
index 6ad1044..14fbbe4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
@@ -191,9 +191,9 @@
         verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
                 times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
 
-        final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
-                cycleThroughStop ? ON_STOP : ON_PAUSE);
-        final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(mActivity.token,
+        final RefreshCallbackItem refreshCallbackItem =
+                new RefreshCallbackItem(mActivity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
+        final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(mActivity.token,
                 /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
 
         verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index eaa1641..f2592d2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -307,9 +307,9 @@
         verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
                 times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
 
-        final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
-                cycleThroughStop ? ON_STOP : ON_PAUSE);
-        final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(mActivity.token,
+        final RefreshCallbackItem refreshCallbackItem =
+                new RefreshCallbackItem(mActivity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
+        final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(mActivity.token,
                 /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
 
         verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 08f1dff..771e290 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -32,11 +32,13 @@
 
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 
 import com.android.server.testutils.StubTransaction;
 import com.android.server.wm.utils.MockAnimationAdapter;
+import com.android.window.flags.Flags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -142,11 +144,12 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_USE_TASKS_DIM_ONLY)
     public void testUpdateDimsAppliesCrop() {
         mHost.addChild(mChild, 0);
 
         mDimmer.adjustAppearance(mChild, 1, 1);
-        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.adjustPosition(mChild, mChild, -1);
 
         int width = 100;
         int height = 300;
@@ -163,7 +166,7 @@
         final int blur = 50;
         mHost.addChild(mChild, 0);
         mDimmer.adjustAppearance(mChild, alpha, blur);
-        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.adjustPosition(mChild, mChild, -1);
         SurfaceControl dimLayer = mDimmer.getDimLayer();
 
         assertNotNull("Dimmer should have created a surface", dimLayer);
@@ -184,12 +187,12 @@
         final int blur = 50;
         // Dim once
         mDimmer.adjustAppearance(mChild, alpha, blur);
-        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.adjustPosition(mChild, mChild, -1);
         SurfaceControl dimLayer = mDimmer.getDimLayer();
         mDimmer.updateDims(mTransaction);
         // Reset, and don't dim
         mDimmer.resetDimStates();
-        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.adjustPosition(mChild, mChild, -1);
         mDimmer.updateDims(mTransaction);
         verify(mTransaction).show(dimLayer);
         verify(mTransaction).remove(dimLayer);
@@ -203,24 +206,25 @@
         final int blur = 20;
         // Dim once
         mDimmer.adjustAppearance(mChild, alpha, blur);
-        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.adjustPosition(mChild, mChild, -1);
         SurfaceControl dimLayer = mDimmer.getDimLayer();
         mDimmer.updateDims(mTransaction);
         // Reset and dim again
         mDimmer.resetDimStates();
         mDimmer.adjustAppearance(mChild, alpha, blur);
-        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.adjustPosition(mChild, mChild, -1);
         mDimmer.updateDims(mTransaction);
         verify(mTransaction).show(dimLayer);
         verify(mTransaction, never()).remove(dimLayer);
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_USE_TASKS_DIM_ONLY)
     public void testDimUpdateWhileDimming() {
         mHost.addChild(mChild, 0);
         final float alpha = 0.8f;
         mDimmer.adjustAppearance(mChild, alpha, 20);
-        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.adjustPosition(mChild, mChild, -1);
         final Rect bounds = mDimmer.getDimBounds();
 
         SurfaceControl dimLayer = mDimmer.getDimLayer();
@@ -240,7 +244,7 @@
     public void testRemoveDimImmediately() {
         mHost.addChild(mChild, 0);
         mDimmer.adjustAppearance(mChild, 1, 2);
-        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.adjustPosition(mChild, mChild, -1);
         SurfaceControl dimLayer = mDimmer.getDimLayer();
         mDimmer.updateDims(mTransaction);
         verify(mTransaction, times(1)).show(dimLayer);
@@ -265,18 +269,18 @@
 
         mDimmer.resetDimStates();
         mDimmer.adjustAppearance(mChild, 0.1f, 0);
-        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.adjustPosition(mChild, mChild, -1);
         SurfaceControl dimLayer = mDimmer.getDimLayer();
         mDimmer.updateDims(mTransaction);
 
         mDimmer.resetDimStates();
         mDimmer.adjustAppearance(mChild, 0.2f, 0);
-        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.adjustPosition(mChild, mChild, -1);
         mDimmer.updateDims(mTransaction);
 
         mDimmer.resetDimStates();
         mDimmer.adjustAppearance(mChild, 0.3f, 0);
-        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.adjustPosition(mChild, mChild, -1);
         mDimmer.updateDims(mTransaction);
 
         verify(mTransaction).setAlpha(dimLayer, 0.2f);
@@ -296,18 +300,18 @@
 
         mDimmer.resetDimStates();
         mDimmer.adjustAppearance(mChild, 0.2f, 0);
-        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.adjustPosition(mChild, mChild, -1);
         SurfaceControl dimLayer = mDimmer.getDimLayer();
         mDimmer.updateDims(mTransaction);
 
         mDimmer.resetDimStates();
         mDimmer.adjustAppearance(mChild, 0.1f, 0);
-        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.adjustPosition(mChild, mChild, -1);
         mDimmer.updateDims(mTransaction);
 
         mDimmer.resetDimStates();
         mDimmer.adjustAppearance(mChild, 0f, 0);
-        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.adjustPosition(mChild, mChild, -1);
         mDimmer.updateDims(mTransaction);
 
         mDimmer.resetDimStates();
@@ -326,13 +330,13 @@
         mHost.addChild(second, 1);
 
         mDimmer.adjustAppearance(first, 0.5f, 0);
-        mDimmer.adjustRelativeLayer(first, -1);
+        mDimmer.adjustPosition(mChild, first, -1);
         SurfaceControl dimLayer = mDimmer.getDimLayer();
         mDimmer.updateDims(mTransaction);
 
         mDimmer.resetDimStates();
         mDimmer.adjustAppearance(second, 0.9f, 0);
-        mDimmer.adjustRelativeLayer(second, -1);
+        mDimmer.adjustPosition(mChild, second, -1);
         mDimmer.updateDims(mTransaction);
 
         verify(sTestAnimation, times(2)).startAnimation(
@@ -354,10 +358,10 @@
         mHost.addChild(second, 1);
 
         mDimmer.adjustAppearance(first, 0.5f, 0);
-        mDimmer.adjustRelativeLayer(first, -1);
+        mDimmer.adjustPosition(mChild, first, -1);
         SurfaceControl dimLayer = mDimmer.getDimLayer();
         mDimmer.adjustAppearance(second, 0.9f, 0);
-        mDimmer.adjustRelativeLayer(second, -1);
+        mDimmer.adjustPosition(mChild, second, -1);
         mDimmer.updateDims(mTransaction);
 
         verify(sTestAnimation, times(1)).startAnimation(
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index e9fcc40..2dea6ba 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -613,9 +613,9 @@
         verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
                 times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
 
-        final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
-                cycleThroughStop ? ON_STOP : ON_PAUSE);
-        final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(mActivity.token,
+        final RefreshCallbackItem refreshCallbackItem =
+                new RefreshCallbackItem(mActivity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
+        final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(mActivity.token,
                 /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
 
         verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
index 3722fef..c0e90f9 100644
--- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
@@ -35,6 +35,7 @@
 
 import android.Manifest;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
@@ -77,6 +78,7 @@
 import java.io.File;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -413,6 +415,311 @@
         verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
     }
 
+    @Test
+    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+    public void testCrashLoopWithRescuePartyAndRollbackObserver() throws Exception {
+        PackageWatchdog watchdog = createWatchdog();
+        RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
+        RollbackPackageHealthObserver rollbackObserver =
+                setUpRollbackPackageHealthObserver(watchdog);
+        VersionedPackage versionedPackageA = new VersionedPackage(APP_A, VERSION_CODE);
+
+        when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).then(inv -> {
+            ApplicationInfo info = new ApplicationInfo();
+            info.flags |= ApplicationInfo.FLAG_PERSISTENT
+                    | ApplicationInfo.FLAG_SYSTEM;
+            return info;
+        });
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: SCOPED_DEVICE_CONFIG_RESET
+        verify(rescuePartyObserver).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rollbackObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: ALL_DEVICE_CONFIG_RESET
+        verify(rescuePartyObserver).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
+        verify(rollbackObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: WARM_REBOOT
+        verify(rescuePartyObserver).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
+        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+        verify(rollbackObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: Low impact rollback
+        verify(rollbackObserver).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+
+        // update available rollbacks to mock rollbacks being applied after the call to
+        // rollbackObserver.execute
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+                List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD reached. No more mitigations applied
+        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+        verify(rollbackObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+    public void testCrashLoopWithRescuePartyAndRollbackObserverEnableDeprecateFlagReset()
+            throws Exception {
+        PackageWatchdog watchdog = createWatchdog();
+        RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
+        RollbackPackageHealthObserver rollbackObserver =
+                setUpRollbackPackageHealthObserver(watchdog);
+        VersionedPackage versionedPackageA = new VersionedPackage(APP_A, VERSION_CODE);
+
+        when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).then(inv -> {
+            ApplicationInfo info = new ApplicationInfo();
+            info.flags |= ApplicationInfo.FLAG_PERSISTENT
+                    | ApplicationInfo.FLAG_SYSTEM;
+            return info;
+        });
+
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: WARM_REBOOT
+        verify(rescuePartyObserver).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rollbackObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: Low impact rollback
+        verify(rollbackObserver).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+        // update available rollbacks to mock rollbacks being applied after the call to
+        // rollbackObserver.execute
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+                List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD reached. No more mitigations applied
+        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rollbackObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+    public void testCrashLoopSystemUIWithRescuePartyAndRollbackObserver() throws Exception {
+        PackageWatchdog watchdog = createWatchdog();
+        RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
+        RollbackPackageHealthObserver rollbackObserver =
+                setUpRollbackPackageHealthObserver(watchdog);
+        String systemUi = "com.android.systemui";
+        VersionedPackage versionedPackageUi = new VersionedPackage(
+                systemUi, VERSION_CODE);
+        RollbackInfo rollbackInfoUi = getRollbackInfo(systemUi, VERSION_CODE, 1,
+                PackageManager.ROLLBACK_USER_IMPACT_LOW);
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_LOW,
+                ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL, rollbackInfoUi));
+
+        when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).then(inv -> {
+            ApplicationInfo info = new ApplicationInfo();
+            info.flags |= ApplicationInfo.FLAG_PERSISTENT
+                    | ApplicationInfo.FLAG_SYSTEM;
+            return info;
+        });
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: SCOPED_DEVICE_CONFIG_RESET
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: ALL_DEVICE_CONFIG_RESET
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: WARM_REBOOT
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: Low impact rollback
+        verify(rollbackObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+        // update available rollbacks to mock rollbacks being applied after the call to
+        // rollbackObserver.execute
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+                List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: RESET_SETTINGS_UNTRUSTED_DEFAULTS
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 5);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: RESET_SETTINGS_UNTRUSTED_CHANGES
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 5);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 6);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: RESET_SETTINGS_TRUSTED_DEFAULTS
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 6);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 7);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: Factory reset. High impact rollbacks are performed only for boot loops.
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 7);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 8);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+    public void testCrashLoopSystemUIWithRescuePartyAndRollbackObserverEnableDeprecateFlagReset()
+            throws Exception {
+        PackageWatchdog watchdog = createWatchdog();
+        RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
+        RollbackPackageHealthObserver rollbackObserver =
+                setUpRollbackPackageHealthObserver(watchdog);
+        String systemUi = "com.android.systemui";
+        VersionedPackage versionedPackageUi = new VersionedPackage(
+                systemUi, VERSION_CODE);
+        RollbackInfo rollbackInfoUi = getRollbackInfo(systemUi, VERSION_CODE, 1,
+                PackageManager.ROLLBACK_USER_IMPACT_LOW);
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_LOW,
+                ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL, rollbackInfoUi));
+
+        when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).then(inv -> {
+            ApplicationInfo info = new ApplicationInfo();
+            info.flags |= ApplicationInfo.FLAG_PERSISTENT
+                    | ApplicationInfo.FLAG_SYSTEM;
+            return info;
+        });
+
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: WARM_REBOOT
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: Low impact rollback
+        verify(rollbackObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+        // update available rollbacks to mock rollbacks being applied after the call to
+        // rollbackObserver.execute
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+                List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: Factory reset. High impact rollbacks are performed only for boot loops.
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+    }
+
     RollbackPackageHealthObserver setUpRollbackPackageHealthObserver(PackageWatchdog watchdog) {
         RollbackPackageHealthObserver rollbackObserver =
                 spy(new RollbackPackageHealthObserver(mSpyContext, mApexManager));
@@ -424,7 +731,6 @@
         watchdog.registerHealthObserver(rollbackObserver);
         return rollbackObserver;
     }
-
     RescuePartyObserver setUpRescuePartyObserver(PackageWatchdog watchdog) {
         setCrashRecoveryPropRescueBootCount(0);
         RescuePartyObserver rescuePartyObserver = spy(RescuePartyObserver.getInstance(mSpyContext));
@@ -686,4 +992,20 @@
         mTestLooper.moveTimeForward(milliSeconds);
         mTestLooper.dispatchAll();
     }
+
+    private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog,
+            List<VersionedPackage> packages, int failureReason) {
+        long triggerFailureCount = watchdog.getTriggerFailureCount();
+        if (failureReason == PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK
+                || failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+            triggerFailureCount = 1;
+        }
+        for (int i = 0; i < triggerFailureCount; i++) {
+            watchdog.onPackageFailure(packages, failureReason);
+        }
+        mTestLooper.dispatchAll();
+        if (Flags.recoverabilityDetection()) {
+            moveTimeForwardAndDispatch(watchdog.DEFAULT_MITIGATION_WINDOW_MS);
+        }
+    }
 }
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 4c81939..3f9016b 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -55,7 +55,10 @@
             cflags: ["-D_DARWIN_UNLIMITED_STREAMS"],
         },
     },
-    header_libs: ["jni_headers"],
+    header_libs: [
+        "jni_headers",
+        "native_headers",
+    ],
     static_libs: [
         "libandroidfw",
         "libutils",