Merge "Revert "Per-display group PowerManager APIs by default"" into main
diff --git a/Android.bp b/Android.bp
index 516fc9c..811755d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -368,6 +368,7 @@
     jarjar_rules: ":framework-jarjar-rules",
     javac_shard_size: 150,
     plugins: [
+        "cached-property-annotation-processor",
         "view-inspector-annotation-processor",
         "staledataclass-annotation-processor",
         "error_prone_android_framework",
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 52a761f..31d2ecd 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -34,6 +34,7 @@
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
+import android.os.Process;
 import android.system.SystemCleaner;
 import android.util.Log;
 
@@ -638,6 +639,12 @@
      * @hide
      */
     public void enableCleaner() {
+        // JobParameters objects are passed by reference in local Binder
+        // transactions for clients running as SYSTEM. The life cycle of the
+        // JobParameters objects are no longer controlled by the client.
+        if (Process.myUid() == Process.SYSTEM_UID) {
+            return;
+        }
         if (mJobCleanupCallback == null) {
             initCleaner(new JobCleanupCallback(IJobCallback.Stub.asInterface(callback), jobId));
         }
diff --git a/core/api/current.txt b/core/api/current.txt
index b400f37..53da338 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6858,27 +6858,36 @@
 
   @FlaggedApi("android.app.api_rich_ongoing") public static class Notification.ProgressStyle extends android.app.Notification.Style {
     ctor public Notification.ProgressStyle();
+    method @NonNull public android.app.Notification.ProgressStyle addProgressPoint(@NonNull android.app.Notification.ProgressStyle.Point);
     method @NonNull public android.app.Notification.ProgressStyle addProgressSegment(@NonNull android.app.Notification.ProgressStyle.Segment);
-    method @NonNull public android.app.Notification.ProgressStyle addProgressStep(@NonNull android.app.Notification.ProgressStyle.Step);
     method public int getProgress();
     method @Nullable public android.graphics.drawable.Icon getProgressEndIcon();
     method public int getProgressMax();
+    method @NonNull public java.util.List<android.app.Notification.ProgressStyle.Point> getProgressPoints();
     method @NonNull public java.util.List<android.app.Notification.ProgressStyle.Segment> getProgressSegments();
     method @Nullable public android.graphics.drawable.Icon getProgressStartIcon();
-    method @NonNull public java.util.List<android.app.Notification.ProgressStyle.Step> getProgressSteps();
     method @Nullable public android.graphics.drawable.Icon getProgressTrackerIcon();
     method public boolean isProgressIndeterminate();
     method public boolean isStyledByProgress();
     method @NonNull public android.app.Notification.ProgressStyle setProgress(int);
     method @NonNull public android.app.Notification.ProgressStyle setProgressEndIcon(@Nullable android.graphics.drawable.Icon);
     method @NonNull public android.app.Notification.ProgressStyle setProgressIndeterminate(boolean);
+    method @NonNull public android.app.Notification.ProgressStyle setProgressPoints(@NonNull java.util.List<android.app.Notification.ProgressStyle.Point>);
     method @NonNull public android.app.Notification.ProgressStyle setProgressSegments(@NonNull java.util.List<android.app.Notification.ProgressStyle.Segment>);
     method @NonNull public android.app.Notification.ProgressStyle setProgressStartIcon(@Nullable android.graphics.drawable.Icon);
-    method @NonNull public android.app.Notification.ProgressStyle setProgressSteps(@NonNull java.util.List<android.app.Notification.ProgressStyle.Step>);
     method @NonNull public android.app.Notification.ProgressStyle setProgressTrackerIcon(@Nullable android.graphics.drawable.Icon);
     method @NonNull public android.app.Notification.ProgressStyle setStyledByProgress(boolean);
   }
 
+  public static final class Notification.ProgressStyle.Point {
+    ctor public Notification.ProgressStyle.Point(int);
+    method @ColorInt public int getColor();
+    method public int getId();
+    method public int getPosition();
+    method @NonNull public android.app.Notification.ProgressStyle.Point setColor(@ColorInt int);
+    method @NonNull public android.app.Notification.ProgressStyle.Point setId(int);
+  }
+
   public static final class Notification.ProgressStyle.Segment {
     ctor public Notification.ProgressStyle.Segment(int);
     method @ColorInt public int getColor();
@@ -6888,15 +6897,6 @@
     method @NonNull public android.app.Notification.ProgressStyle.Segment setId(int);
   }
 
-  public static final class Notification.ProgressStyle.Step {
-    ctor public Notification.ProgressStyle.Step(int);
-    method @ColorInt public int getColor();
-    method public int getId();
-    method public int getPosition();
-    method @NonNull public android.app.Notification.ProgressStyle.Step setColor(@ColorInt int);
-    method @NonNull public android.app.Notification.ProgressStyle.Step setId(int);
-  }
-
   public abstract static class Notification.Style {
     ctor @Deprecated public Notification.Style();
     method public android.app.Notification build();
@@ -8673,6 +8673,8 @@
     field public static final int TAG_MAX_SCREEN_LOCK_TIMEOUT_SET = 210019; // 0x33463
     field public static final int TAG_MEDIA_MOUNT = 210013; // 0x3345d
     field public static final int TAG_MEDIA_UNMOUNT = 210014; // 0x3345e
+    field @FlaggedApi("android.nfc.nfc_state_change_security_log_event_enabled") public static final int TAG_NFC_DISABLED = 210046; // 0x3347e
+    field @FlaggedApi("android.nfc.nfc_state_change_security_log_event_enabled") public static final int TAG_NFC_ENABLED = 210045; // 0x3347d
     field public static final int TAG_OS_SHUTDOWN = 210010; // 0x3345a
     field public static final int TAG_OS_STARTUP = 210009; // 0x33459
     field public static final int TAG_PACKAGE_INSTALLED = 210041; // 0x33479
@@ -8787,7 +8789,7 @@
   @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service {
     ctor public AppFunctionService();
     method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
-    method @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+    method @Deprecated @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
     method @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
     field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
   }
@@ -8822,13 +8824,12 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
     field public static final String PROPERTY_RETURN_VALUE = "returnValue";
     field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
-    field public static final int RESULT_CANCELLED = 7; // 0x7
+    field public static final int RESULT_CANCELLED = 6; // 0x6
     field public static final int RESULT_DENIED = 1; // 0x1
-    field public static final int RESULT_DISABLED = 6; // 0x6
+    field public static final int RESULT_DISABLED = 5; // 0x5
     field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
     field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
     field public static final int RESULT_OK = 0; // 0x0
-    field public static final int RESULT_TIMED_OUT = 5; // 0x5
   }
 
 }
@@ -36993,7 +36994,7 @@
   }
 
   @FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount {
-    ctor public ContactsContract.RawContacts.DefaultAccount();
+    method @FlaggedApi("android.provider.new_default_account_api_enabled") @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState getDefaultAccountForNewContacts(@NonNull android.content.ContentResolver);
   }
 
   @FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState {
@@ -37433,6 +37434,7 @@
     field public static final String ACTION_APPLICATION_SETTINGS = "android.settings.APPLICATION_SETTINGS";
     field public static final String ACTION_APP_LOCALE_SETTINGS = "android.settings.APP_LOCALE_SETTINGS";
     field public static final String ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS = "android.settings.APP_NOTIFICATION_BUBBLE_SETTINGS";
+    field @FlaggedApi("android.app.api_rich_ongoing") public static final String ACTION_APP_NOTIFICATION_PROMOTION_SETTINGS = "android.settings.APP_NOTIFICATION_PROMOTION_SETTINGS";
     field public static final String ACTION_APP_NOTIFICATION_SETTINGS = "android.settings.APP_NOTIFICATION_SETTINGS";
     field public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS = "android.settings.APP_OPEN_BY_DEFAULT_SETTINGS";
     field public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS";
@@ -61656,6 +61658,7 @@
     method public void unregisterOnBackInvokedCallback(@NonNull android.window.OnBackInvokedCallback);
     field public static final int PRIORITY_DEFAULT = 0; // 0x0
     field public static final int PRIORITY_OVERLAY = 1000000; // 0xf4240
+    field @FlaggedApi("com.android.window.flags.predictive_back_priority_system_navigation_observer") public static final int PRIORITY_SYSTEM_NAVIGATION_OBSERVER = -2; // 0xfffffffe
   }
 
   public interface SplashScreen {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 4b6c62e..7e43e46 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -11950,6 +11950,10 @@
     field @Deprecated public static final String STATE = "state";
   }
 
+  @FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount {
+    method @FlaggedApi("android.provider.new_default_account_api_enabled") @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS) public static void setDefaultAccountForNewContacts(@NonNull android.content.ContentResolver, @NonNull android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState);
+  }
+
   public static final class ContactsContract.Settings implements android.provider.ContactsContract.SettingsColumns {
     method @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS) public static void setDefaultAccount(@NonNull android.content.ContentResolver, @Nullable android.accounts.Account);
   }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5b556cc..95d3ea5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1960,8 +1960,12 @@
 
         @Override
         public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) {
-            PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
-            IoUtils.closeQuietly(pfd);
+            try {
+                PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
+                BroadcastStickyCache.dump(pfd);
+            } finally {
+                IoUtils.closeQuietly(pfd);
+            }
         }
 
         private File getDatabasesDir(Context context) {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index f27dc32..5907af0 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -10153,6 +10153,9 @@
         }
 
         p.writeInt(Parcel.EX_HAS_NOTED_APPOPS_REPLY_HEADER);
+        final int sizePosition = p.dataPosition();
+        // Write size placeholder. With this size we can easily skip it in native.
+        p.writeInt(0);
 
         int numAttributionWithNotesAppOps = notedAppOps.size();
         p.writeInt(numAttributionWithNotesAppOps);
@@ -10169,6 +10172,12 @@
                 }
             }
         }
+
+        final int payloadPosition = p.dataPosition();
+        p.setDataPosition(sizePosition);
+        // Total header size including 4 bytes size itself.
+        p.writeInt(payloadPosition - sizePosition);
+        p.setDataPosition(payloadPosition);
     }
 
     /**
@@ -10182,6 +10191,8 @@
      * @hide
      */
     public static void readAndLogNotedAppops(@NonNull Parcel p) {
+        // Skip size.
+        p.readInt();
         int numAttributionsWithNotedAppOps = p.readInt();
 
         for (int i = 0; i < numAttributionsWithNotedAppOps; i++) {
diff --git a/core/java/android/app/BroadcastStickyCache.java b/core/java/android/app/BroadcastStickyCache.java
index d6f061b..ea81731 100644
--- a/core/java/android/app/BroadcastStickyCache.java
+++ b/core/java/android/app/BroadcastStickyCache.java
@@ -27,16 +27,21 @@
 import android.net.nsd.NsdManager;
 import android.net.wifi.WifiManager;
 import android.net.wifi.p2p.WifiP2pManager;
+import android.os.ParcelFileDescriptor;
 import android.os.SystemProperties;
 import android.os.UpdateLock;
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
 import android.view.WindowManagerPolicyConstants;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastPrintWriter;
 
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 
 /** @hide */
@@ -214,6 +219,41 @@
         }
     }
 
+    public static void dump(@NonNull ParcelFileDescriptor pfd) {
+        if (!Flags.useStickyBcastCache()) {
+            return;
+        }
+        final PrintWriter pw = new FastPrintWriter(new FileOutputStream(pfd.getFileDescriptor()));
+        synchronized (sCachedStickyBroadcasts) {
+            dumpLocked(pw);
+        }
+        pw.flush();
+    }
+
+    @GuardedBy("sCachedStickyBroadcasts")
+    private static void dumpLocked(@NonNull PrintWriter pw) {
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(
+                pw, "  " /* singleIndent */, "  " /* prefix */);
+        ipw.println("Cached sticky broadcasts:");
+        ipw.increaseIndent();
+        final int count = sCachedStickyBroadcasts.size();
+        if (count == 0) {
+            ipw.println("<empty>");
+        } else {
+            for (int i = 0; i < count; ++i) {
+                final CachedStickyBroadcast cachedStickyBroadcast = sCachedStickyBroadcasts.get(i);
+                ipw.print("Entry #"); ipw.print(i); ipw.println(":");
+                ipw.increaseIndent();
+                ipw.print("filter="); ipw.println(cachedStickyBroadcast.filter.toLongString());
+                ipw.print("intent="); ipw.println(cachedStickyBroadcast.intent);
+                ipw.print("version="); ipw.println(cachedStickyBroadcast.version);
+                ipw.print("handle="); ipw.println(cachedStickyBroadcast.propertyHandle);
+                ipw.decreaseIndent();
+            }
+        }
+        ipw.decreaseIndent();
+    }
+
     private static final class CachedStickyBroadcast {
         @NonNull public final IntentFilter filter;
         @Nullable public Intent intent;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 34d0f3b..38632bd 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -809,6 +809,10 @@
         return false;
     }
 
+    private static boolean isStandardLayout(int layoutId) {
+        return STANDARD_LAYOUTS.contains(layoutId);
+    }
+
     /** @hide */
     @IntDef(flag = true, prefix = {"FLAG_"}, value = {
             FLAG_SHOW_LIGHTS,
@@ -1637,16 +1641,16 @@
     public static final String EXTRA_PROGRESS_SEGMENTS = "android.progressSegments";
 
     /**
-     * {@link #extras} key: an arraylist of {@link android.app.Notification.ProgressStyle.Step}
+     * {@link #extras} key: an arraylist of {@link ProgressStyle.Point}
      * bundles provided by a
      * {@link android.app.Notification.ProgressStyle} notification as supplied to
-     * {@link ProgressStyle#setProgressSteps}
-     * or {@link ProgressStyle#addProgressStep(ProgressStyle.Step)}.
+     * {@link ProgressStyle#setProgressPoints}
+     * or {@link ProgressStyle#addProgressPoint(ProgressStyle.Point)}.
      * This extra is a parcelable array list of bundles.
      * @hide
      */
     @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
-    public static final String EXTRA_PROGRESS_STEPS = "android.progressSteps";
+    public static final String EXTRA_PROGRESS_POINTS = "android.progressPoints";
 
     /**
      * {@link #extras} key: whether the progress bar should be styled by its progress as
@@ -5983,9 +5987,9 @@
                 }
             }
             boolean contentViewUsesHeader = mN.contentView == null
-                    || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId());
+                    || isStandardLayout(mN.contentView.getLayoutId());
             boolean bigContentViewUsesHeader = mN.bigContentView == null
-                    || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId());
+                    || isStandardLayout(mN.bigContentView.getLayoutId());
             return contentViewUsesHeader && bigContentViewUsesHeader;
         }
 
@@ -6781,7 +6785,7 @@
                 return false;
             }
             if (fullyCustomViewRequiresDecoration(false)
-                    && STANDARD_LAYOUTS.contains(customContent.getLayoutId())) {
+                    && isStandardLayout(customContent.getLayoutId())) {
                 // If the app's custom views are objects returned from Builder.create*ContentView()
                 // then the app is most likely attempting to spoof the user.  Even if they are not,
                 // the result would be broken (b/189189308) so we will ignore it.
@@ -11159,7 +11163,7 @@
 
     /**
      * A Notification Style used to to define a notification whose expanded state includes
-     * a highly customizable progress bar with segments, steps, a custom tracker icon,
+     * a highly customizable progress bar with segments, points, a custom tracker icon,
      * and custom icons at the start and end of the progress bar.
      *
      * This style is suggested for use cases where the app is showing a tracker to the
@@ -11185,8 +11189,8 @@
      *       .addProgressSegment(new Segment(552).setColor(Color.YELLOW))
      *       .addProgressSegment(new Segment(253).setColor(Color.YELLOW))
      *       .addProgressSegment(new Segment(94).setColor(Color.BLUE))
-     *       .addProgressStep(new Step(60).setColor(Color.RED))
-     *       .addProgressStep(new Step(560).setColor(Color.YELLOW))
+     *       .addProgressPoint(new Point(60).setColor(Color.RED))
+     *       .addProgressPoint(new Point(560).setColor(Color.YELLOW))
      *   )
      * </pre>
      *
@@ -11202,14 +11206,14 @@
         private static final String KEY_ELEMENT_ID = "id";
         private static final String KEY_ELEMENT_COLOR = "colorInt";
         private static final String KEY_SEGMENT_LENGTH = "length";
-        private static final String KEY_STEP_POSITION = "position";
+        private static final String KEY_POINT_POSITION = "position";
 
         private static final int MAX_PROGRESS_SEGMENT_LIMIT = 15;
-        private static final int MAX_PROGRESS_STEP_LIMIT = 5;
+        private static final int MAX_PROGRESS_STOP_LIMIT = 5;
         private static final int DEFAULT_PROGRESS_MAX = 100;
 
         private List<Segment> mProgressSegments = new ArrayList<>();
-        private List<Step> mProgressSteps = new ArrayList<>();
+        private List<Point> mProgressPoints = new ArrayList<>();
 
         private int mProgress = 0;
 
@@ -11246,7 +11250,7 @@
                 nonIndeterminateCheckResult = !Objects.equals(mProgress, progressStyle.mProgress)
                         || !Objects.equals(mIsStyledByProgress, progressStyle.mIsStyledByProgress)
                         || !Objects.equals(mProgressSegments, progressStyle.mProgressSegments)
-                        || !Objects.equals(mProgressSteps, progressStyle.mProgressSteps)
+                        || !Objects.equals(mProgressPoints, progressStyle.mProgressPoints)
                         || !Objects.equals(mTrackerIcon, progressStyle.mTrackerIcon);
             }
 
@@ -11300,48 +11304,47 @@
         }
 
         /**
-         * Gets the steps that are displayed on the progress bar.
+         * Gets the points that are displayed on the progress bar.
          *.
-         * @see #setProgressSteps
-         * @see #addProgressStep
-         * @see Step
+         * @see #setProgressPoints
+         * @see #addProgressPoint
+         * @see Point
          */
-        public @NonNull List<Step> getProgressSteps() {
-            return mProgressSteps;
+        public @NonNull List<Point> getProgressPoints() {
+            return mProgressPoints;
         }
 
         /**
-         * Replaces all the progress steps.
+         * Replaces all the progress points.
          *
-         * Steps are designated points within a progressbar to visualize
-         * distinct stages or milestones.
-         * For example, you might use steps to mark stops in a multi-stop
-         * navigation journey, where each step represents a destination.
-         * @see Step
+         * Points within a progress bar are used to visualize distinct stages or milestones.
+         * For example, you might use points to mark stops in a multi-stop
+         * navigation journey, where each point represents a destination.
+         * @see Point
          */
-        public @NonNull ProgressStyle setProgressSteps(@NonNull List<Step> steps) {
-            mProgressSteps = new ArrayList<>(steps);
+        public @NonNull ProgressStyle setProgressPoints(@NonNull List<Point> points) {
+            mProgressPoints = new ArrayList<>(points);
             return this;
         }
 
         /**
-         * Adds another step.
+         * Adds another point.
          *
-         * Steps are designated points within a progressbar to visualize
-         * distinct stages or milestones.
-         * For example, you might use steps to mark stops in a multi-stop
-         * navigation journey, where each step represents a destination.
+         * Points within a progress bar are used to visualize distinct stages or milestones.
          *
-         * Steps can be added in any order, as their
+         * For example, you might use points to mark stops in a multi-stop
+         * navigation journey, where each point represents a destination.
+         *
+         * Points can be added in any order, as their
          * position within the progress bar is determined by their individual
-         * {@link Step#getPosition()}.
-         * @see Step
+         * {@link Point#getPosition()}.
+         * @see Point
          */
-        public @NonNull ProgressStyle addProgressStep(@NonNull Step step) {
-            if (mProgressSteps == null) {
-                mProgressSteps = new ArrayList<>();
+        public @NonNull ProgressStyle addProgressPoint(@NonNull Point point) {
+            if (mProgressPoints == null) {
+                mProgressPoints = new ArrayList<>();
             }
-            mProgressSteps.add(step);
+            mProgressPoints.add(point);
 
             return this;
         }
@@ -11414,7 +11417,7 @@
          * When specified, the following fields are ignored:
          * @see #setProgress
          * @see #setProgressSegments
-         * @see #setProgressSteps
+         * @see #setProgressPoints
          * @see #setProgressTrackerIcon
          * @see #setStyledByProgress
          *
@@ -11435,7 +11438,7 @@
         }
 
         /**
-         * Indicates whether the segments and steps will be styled differently
+         * Indicates whether the segments and points will be styled differently
          * based on whether they are behind or ahead of the current progress.
          * When true, segments appearing ahead of the current progress will be given a
          * slightly different appearance to indicate that it is part of the progress bar
@@ -11558,8 +11561,8 @@
             super.addExtras(extras);
             extras.putParcelableArrayList(EXTRA_PROGRESS_SEGMENTS,
                     getProgressSegmentsAsBundleList(mProgressSegments));
-            extras.putParcelableArrayList(EXTRA_PROGRESS_STEPS,
-                    getProgressStepsAsBundleList(mProgressSteps));
+            extras.putParcelableArrayList(EXTRA_PROGRESS_POINTS,
+                    getProgressPointsAsBundleList(mProgressPoints));
 
             extras.putInt(EXTRA_PROGRESS, mProgress);
             extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mIndeterminate);
@@ -11599,8 +11602,8 @@
             mTrackerIcon = extras.getParcelable(EXTRA_PROGRESS_TRACKER_ICON, Icon.class);
             mStartIcon = extras.getParcelable(EXTRA_PROGRESS_START_ICON, Icon.class);
             mEndIcon = extras.getParcelable(EXTRA_PROGRESS_END_ICON, Icon.class);
-            mProgressSteps = getProgressStepsFromBundleList(
-                    extras.getParcelableArrayList(EXTRA_PROGRESS_STEPS, Bundle.class));
+            mProgressPoints = getProgressPointsFromBundleList(
+                    extras.getParcelableArrayList(EXTRA_PROGRESS_POINTS, Bundle.class));
         }
 
         /**
@@ -11613,6 +11616,30 @@
             // actually be included.
             return true;
         }
+        /**
+         * @hide
+         */
+        @Override
+        public RemoteViews makeContentView(boolean increasedHeight) {
+            final StandardTemplateParams p = mBuilder.mParams.reset()
+                    .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
+                    .hideProgress(true)
+                    .fillTextsFrom(mBuilder);
+
+            return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */);
+        }
+        /**
+         * @hide
+         */
+        @Override
+        public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+            final StandardTemplateParams p = mBuilder.mParams.reset()
+                    .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
+                    .hideProgress(true)
+                    .fillTextsFrom(mBuilder);
+
+            return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */);
+        }
 
         private static @NonNull ArrayList<Bundle> getProgressSegmentsAsBundleList(
                 @Nullable List<Segment> progressSegments) {
@@ -11660,48 +11687,48 @@
             return segments;
         }
 
-        private static @NonNull ArrayList<Bundle> getProgressStepsAsBundleList(
-                @Nullable List<Step> progressSteps) {
-            final ArrayList<Bundle> steps = new ArrayList<>();
-            if (progressSteps != null && !progressSteps.isEmpty()) {
-                for (int i = 0; i < progressSteps.size(); i++) {
-                    final Step step = progressSteps.get(i);
-                    if (step.getPosition() < 0) {
+        private static @NonNull ArrayList<Bundle> getProgressPointsAsBundleList(
+                @Nullable List<Point> progressPoints) {
+            final ArrayList<Bundle> points = new ArrayList<>();
+            if (progressPoints != null && !progressPoints.isEmpty()) {
+                for (int i = 0; i < progressPoints.size(); i++) {
+                    final Point point = progressPoints.get(i);
+                    if (point.getPosition() < 0) {
                         continue;
                     }
 
                     final Bundle bundle = new Bundle();
-                    bundle.putInt(KEY_STEP_POSITION, step.getPosition());
-                    bundle.putInt(KEY_ELEMENT_ID, step.getId());
-                    bundle.putInt(KEY_ELEMENT_COLOR, step.getColor());
+                    bundle.putInt(KEY_POINT_POSITION, point.getPosition());
+                    bundle.putInt(KEY_ELEMENT_ID, point.getId());
+                    bundle.putInt(KEY_ELEMENT_COLOR, point.getColor());
 
-                    steps.add(bundle);
+                    points.add(bundle);
                 }
             }
 
-            return steps;
+            return points;
         }
 
-        private static @NonNull List<Step> getProgressStepsFromBundleList(
-                @Nullable List<Bundle> stepBundleList) {
-            final ArrayList<Step> steps = new ArrayList<>();
+        private static @NonNull List<Point> getProgressPointsFromBundleList(
+                @Nullable List<Bundle> pointBundleList) {
+            final ArrayList<Point> points = new ArrayList<>();
 
-            if (stepBundleList != null && !stepBundleList.isEmpty()) {
-                for (int i = 0; i < stepBundleList.size(); i++) {
-                    final Bundle segmentBundle = stepBundleList.get(i);
-                    final int position = segmentBundle.getInt(KEY_STEP_POSITION);
+            if (pointBundleList != null && !pointBundleList.isEmpty()) {
+                for (int i = 0; i < pointBundleList.size(); i++) {
+                    final Bundle pointBundle = pointBundleList.get(i);
+                    final int position = pointBundle.getInt(KEY_POINT_POSITION);
                     if (position < 0) {
                         continue;
                     }
-                    final int id = segmentBundle.getInt(KEY_ELEMENT_ID);
-                    final int color = segmentBundle.getInt(KEY_ELEMENT_COLOR,
+                    final int id = pointBundle.getInt(KEY_ELEMENT_ID);
+                    final int color = pointBundle.getInt(KEY_ELEMENT_COLOR,
                             Notification.COLOR_DEFAULT);
-                    final Step step = new Step(position).setId(id).setColor(color);
-                    steps.add(step);
+                    final Point point = new Point(position).setId(id).setColor(color);
+                    points.add(point);
                 }
             }
 
-            return steps;
+            return points;
         }
 
         /**
@@ -11776,7 +11803,7 @@
             public boolean equals(Object o) {
                 if (this == o) return true;
                 if (o == null || getClass() != o.getClass()) return false;
-                Segment segment = (Segment) o;
+                final Segment segment = (Segment) o;
                 return mLength == segment.mLength && mId == segment.mId
                         && mColor == segment.mColor;
             }
@@ -11788,13 +11815,12 @@
         }
 
         /**
-         * A step within the progress bar, defining its position and color.
-         * Steps are designated points within a progressbar to visualize
-         * distinct stages or milestones.
-         * For example, you might use steps to mark stops in a multi-stop
-         * navigation journey, where each step represents a destination.
+         * A point within the progress bar, defining its position and color.
+         * Points within a progress bar are used to visualize distinct stages or milestones.
+         * For example, you might use points to mark stops in a multi-stop
+         * navigation journey, where each point represents a destination.
          */
-        public static final class Step {
+        public static final class Point {
 
             private int mPosition;
             private int mId;
@@ -11802,19 +11828,19 @@
             private int mColor = Notification.COLOR_DEFAULT;
 
             /**
-             * Create a step element.
-             * The position of this step on the progress bar
+             * Create a point element.
+             * The position of this point on the progress bar
              * relative to {@link ProgressStyle#getProgressMax}
              * @param position
              * See {@link #getPosition}
              */
-            public Step(int position) {
+            public Point(int position) {
                 mPosition = position;
             }
 
             /**
-             * Gets the position of this Step.
-             * The position of this step on the progress bar
+             * Gets the position of this Point.
+             * The position of this point on the progress bar
              * relative to {@link ProgressStyle#getProgressMax}.
              */
             public int getPosition() {
@@ -11832,7 +11858,7 @@
             /**
              * Optional ID used to uniquely identify the element across updates.
              */
-            public @NonNull Step setId(int id) {
+            public @NonNull Point setId(int id) {
                 mId = id;
                 return this;
             }
@@ -11850,7 +11876,7 @@
             /**
              * Optional color of this Segment
              */
-            public @NonNull Step setColor(@ColorInt int color) {
+            public @NonNull Point setColor(@ColorInt int color) {
                 mColor = color;
                 return this;
             }
@@ -11862,9 +11888,9 @@
             public boolean equals(Object o) {
                 if (this == o) return true;
                 if (o == null || getClass() != o.getClass()) return false;
-                Step step = (Step) o;
-                return mPosition == step.mPosition && mId == step.mId
-                        && mColor == step.mColor;
+                final Point point = (Point) o;
+                return mPosition == point.mPosition && mId == point.mId
+                        && mColor == point.mColor;
             }
 
             @Override
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index dfed1f7..41abd68 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -958,6 +958,9 @@
      * Returns whether the calling app's properly formatted notifications can appear in a promoted
      * format, which may result in higher ranking, appearances on additional surfaces, and richer
      * presentation.
+     *
+     * Apps can request this permission by sending the user to the activity that matches the system
+     * intent action {@link android.provider.Settings#ACTION_APP_NOTIFICATION_PROMOTION_SETTINGS}.
      */
     @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
     public boolean canPostPromotedNotifications() {
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 84a4eb4..a458b4e 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -431,16 +431,19 @@
     }
 
     /**
-     * Protected so that tests can override and returns something a fixed value.
+     * public so that tests can access and override
      */
     @VisibleForTesting
-    protected @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) {
+    public @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) {
         final DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance();
         final DisplayMetrics dm = new DisplayMetrics();
         final DisplayInfo displayInfo = displayManagerGlobal != null
                 ? displayManagerGlobal.getDisplayInfo(displayId) : null;
         if (displayInfo != null) {
-            displayInfo.getAppMetrics(dm, da);
+            final Configuration dajConfig = da.getConfiguration();
+            displayInfo.getAppMetrics(dm, da.getCompatibilityInfo(),
+                    (mResDisplayId == displayId && Configuration.EMPTY.equals(dajConfig))
+                            ? mResConfiguration : dajConfig);
         } else {
             dm.setToDefaults();
         }
@@ -1977,6 +1980,7 @@
     public void registerAllResourcesReference(@NonNull Resources resources) {
         if (android.content.res.Flags.registerResourcePaths()) {
             synchronized (mLock) {
+                cleanupReferences(mAllResourceReferences, mAllResourceReferencesQueue);
                 mAllResourceReferences.add(
                         new WeakReference<>(resources, mAllResourceReferencesQueue));
             }
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index af242dd..e882bb5 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -275,7 +275,10 @@
     public int parentTaskId;
 
     /**
-     * Whether this task is focused.
+     * Whether this task is focused on the display. This means the task receives input events that
+     * target the display.
+     * CAUTION: This can be true for multiple tasks especially when multiple displays are connected
+     * in the system.
      * @hide
      */
     public boolean isFocused;
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 7903f1c..2e6f3e1 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import static android.app.Flags.enableCurrentModeTypeBinderCache;
 import static android.app.Flags.enableNightModeBinderCache;
 
 import android.annotation.CallbackExecutor;
@@ -682,6 +683,53 @@
         }
     }
 
+    private Integer getCurrentModeTypeFromServer() {
+        try {
+            if (sGlobals != null) {
+                return sGlobals.mService.getCurrentModeType();
+            }
+            return Configuration.UI_MODE_TYPE_NORMAL;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Retrieve the current running mode type for the user.
+     */
+    private final IpcDataCache.QueryHandler<Void, Integer> mCurrentModeTypeQuery =
+            new IpcDataCache.QueryHandler<>() {
+
+                @Override
+                @NonNull
+                public Integer apply(Void query) {
+                    return getCurrentModeTypeFromServer();
+                }
+            };
+
+    private static final String CURRENT_MODE_TYPE_API = "getCurrentModeType";
+
+    /**
+     * Cache the current running mode type for a user.
+     */
+    private final IpcDataCache<Void, Integer> mCurrentModeTypeCache =
+            new IpcDataCache<>(1, IpcDataCache.MODULE_SYSTEM,
+                    CURRENT_MODE_TYPE_API, /* cacheName= */ "CurrentModeTypeCache",
+                    mCurrentModeTypeQuery);
+
+    /**
+     * Invalidate the current mode type cache.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_CURRENT_MODE_TYPE_BINDER_CACHE)
+    public static void invalidateCurrentModeTypeCache() {
+        IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
+                CURRENT_MODE_TYPE_API);
+    }
+
+
     /**
      * Return the current running mode type.  May be one of
      * {@link Configuration#UI_MODE_TYPE_NORMAL Configuration.UI_MODE_TYPE_NORMAL},
@@ -693,14 +741,11 @@
      * {@link Configuration#UI_MODE_TYPE_VR_HEADSET Configuration.UI_MODE_TYPE_VR_HEADSET}.
      */
     public int getCurrentModeType() {
-        if (sGlobals != null) {
-            try {
-                return sGlobals.mService.getCurrentModeType();
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
+        if (enableCurrentModeTypeBinderCache()) {
+            return mCurrentModeTypeCache.query(null);
+        } else {
+            return getCurrentModeTypeFromServer();
         }
-        return Configuration.UI_MODE_TYPE_NORMAL;
     }
 
     /**
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index beb93fd..eb0ea1e 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -16,7 +16,10 @@
 
 package android.app.admin;
 
+import static android.nfc.Flags.FLAG_NFC_STATE_CHANGE_SECURITY_LOG_EVENT_ENABLED;
+
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -100,6 +103,8 @@
             TAG_PACKAGE_UPDATED,
             TAG_PACKAGE_UNINSTALLED,
             TAG_BACKUP_SERVICE_TOGGLED,
+            TAG_NFC_ENABLED,
+            TAG_NFC_DISABLED,
     })
     public @interface SecurityLogTag {}
 
@@ -610,6 +615,18 @@
      */
     public static final int TAG_BACKUP_SERVICE_TOGGLED =
             SecurityLogTags.SECURITY_BACKUP_SERVICE_TOGGLED;
+
+    /**
+     * Indicates that NFC service is enabled. There is no extra payload in the log event.
+     */
+    @FlaggedApi(FLAG_NFC_STATE_CHANGE_SECURITY_LOG_EVENT_ENABLED)
+    public static final int TAG_NFC_ENABLED = SecurityLogTags.SECURITY_NFC_ENABLED;
+
+    /**
+     * Indicates that NFC service is disabled. There is no extra payload in the log event.
+     */
+    @FlaggedApi(FLAG_NFC_STATE_CHANGE_SECURITY_LOG_EVENT_ENABLED)
+    public static final int TAG_NFC_DISABLED = SecurityLogTags.SECURITY_NFC_DISABLED;
     /**
      * Event severity level indicating that the event corresponds to normal workflow.
      */
diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags
index 7b3aa7b..8f22c76 100644
--- a/core/java/android/app/admin/SecurityLogTags.logtags
+++ b/core/java/android/app/admin/SecurityLogTags.logtags
@@ -48,4 +48,6 @@
 210041 security_package_installed               (package_name|3),(version_code|1),(user_id|1)
 210042 security_package_updated                 (package_name|3),(version_code|1),(user_id|1)
 210043 security_package_uninstalled             (package_name|3),(version_code|1),(user_id|1)
-210044 security_backup_service_toggled          (package|3),(admin_user|1),(enabled|1)
\ No newline at end of file
+210044 security_backup_service_toggled          (package|3),(admin_user|1),(enabled|1)
+210045 security_nfc_enabled
+210046 security_nfc_disabled
\ No newline at end of file
diff --git a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
index fe2db49..64dece9 100644
--- a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
+++ b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
@@ -16,6 +16,8 @@
 
 package android.app.appfunctions;
 
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DEFAULT;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED;
 import static android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID;
 import static android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_ENABLED;
 import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_INDEXER_PACKAGE;
@@ -166,15 +168,18 @@
             if (runtimeMetadataResults.isEmpty()) {
                 throw new IllegalArgumentException("App function not found.");
             }
-            boolean[] enabled =
+            long enabled =
                     runtimeMetadataResults
                             .getFirst()
                             .getGenericDocument()
-                            .getPropertyBooleanArray(PROPERTY_ENABLED);
-            if (enabled != null && enabled.length != 0) {
-                return enabled[0];
+                            .getPropertyLong(PROPERTY_ENABLED);
+            // If enabled is not equal to APP_FUNCTION_STATE_DEFAULT, it means it IS overridden and
+            // we should return the overridden value.
+            if (enabled != APP_FUNCTION_STATE_DEFAULT) {
+                return enabled == APP_FUNCTION_STATE_ENABLED;
             }
-            // Runtime metadata not found. Using the default value in the static metadata.
+            // Runtime metadata not found or enabled is equal to APP_FUNCTION_STATE_DEFAULT.
+            // Using the default value in the static metadata.
             return joinedStaticRuntimeResults
                     .getFirst()
                     .getGenericDocument()
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
index 8b7f326..08ecced 100644
--- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -16,11 +16,15 @@
 
 package android.app.appfunctions;
 
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DEFAULT;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DISABLED;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED;
 import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.appfunctions.AppFunctionManager.EnabledState;
 import android.app.appsearch.AppSearchSchema;
 import android.app.appsearch.GenericDocument;
 
@@ -162,15 +166,13 @@
      * Returns if the function is set to be enabled or not. If not set, the {@link
      * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT} value would be used.
      */
-    @Nullable
-    public Boolean getEnabled() {
-        // We can't use getPropertyBoolean here. getPropertyBoolean returns false instead of null
-        // if the value is missing.
-        boolean[] enabled = getPropertyBooleanArray(PROPERTY_ENABLED);
-        if (enabled == null || enabled.length == 0) {
-            return null;
-        }
-        return enabled[0];
+    @EnabledState
+    public int getEnabled() {
+        // getPropertyLong returns the first long associated with the given path or default value 0
+        // if there is no such value or the value is of a different type.
+        // APP_FUNCTION_STATE_DEFAULT also equals 0 which means the returned value will be 0 when an
+        // app as either never changed the enabled bit at runtime or has reset it to the default.
+        return (int) getPropertyLong(PROPERTY_ENABLED);
     }
 
     /** Returns the qualified id linking to the static metadata of the app function. */
@@ -217,12 +219,14 @@
          * TODO(369683073) Replace the tristate Boolean with IntDef EnabledState.
          */
         @NonNull
-        public Builder setEnabled(@Nullable Boolean enabled) {
-            if (enabled == null) {
-                setPropertyBoolean(PROPERTY_ENABLED);
-            } else {
-                setPropertyBoolean(PROPERTY_ENABLED, enabled);
+        public Builder setEnabled(@EnabledState int enabledState) {
+            if (enabledState != APP_FUNCTION_STATE_DEFAULT
+                    && enabledState != APP_FUNCTION_STATE_ENABLED
+                    && enabledState != APP_FUNCTION_STATE_DISABLED) {
+                throw new IllegalArgumentException(
+                        "Value of EnabledState is unsupported.");
             }
+            setPropertyLong(PROPERTY_ENABLED, enabledState);
             return this;
         }
 
diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
index 8e41773..7a68a65 100644
--- a/core/java/android/app/appfunctions/AppFunctionService.java
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -35,6 +35,7 @@
 import android.os.CancellationSignal;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.util.Log;
 
 import java.util.function.Consumer;
 
@@ -166,9 +167,13 @@
      */
     @MainThread
     @Deprecated
-    public abstract void onExecuteFunction(
+    public void onExecuteFunction(
             @NonNull ExecuteAppFunctionRequest request,
-            @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+            @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+        Log.w(
+                "AppFunctionService",
+                "Calling deprecated default implementation of onExecuteFunction");
+    }
 
     /**
      * Called by the system to execute a specific app function.
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index 2851e92..a879b1b 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -96,17 +96,14 @@
      */
     public static final int RESULT_INVALID_ARGUMENT = 4;
 
-    /** The operation was timed out. */
-    public static final int RESULT_TIMED_OUT = 5;
-
     /** The caller tried to execute a disabled app function. */
-    public static final int RESULT_DISABLED = 6;
+    public static final int RESULT_DISABLED = 5;
 
     /**
      * The operation was cancelled. Use this error code to report that a cancellation is done after
      * receiving a cancellation signal.
      */
-    public static final int RESULT_CANCELLED = 7;
+    public static final int RESULT_CANCELLED = 6;
 
     /** The result code of the app function execution. */
     @ResultCode private final int mResultCode;
@@ -282,7 +279,6 @@
                 RESULT_APP_UNKNOWN_ERROR,
                 RESULT_INTERNAL_ERROR,
                 RESULT_INVALID_ARGUMENT,
-                RESULT_TIMED_OUT,
                 RESULT_DISABLED,
                 RESULT_CANCELLED
             })
diff --git a/core/java/android/app/appfunctions/OWNERS b/core/java/android/app/appfunctions/OWNERS
index c6827cc..6a69e15 100644
--- a/core/java/android/app/appfunctions/OWNERS
+++ b/core/java/android/app/appfunctions/OWNERS
@@ -4,3 +4,4 @@
 tonymak@google.com
 mingweiliao@google.com
 anothermark@google.com
+utkarshnigam@google.com
diff --git a/core/java/android/app/appfunctions/TEST_MAPPING b/core/java/android/app/appfunctions/TEST_MAPPING
index 91e82ec..27517c8 100644
--- a/core/java/android/app/appfunctions/TEST_MAPPING
+++ b/core/java/android/app/appfunctions/TEST_MAPPING
@@ -1,10 +1,7 @@
 {
-  "postsubmit": [
+  "imports": [
     {
-      "name": "FrameworksAppFunctionsTests"
-    },
-    {
-      "name": "CtsAppFunctionTestCases"
+      "path": "frameworks/base/services/appfunctions/TEST_MAPPING"
     }
   ]
 }
\ No newline at end of file
diff --git a/core/java/android/app/ui_mode_manager.aconfig b/core/java/android/app/ui_mode_manager.aconfig
index 9f44a4d..05b46e0 100644
--- a/core/java/android/app/ui_mode_manager.aconfig
+++ b/core/java/android/app/ui_mode_manager.aconfig
@@ -9,4 +9,15 @@
      metadata {
          purpose: PURPOSE_BUGFIX
      }
+}
+
+flag {
+     namespace: "systemui"
+     name: "enable_current_mode_type_binder_cache"
+     description: "Enables the use of binder caching for current running mode type."
+     bug: "362572732"
+     is_fixed_read_only: true
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index d5edc92..cf65539 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -456,7 +456,6 @@
   }
 }
 
-
 flag {
     name: "caching_development_improvements"
     namespace: "multiuser"
@@ -464,3 +463,13 @@
     bug: "364947162"
     is_fixed_read_only: true
 }
+
+flag {
+  name: "show_custom_unlock_title_inside_private_profile"
+  namespace: "profile_experiences"
+  description: "When private space is unlocked show dynamic title in unlock factor screens based on lock factor set for the profile"
+  bug: "323835257"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index b9eba9c..ce8661e 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -1028,6 +1028,9 @@
                     // Camera is already closed, so nothing left to do
                     if (DEBUG) Log.v(TAG, mIdString +
                             "Camera was already closed or busy, skipping unconfigure");
+                } catch (SecurityException e) {
+                    // UID state change revoked camera permission
+                    Log.e(TAG, mIdString + "Exception while unconfiguring outputs: ", e);
                 }
             }
         }
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index e598097..36e816a 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -455,6 +455,11 @@
     public abstract void onPresentation(int displayId, boolean isShown);
 
     /**
+     * Called upon the usage of stylus.
+     */
+    public abstract void stylusGestureStarted(long eventTime);
+
+    /**
      * Describes the requested power state of the display.
      *
      * This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
index 51024ba..6a39365 100644
--- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
@@ -43,3 +43,10 @@
     description: "Enable usb state update based on udc sysfs"
     bug: "339241080"
 }
+
+flag {
+    name: "enable_usb_data_signal_staking_internal"
+    namespace: "preload_safety"
+    description: "Enables signal API with staking for internal local service callers"
+    bug: "369382558"
+}
\ No newline at end of file
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index b9b5295..c41e626 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -3088,8 +3088,9 @@
 
     public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] {
             "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf", "Ecn",
-            "Eac", "Epi", "Epu", "Eal", "Est", "Eai", "Eaa", "Etw",
-            "Esw", "Ewa", "Elw", "Eec", "Esc", "Eds"
+            "Eac", "Epi", "Epu", "Eal", "Est", "Eai", "Eaa",
+            "Etw", "Esw", "Ewa", "Elw", "Esc",
+            "Eds"
     };
 
     @FunctionalInterface
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 80546cd..3b5a99e 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -493,6 +493,11 @@
     public native boolean pingBinder();
 
     /**
+     * Check to see if the process that the binder is in is still alive.
+     *
+     * Note, this only reflects the last known death state, if the object
+     * is linked to death or has made a transactions since the death occurs.
+     *
      * @return false if the hosting process is gone
      */
     public native boolean isBinderAlive();
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 461f1e0..3ae9511 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -69,6 +69,8 @@
 import android.view.WindowManager.LayoutParams;
 
 import com.android.internal.R;
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -90,6 +92,7 @@
  */
 @SystemService(Context.USER_SERVICE)
 @android.ravenwood.annotation.RavenwoodKeepPartialClass
+@CachedPropertyDefaults()
 public class UserManager {
 
     private static final String TAG = "UserManager";
@@ -108,6 +111,9 @@
     /** Whether the device is in headless system user mode; null until cached. */
     private static Boolean sIsHeadlessSystemUser = null;
 
+    /** Generated class containing IpcDataCaches. */
+    private final Object mIpcDataCache = new UserManagerCache();
+
     /** Maximum length of username.
      * @hide
      */
@@ -3766,62 +3772,18 @@
         return isUserUnlocked(user.getIdentifier());
     }
 
-    private static final String CACHE_KEY_IS_USER_UNLOCKED_PROPERTY =
-        PropertyInvalidatedCache.createPropertyName(
-            PropertyInvalidatedCache.MODULE_SYSTEM, "is_user_unlocked");
-
-    private final PropertyInvalidatedCache<Integer, Boolean> mIsUserUnlockedCache =
-            new PropertyInvalidatedCache<Integer, Boolean>(
-                32, CACHE_KEY_IS_USER_UNLOCKED_PROPERTY) {
-                @Override
-                public Boolean recompute(Integer query) {
-                    try {
-                        return mService.isUserUnlocked(query);
-                    } catch (RemoteException re) {
-                        throw re.rethrowFromSystemServer();
-                    }
-                }
-                @Override
-                public boolean bypass(Integer query) {
-                    return query < 0;
-                }
-            };
-
-    // Uses IS_USER_UNLOCKED_PROPERTY for invalidation as the APIs have the same dependencies.
-    private final PropertyInvalidatedCache<Integer, Boolean> mIsUserUnlockingOrUnlockedCache =
-            new PropertyInvalidatedCache<Integer, Boolean>(
-                32, CACHE_KEY_IS_USER_UNLOCKED_PROPERTY) {
-                @Override
-                public Boolean recompute(Integer query) {
-                    try {
-                        return mService.isUserUnlockingOrUnlocked(query);
-                    } catch (RemoteException re) {
-                        throw re.rethrowFromSystemServer();
-                    }
-                }
-                @Override
-                public boolean bypass(Integer query) {
-                    return query < 0;
-                }
-            };
-
     /** @hide */
     @UnsupportedAppUsage
     @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
             Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+    @CachedProperty(modsFlagOnOrNone = {}, api = "is_user_unlocked")
     public boolean isUserUnlocked(@UserIdInt int userId) {
-        return mIsUserUnlockedCache.query(userId);
-    }
-
-    /** @hide */
-    public void disableIsUserUnlockedCache() {
-        mIsUserUnlockedCache.disableLocal();
-        mIsUserUnlockingOrUnlockedCache.disableLocal();
+        return ((UserManagerCache) mIpcDataCache).isUserUnlocked(mService::isUserUnlocked, userId);
     }
 
     /** @hide */
     public static final void invalidateIsUserUnlockedCache() {
-        PropertyInvalidatedCache.invalidateCache(CACHE_KEY_IS_USER_UNLOCKED_PROPERTY);
+        UserManagerCache.invalidateUserUnlocked();
     }
 
     /**
@@ -3852,8 +3814,10 @@
     /** @hide */
     @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
             Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+    @CachedProperty(modsFlagOnOrNone = {}, api = "is_user_unlocked")
     public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
-        return mIsUserUnlockingOrUnlockedCache.query(userId);
+        return ((UserManagerCache) mIpcDataCache)
+                .isUserUnlockingOrUnlocked(mService::isUserUnlockingOrUnlocked, userId);
     }
 
     /**
@@ -5686,31 +5650,9 @@
         }
     }
 
-    private static final String CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY =
-        PropertyInvalidatedCache.createPropertyName(
-            PropertyInvalidatedCache.MODULE_SYSTEM, "quiet_mode_enabled");
-
-    private final PropertyInvalidatedCache<Integer, Boolean> mQuietModeEnabledCache =
-            new PropertyInvalidatedCache<Integer, Boolean>(
-                32, CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY) {
-                @Override
-                public Boolean recompute(Integer query) {
-                    try {
-                        return mService.isQuietModeEnabled(query);
-                    } catch (RemoteException re) {
-                        throw re.rethrowFromSystemServer();
-                    }
-                }
-                @Override
-                public boolean bypass(Integer query) {
-                    return query < 0;
-                }
-            };
-
-
     /** @hide */
     public static final void invalidateQuietModeEnabledCache() {
-        PropertyInvalidatedCache.invalidateCache(CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY);
+        UserManagerCache.invalidateQuietModeEnabled();
     }
 
     /**
@@ -5719,13 +5661,15 @@
      * @param userHandle The user handle of the profile to be queried.
      * @return true if the profile is in quiet mode, false otherwise.
      */
+    @CachedProperty(modsFlagOnOrNone = {})
     public boolean isQuietModeEnabled(UserHandle userHandle) {
-        if (android.multiuser.Flags.cacheQuietModeState()){
+        if (android.multiuser.Flags.cacheQuietModeState()) {
             final int userId = userHandle.getIdentifier();
             if (userId < 0) {
                 return false;
             }
-            return mQuietModeEnabledCache.query(userId);
+            return ((UserManagerCache) mIpcDataCache).isQuietModeEnabled(
+                    (UserHandle uh) -> mService.isQuietModeEnabled(uh.getIdentifier()), userHandle);
         }
         try {
             return mService.isQuietModeEnabled(userHandle.getIdentifier());
@@ -6424,41 +6368,21 @@
                 Settings.Global.DEVICE_DEMO_MODE, 0) > 0;
     }
 
-    private static final String CACHE_KEY_USER_SERIAL_NUMBER_PROPERTY =
-        PropertyInvalidatedCache.createPropertyName(
-            PropertyInvalidatedCache.MODULE_SYSTEM, "user_serial_number");
-
-    private final PropertyInvalidatedCache<Integer, Integer> mUserSerialNumberCache =
-            new PropertyInvalidatedCache<Integer, Integer>(
-                32, CACHE_KEY_USER_SERIAL_NUMBER_PROPERTY) {
-                @Override
-                public Integer recompute(Integer query) {
-                    try {
-                        return mService.getUserSerialNumber(query);
-                    } catch (RemoteException re) {
-                        throw re.rethrowFromSystemServer();
-                    }
-                }
-                @Override
-                public boolean bypass(Integer query) {
-                    return query <= 0;
-                }
-            };
-
-
     /** @hide */
     public static final void invalidateUserSerialNumberCache() {
-        PropertyInvalidatedCache.invalidateCache(CACHE_KEY_USER_SERIAL_NUMBER_PROPERTY);
+        UserManagerCache.invalidateUserSerialNumber();
     }
 
     /**
      * Returns a serial number on this device for a given userId. User handles can be recycled
-     * when deleting and creating users, but serial numbers are not reused until the device is wiped.
+     * when deleting and creating users, but serial numbers are not reused until the device is
+     * wiped.
      * @param userId
      * @return a serial number associated with that user, or -1 if the userId is not valid.
      * @hide
      */
     @UnsupportedAppUsage
+    @CachedProperty(modsFlagOnOrNone = {})
     public int getUserSerialNumber(@UserIdInt int userId) {
         // Read only flag should is to fix early access to this API
         // cacheUserSerialNumber to be removed after the
@@ -6470,7 +6394,8 @@
             if (userId == UserHandle.USER_SYSTEM) {
                return UserHandle.USER_SERIAL_SYSTEM;
             }
-            return mUserSerialNumberCache.query(userId);
+            return ((UserManagerCache) mIpcDataCache).getUserSerialNumber(
+                    mService::getUserSerialNumber, userId);
         }
         try {
             return mService.getUserSerialNumber(userId);
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 27b1dfb..d557046 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -3027,6 +3027,46 @@
          */
         @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
         public static final class DefaultAccount {
+            /**
+             * Key in the outgoing Bundle for the default account list.
+             *
+             * @hide
+             */
+            public static final String KEY_ELIGIBLE_DEFAULT_ACCOUNTS =
+                    "key_eligible_default_accounts";
+            /**
+             * The method to invoke in order to query eligiblie default accounts.
+             *
+             * @hide
+             */
+            public static final String QUERY_ELIGIBLE_DEFAULT_ACCOUNTS_METHOD =
+                    "queryEligibleDefaultAccounts";
+            /**
+             * Key in the Bundle for the default account state.
+             *
+             * @hide
+             */
+            public static final String KEY_DEFAULT_ACCOUNT_STATE =
+                    "key_default_account_state";
+            /**
+             * The method to invoke in order to set the default account.
+             *
+             * @hide
+             */
+            public static final String SET_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD =
+                    "setDefaultAccountForNewContacts";
+            /**
+             * The method to invoke in order to query the default account.
+             *
+             * @hide
+             */
+            public static final String QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD =
+                    "queryDefaultAccountForNewContacts";
+
+            private DefaultAccount() {
+
+            }
+
 
             /**
              * Represents the state of the default account, and the actual {@link Account} if it's
@@ -3228,6 +3268,94 @@
                 public @interface DefaultAccountState {
                 }
             }
+
+            /**
+             * Get the account that is set as the default account for new contacts, which should be
+             * initially selected when creating a new contact on contact management apps.
+             *
+             * @param resolver the ContentResolver to query.
+             *
+             * @return the default account state for new contacts.
+             * @throws RuntimeException if failed to look up the default account.
+             * @throws IllegalStateException if the default account is in an invalid state.
+             */
+            @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
+            public static @NonNull DefaultAccountAndState getDefaultAccountForNewContacts(
+                    @NonNull ContentResolver resolver) {
+                Bundle response = nullSafeCall(resolver, ContactsContract.AUTHORITY_URI,
+                        QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD, null, null);
+
+                int defaultContactsAccountState = response.getInt(KEY_DEFAULT_ACCOUNT_STATE, -1);
+                if (defaultContactsAccountState
+                        == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD) {
+                    String accountName = response.getString(Settings.ACCOUNT_NAME);
+                    String accountType = response.getString(Settings.ACCOUNT_TYPE);
+                    if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
+                        throw new IllegalStateException(
+                                "account name and type cannot be null or empty");
+                    }
+                    return new DefaultAccountAndState(
+                            DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD,
+                            new Account(accountName, accountType));
+                } else if (defaultContactsAccountState
+                        == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_LOCAL
+                        || defaultContactsAccountState
+                        == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_NOT_SET) {
+                    return new DefaultAccountAndState(defaultContactsAccountState, /*cloudAccount=*/
+                            null);
+                } else {
+                    throw new IllegalStateException("Invalid default account state");
+                }
+            }
+
+            /**
+             * Sets the default account that should be initially selected when creating a new
+             * contact on
+             * contact management apps. Apps can only set one of
+             * The following accounts as the default account:
+             * <ol>
+             *   <li> local account
+             *   <li> cloud account that are eligible to be set as default account.
+             * </ol>
+             *
+             * @param resolver               the ContentResolver to query.
+             * @param defaultAccountAndState the default account and state to be set. To set the
+             *                               local
+             *                               account as the
+             *                               default account, this parameter should be
+             *                               {@link DefaultAccountAndState#ofLocal()}. To set the a
+             *                               cloud
+             *                               account as the default account, this parameter should
+             *                               be
+             *                               {@link DefaultAccountAndState#ofCloud(Account)}. To
+             *                               set
+             *                               the
+             *                               default account to a "not set" state, this parameter
+             *                               should
+             *                               be {@link DefaultAccountAndState#ofNotSet()}.
+             *
+             * @throws RuntimeException if it fails to set the default account.
+             *
+             * @hide
+             */
+            @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS)
+            @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
+            @SystemApi
+            public static void setDefaultAccountForNewContacts(@NonNull ContentResolver resolver,
+                    @NonNull DefaultAccountAndState defaultAccountAndState) {
+                Bundle extras = new Bundle();
+
+                extras.putInt(KEY_DEFAULT_ACCOUNT_STATE, defaultAccountAndState.getState());
+                if (defaultAccountAndState.getState()
+                        == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD) {
+                    Account cloudAccount = defaultAccountAndState.getAccount();
+                    assert cloudAccount != null;
+                    extras.putString(Settings.ACCOUNT_NAME, cloudAccount.name);
+                    extras.putString(Settings.ACCOUNT_TYPE, cloudAccount.type);
+                }
+                nullSafeCall(resolver, ContactsContract.AUTHORITY_URI,
+                        SET_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD, null, extras);
+            }
         }
 
         /**
@@ -9055,30 +9183,6 @@
         public static final String KEY_DEFAULT_ACCOUNT = "key_default_account";
 
         /**
-         * Key in the Bundle for the default account state.
-         *
-         * @hide
-         */
-        public static final String KEY_DEFAULT_ACCOUNT_STATE =
-                "key_default_contacts_account_state";
-
-        /**
-         * The method to invoke in order to set the default account.
-         *
-         * @hide
-         */
-        public static final String SET_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD =
-                "setDefaultAccountForNewContacts";
-
-        /**
-         * The method to invoke in order to query the default account.
-         *
-         * @hide
-         */
-        public static final String QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD =
-                "queryDefaultAccountForNewContacts";
-
-        /**
          * Get the account that is set as the default account for new contacts, which should be
          * initially selected when creating a new contact on contact management apps.
          * If the setting has not been set by any app, it will return null. Once the setting
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d82af55..1a15d09 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2092,23 +2092,6 @@
     public static final String ACTION_ZEN_MODE_SETTINGS = "android.settings.ZEN_MODE_SETTINGS";
 
     /**
-     * Activity Action: Show Zen Mode visual effects configuration settings.
-     *
-     * @hide
-     */
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ZEN_MODE_BLOCKED_EFFECTS_SETTINGS =
-            "android.settings.ZEN_MODE_BLOCKED_EFFECTS_SETTINGS";
-
-    /**
-     * Activity Action: Show Zen Mode onboarding activity.
-     *
-     * @hide
-     */
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ZEN_MODE_ONBOARDING = "android.settings.ZEN_MODE_ONBOARDING";
-
-    /**
      * Activity Action: Show Zen Mode (aka Do Not Disturb) priority configuration settings.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -2367,6 +2350,21 @@
             "android.settings.ALL_APPS_NOTIFICATION_SETTINGS_FOR_REVIEW";
 
     /**
+     * Activity Action: Show the permission screen for allowing apps to post promoted notifications.
+     * <p>
+     *     Input: {@link #EXTRA_APP_PACKAGE}, the package to display.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Output: Nothing.
+     */
+    @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_APP_NOTIFICATION_PROMOTION_SETTINGS
+            = "android.settings.APP_NOTIFICATION_PROMOTION_SETTINGS";
+
+    /**
      * Activity Action: Show notification settings for a single app.
      * <p>
      *     Input: {@link #EXTRA_APP_PACKAGE}, the package to display.
@@ -8748,35 +8746,6 @@
         /** @hide */ public static final int ZEN_DURATION_FOREVER = 0;
 
         /**
-         * If nonzero, will show the zen upgrade notification when the user toggles DND on/off.
-         * @hide
-         */
-        @Readable
-        public static final String SHOW_ZEN_UPGRADE_NOTIFICATION = "show_zen_upgrade_notification";
-
-        /**
-         * If nonzero, will show the zen update settings suggestion.
-         * @hide
-         */
-        @Readable
-        public static final String SHOW_ZEN_SETTINGS_SUGGESTION = "show_zen_settings_suggestion";
-
-        /**
-         * If nonzero, zen has not been updated to reflect new changes.
-         * @hide
-         */
-        @Readable
-        public static final String ZEN_SETTINGS_UPDATED = "zen_settings_updated";
-
-        /**
-         * If nonzero, zen setting suggestion has been viewed by user
-         * @hide
-         */
-        @Readable
-        public static final String ZEN_SETTINGS_SUGGESTION_VIEWED =
-                "zen_settings_suggestion_viewed";
-
-        /**
          * Whether the in call notification is enabled to play sound during calls.  The value is
          * boolean (1 or 0).
          * @hide
@@ -17844,12 +17813,6 @@
         public static final String FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT =
                 "force_non_debuggable_final_build_for_compat";
 
-        /**
-         * Flag to enable the use of ApplicationInfo for getting not-launched status.
-         *
-         * @hide
-         */
-        public static final String ENABLE_USE_APP_INFO_NOT_LAUNCHED = "use_app_info_not_launched";
 
         /**
          * Current version of signed configuration applied.
@@ -18072,10 +18035,6 @@
             MOVED_TO_SECURE = new HashSet<>(8);
             MOVED_TO_SECURE.add(Global.INSTALL_NON_MARKET_APPS);
             MOVED_TO_SECURE.add(Global.ZEN_DURATION);
-            MOVED_TO_SECURE.add(Global.SHOW_ZEN_UPGRADE_NOTIFICATION);
-            MOVED_TO_SECURE.add(Global.SHOW_ZEN_SETTINGS_SUGGESTION);
-            MOVED_TO_SECURE.add(Global.ZEN_SETTINGS_UPDATED);
-            MOVED_TO_SECURE.add(Global.ZEN_SETTINGS_SUGGESTION_VIEWED);
             MOVED_TO_SECURE.add(Global.CHARGING_SOUNDS_ENABLED);
             MOVED_TO_SECURE.add(Global.CHARGING_VIBRATION_ENABLED);
             MOVED_TO_SECURE.add(Global.NOTIFICATION_BUBBLES);
@@ -18910,40 +18869,6 @@
         @Readable
         public static final String SHOW_MUTE_IN_CRASH_DIALOG = "show_mute_in_crash_dialog";
 
-
-        /**
-         * If nonzero, will show the zen upgrade notification when the user toggles DND on/off.
-         * @hide
-         * @deprecated - Use {@link android.provider.Settings.Secure#SHOW_ZEN_UPGRADE_NOTIFICATION}
-         */
-        @Deprecated
-        public static final String SHOW_ZEN_UPGRADE_NOTIFICATION = "show_zen_upgrade_notification";
-
-        /**
-         * If nonzero, will show the zen update settings suggestion.
-         * @hide
-         * @deprecated - Use {@link android.provider.Settings.Secure#SHOW_ZEN_SETTINGS_SUGGESTION}
-         */
-        @Deprecated
-        public static final String SHOW_ZEN_SETTINGS_SUGGESTION = "show_zen_settings_suggestion";
-
-        /**
-         * If nonzero, zen has not been updated to reflect new changes.
-         * @deprecated - Use {@link android.provider.Settings.Secure#ZEN_SETTINGS_UPDATED}
-         * @hide
-         */
-        @Deprecated
-        public static final String ZEN_SETTINGS_UPDATED = "zen_settings_updated";
-
-        /**
-         * If nonzero, zen setting suggestion has been viewed by user
-         * @hide
-         * @deprecated - Use {@link android.provider.Settings.Secure#ZEN_SETTINGS_SUGGESTION_VIEWED}
-         */
-        @Deprecated
-        public static final String ZEN_SETTINGS_SUGGESTION_VIEWED =
-                "zen_settings_suggestion_viewed";
-
         /**
          * Backup and restore agent timeout parameters.
          * These parameters are represented by a comma-delimited key-value list.
@@ -20233,6 +20158,12 @@
             public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION_SUCCESS = 11;
 
             /**
+             * Phone switching has finished account match step.
+             * @hide
+             */
+            public static final int PHONE_SWITCHING_STATUS_ACCOUNTS_MATCHED = 12;
+
+            /**
              * Phone switching request source
              * @hide
              */
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index a86c961..aedf8e0 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -106,3 +106,10 @@
     description: "Clear StrongAuth on add credential"
     bug: "320817991"
 }
+
+flag {
+    name: "afl_api"
+    namespace: "platform_security"
+    description: "AFL feature"
+    bug: "365994454"
+}
diff --git a/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java b/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java
index de2c6f77..afff8fe 100644
--- a/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java
+++ b/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -28,8 +29,10 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.telephony.SmsMessage;
 
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.util.Preconditions;
 
 import java.util.List;
@@ -91,8 +94,12 @@
         mOnServiceReadyCallback = onServiceReadyCallback;
         mServiceReadyCallbackExecutor = executor;
         mContext = context;
-        return context.bindService(intent, mCarrierMessagingServiceConnection,
-                Context.BIND_AUTO_CREATE);
+        return Flags.supportCarrierServicesForHsum()
+                ? context.bindServiceAsUser(intent, mCarrierMessagingServiceConnection,
+                Context.BIND_AUTO_CREATE,
+                UserHandle.of(ActivityManager.getCurrentUser()))
+                : context.bindService(intent, mCarrierMessagingServiceConnection,
+                        Context.BIND_AUTO_CREATE);
     }
 
     /**
diff --git a/core/java/android/tracing/TEST_MAPPING b/core/java/android/tracing/TEST_MAPPING
new file mode 100644
index 0000000..b51d19d
--- /dev/null
+++ b/core/java/android/tracing/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+  "postsubmit": [
+    {
+      "name": "TracingTests"
+    },
+    {
+      "name": "ProtologPerfTests"
+    }
+  ]
+}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 90ceb44..2748e32 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -152,6 +152,8 @@
             long nativeObject, int priority);
     private static native void nativeSetWindowCrop(long transactionObj, long nativeObject,
             int l, int t, int r, int b);
+    private static native void nativeSetCrop(long transactionObj, long nativeObject,
+            float l, float t, float r, float b);
     private static native void nativeSetCornerRadius(long transactionObj, long nativeObject,
             float cornerRadius);
     private static native void nativeSetBackgroundBlurRadius(long transactionObj, long nativeObject,
@@ -3452,6 +3454,29 @@
         }
 
         /**
+         * Bounds the surface and its children to the bounds specified. Size of the surface will be
+         * ignored and only the crop and buffer size will be used to determine the bounds of the
+         * surface. If no crop is specified and the surface has no buffer, the surface bounds is
+         * only constrained by the size of its parent bounds.
+         *
+         * @param sc   SurfaceControl to set crop of.
+         * @param crop Bounds of the crop to apply.
+         * @return this This transaction for chaining
+         * @hide
+         */
+        public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, float top, float left,
+                float bottom, float right) {
+            checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setCrop", this, sc, "crop={" + top + ", " + left + ", " +
+                        bottom + ", " + right + "}");
+            }
+            nativeSetCrop(mNativeObject, sc.mNativeObject, top, left, bottom, right);
+            return this;
+        }
+
+        /**
          * Sets the corner radius of a {@link SurfaceControl}.
          * @param sc SurfaceControl
          * @param cornerRadius Corner radius in pixels.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8fb17c7..0ca442d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -7531,6 +7531,7 @@
                         if (keyEvent.isCanceled()) {
                             animationCallback.onBackCancelled();
                         } else {
+                            dispatcher.tryInvokeSystemNavigationObserverCallback();
                             topCallback.onBackInvoked();
                         }
                         break;
@@ -7538,6 +7539,7 @@
             } else if (topCallback != null) {
                 if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
                     if (!keyEvent.isCanceled()) {
+                        dispatcher.tryInvokeSystemNavigationObserverCallback();
                         topCallback.onBackInvoked();
                     } else {
                         Log.d(mTag, "Skip onBackInvoked(), reason: keyEvent.isCanceled=true");
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 0582afe..381006c 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1345,7 +1345,7 @@
     }
 
     /**
-     * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of
+     * <p>Sets the desired amount of HDR headroom to be used when rendering as a ratio of
      * targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when
      * {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p>
      *
diff --git a/core/java/android/window/OnBackInvokedDispatcher.java b/core/java/android/window/OnBackInvokedDispatcher.java
index 0632a37..02ed57d 100644
--- a/core/java/android/window/OnBackInvokedDispatcher.java
+++ b/core/java/android/window/OnBackInvokedDispatcher.java
@@ -16,11 +16,14 @@
 
 package android.window;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 
+import com.android.window.flags.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -43,6 +46,7 @@
     @IntDef({
             PRIORITY_DEFAULT,
             PRIORITY_OVERLAY,
+            PRIORITY_SYSTEM_NAVIGATION_OBSERVER,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface Priority{}
@@ -67,6 +71,20 @@
     int PRIORITY_SYSTEM = -1;
 
     /**
+     * Priority level of {@link OnBackInvokedCallback}s designed to observe system-level back
+     * handling.
+     *
+     * <p>Callbacks registered with this priority do not consume back events. They receive back
+     * events whenever the system handles a back navigation and have no impact on the normal back
+     * navigation flow. Useful for logging or analytics.
+     *
+     * <p>Only one callback with {@link #PRIORITY_SYSTEM_NAVIGATION_OBSERVER} can be registered at a
+     * time.
+     */
+    @FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER)
+    int PRIORITY_SYSTEM_NAVIGATION_OBSERVER = -2;
+
+    /**
      * Registers a {@link OnBackInvokedCallback}.
      *
      * Within the same priority level, callbacks are invoked in the reverse order in which
diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
index 56c05b2..dfc4a58 100644
--- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
@@ -16,6 +16,8 @@
 
 package android.window;
 
+import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -95,7 +97,7 @@
         synchronized (mLock) {
             mCallbacks.add(Pair.create(callback, priority));
             if (mActualDispatcher != null) {
-                if (priority <= PRIORITY_SYSTEM) {
+                if (priority == PRIORITY_SYSTEM) {
                     mActualDispatcher.registerSystemOnBackInvokedCallback(callback);
                 } else {
                     mActualDispatcher.registerOnBackInvokedCallback(priority, callback);
@@ -123,10 +125,19 @@
         }
         for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) {
             int priority = callbackPair.second;
-            if (priority >= PRIORITY_DEFAULT) {
-                mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first);
+            if (predictiveBackPrioritySystemNavigationObserver()) {
+                if (priority >= PRIORITY_DEFAULT
+                        || priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER) {
+                    mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first);
+                } else {
+                    mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
+                }
             } else {
-                mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
+                if (priority >= PRIORITY_DEFAULT) {
+                    mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first);
+                } else {
+                    mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
+                }
             }
         }
         mCallbacks.clear();
diff --git a/core/java/android/window/TaskConstants.java b/core/java/android/window/TaskConstants.java
index 44bb33db..46bc30e 100644
--- a/core/java/android/window/TaskConstants.java
+++ b/core/java/android/window/TaskConstants.java
@@ -53,25 +53,30 @@
      */
     public static final int TASK_CHILD_LAYER_COMPAT_UI = TASK_CHILD_LAYER_REGION_SIZE;
 
+    /**
+     * Settings dialogs belonging to the task (e.g. Open by default settings dialog)
+     * @hide
+     */
+    public static final int TASK_CHILD_LAYER_SETTINGS_DIALOG = 2 * TASK_CHILD_LAYER_REGION_SIZE;
 
     /**
      * Captions, window frames and resize handlers around task windows.
      * @hide
      */
-    public static final int TASK_CHILD_LAYER_WINDOW_DECORATIONS = 2 * TASK_CHILD_LAYER_REGION_SIZE;
+    public static final int TASK_CHILD_LAYER_WINDOW_DECORATIONS = 3 * TASK_CHILD_LAYER_REGION_SIZE;
 
     /**
      * Overlays the task when going into PIP w/ gesture navigation.
      * @hide
      */
     public static final int TASK_CHILD_LAYER_RECENTS_ANIMATION_PIP_OVERLAY =
-            3 * TASK_CHILD_LAYER_REGION_SIZE;
+            4 * TASK_CHILD_LAYER_REGION_SIZE;
 
     /**
      * Allows other apps to add overlays on the task (i.e. game dashboard)
      * @hide
      */
-    public static final int TASK_CHILD_LAYER_TASK_OVERLAY = 4 * TASK_CHILD_LAYER_REGION_SIZE;
+    public static final int TASK_CHILD_LAYER_TASK_OVERLAY = 5 * TASK_CHILD_LAYER_REGION_SIZE;
 
 
     /**
@@ -95,6 +100,7 @@
             TASK_CHILD_LAYER_TASK_BACKGROUND,
             TASK_CHILD_LAYER_LETTERBOX_BACKGROUND,
             TASK_CHILD_LAYER_COMPAT_UI,
+            TASK_CHILD_LAYER_SETTINGS_DIALOG,
             TASK_CHILD_LAYER_WINDOW_DECORATIONS,
             TASK_CHILD_LAYER_RECENTS_ANIMATION_PIP_OVERLAY,
             TASK_CHILD_LAYER_TASK_OVERLAY,
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 314bf89..0dc9263 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -28,6 +28,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.PendingIntent;
 import android.app.WindowConfiguration;
@@ -44,6 +45,7 @@
 import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
 import android.view.SurfaceControl;
+import android.view.WindowInsets;
 import android.view.WindowInsets.Type.InsetsType;
 
 import com.android.window.flags.Flags;
@@ -267,6 +269,23 @@
     }
 
     /**
+     * Sets whether the IME insets should be excluded by {@link com.android.server.wm.InsetsPolicy}.
+     * @hide
+     */
+    @SuppressLint("UnflaggedApi")
+    @NonNull
+    public WindowContainerTransaction setExcludeImeInsets(
+            @NonNull WindowContainerToken container, boolean exclude) {
+        final HierarchyOp hierarchyOp =
+                new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES)
+                        .setContainer(container.asBinder())
+                        .setExcludeInsetsTypes(exclude ? WindowInsets.Type.ime() : 0)
+                        .build();
+        mHierarchyOps.add(hierarchyOp);
+        return this;
+    }
+
+    /**
      * Sets whether a container or its children should be hidden. When {@code false}, the existing
      * visibility of the container applies, but when {@code true} the container will be forced
      * to be hidden.
@@ -1449,6 +1468,7 @@
         public static final int HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK = 18;
         public static final int HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE = 19;
         public static final int HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION = 20;
+        public static final int HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES = 21;
 
         // The following key(s) are for use with mLaunchOptions:
         // When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1512,6 +1532,8 @@
 
         private boolean mIsTrimmableFromRecents;
 
+        private @InsetsType int mExcludeInsetsTypes;
+
         public static HierarchyOp createForReparent(
                 @NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
             return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
@@ -1649,6 +1671,7 @@
             mAlwaysOnTop = copy.mAlwaysOnTop;
             mReparentLeafTaskIfRelaunch = copy.mReparentLeafTaskIfRelaunch;
             mIsTrimmableFromRecents = copy.mIsTrimmableFromRecents;
+            mExcludeInsetsTypes = copy.mExcludeInsetsTypes;
         }
 
         protected HierarchyOp(Parcel in) {
@@ -1671,6 +1694,7 @@
             mAlwaysOnTop = in.readBoolean();
             mReparentLeafTaskIfRelaunch = in.readBoolean();
             mIsTrimmableFromRecents = in.readBoolean();
+            mExcludeInsetsTypes = in.readInt();
         }
 
         public int getType() {
@@ -1772,6 +1796,10 @@
             return mIsTrimmableFromRecents;
         }
 
+        public @InsetsType int getExcludeInsetsTypes() {
+            return mExcludeInsetsTypes;
+        }
+
         /** Gets a string representation of a hierarchy-op type. */
         public static String hopToString(int type) {
             switch (type) {
@@ -1795,6 +1823,7 @@
                     return "setReparentLeafTaskIfRelaunch";
                 case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION:
                     return "addTaskFragmentOperation";
+                case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES: return "setExcludeInsetsTypes";
                 default: return "HOP(" + type + ")";
             }
         }
@@ -1868,6 +1897,11 @@
                     sb.append("fragmentToken= ").append(mContainer)
                             .append(" operation= ").append(mTaskFragmentOperation);
                     break;
+                case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES:
+                    sb.append("container= ").append(mContainer)
+                            .append(" mExcludeInsetsTypes= ")
+                            .append(WindowInsets.Type.toString(mExcludeInsetsTypes));
+                    break;
                 case HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE:
                     sb.append("container= ").append(mContainer)
                             .append(" isTrimmable= ")
@@ -1903,6 +1937,7 @@
             dest.writeBoolean(mAlwaysOnTop);
             dest.writeBoolean(mReparentLeafTaskIfRelaunch);
             dest.writeBoolean(mIsTrimmableFromRecents);
+            dest.writeInt(mExcludeInsetsTypes);
         }
 
         @Override
@@ -1974,6 +2009,8 @@
 
             private boolean mIsTrimmableFromRecents;
 
+            private @InsetsType int mExcludeInsetsTypes;
+
             Builder(int type) {
                 mType = type;
             }
@@ -2069,6 +2106,11 @@
                 return this;
             }
 
+            Builder setExcludeInsetsTypes(@InsetsType int excludeInsetsTypes) {
+                mExcludeInsetsTypes = excludeInsetsTypes;
+                return this;
+            }
+
             HierarchyOp build() {
                 final HierarchyOp hierarchyOp = new HierarchyOp(mType);
                 hierarchyOp.mContainer = mContainer;
@@ -2093,6 +2135,7 @@
                 hierarchyOp.mIncludingParents = mIncludingParents;
                 hierarchyOp.mReparentLeafTaskIfRelaunch = mReparentLeafTaskIfRelaunch;
                 hierarchyOp.mIsTrimmableFromRecents = mIsTrimmableFromRecents;
+                hierarchyOp.mExcludeInsetsTypes = mExcludeInsetsTypes;
 
                 return hierarchyOp;
             }
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index bb89a24..51bc7d5 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -16,6 +16,8 @@
 
 package android.window;
 
+import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -103,6 +105,9 @@
     public final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
             mOnBackInvokedCallbacks = new TreeMap<>();
 
+    @VisibleForTesting
+    public OnBackInvokedCallback mSystemNavigationObserverCallback = null;
+
     private Checker mChecker;
     private final Object mLock = new Object();
     // The threshold for back swipe full progress.
@@ -170,6 +175,20 @@
         }
     }
 
+    private void registerSystemNavigationObserverCallback(@NonNull OnBackInvokedCallback callback) {
+        synchronized (mLock) {
+            // If callback has already been added as regular callback, remove it.
+            if (mAllCallbacks.containsKey(callback)) {
+                if (DEBUG) {
+                    Log.i(TAG, "Callback already added. Removing and re-adding it as "
+                            + "system-navigation-observer-callback.");
+                }
+                removeCallbackInternal(callback);
+            }
+            mSystemNavigationObserverCallback = callback;
+        }
+    }
+
     /**
      * Register a callback bypassing platform checks. This is used to register compatibility
      * callbacks.
@@ -181,6 +200,12 @@
                 mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
                 return;
             }
+            if (predictiveBackPrioritySystemNavigationObserver()) {
+                if (priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER) {
+                    registerSystemNavigationObserverCallback(callback);
+                    return;
+                }
+            }
             if (callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) {
                 if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback
                         && mImeBackAnimationController != null) {
@@ -202,6 +227,13 @@
                 Integer prevPriority = mAllCallbacks.get(callback);
                 mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
             }
+            if (mSystemNavigationObserverCallback == callback) {
+                mSystemNavigationObserverCallback = null;
+                if (DEBUG) {
+                    Log.i(TAG, "Callback already registered (as system-navigation-observer "
+                            + "callback). Removing and re-adding it.");
+                }
+            }
 
             OnBackInvokedCallback previousTopCallback = getTopCallback();
             callbacks.add(callback);
@@ -221,6 +253,10 @@
                 mImeDispatcher.unregisterOnBackInvokedCallback(callback);
                 return;
             }
+            if (mSystemNavigationObserverCallback == callback) {
+                mSystemNavigationObserverCallback = null;
+                return;
+            }
             if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
                 callback = mImeBackAnimationController;
             }
@@ -230,25 +266,29 @@
                 }
                 return;
             }
-            OnBackInvokedCallback previousTopCallback = getTopCallback();
-            Integer priority = mAllCallbacks.get(callback);
-            ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
-            callbacks.remove(callback);
-            if (callbacks.isEmpty()) {
-                mOnBackInvokedCallbacks.remove(priority);
-            }
-            mAllCallbacks.remove(callback);
-            // Re-populate the top callback to WM if the removed callback was previously the top
-            // one.
-            if (previousTopCallback == callback) {
-                // We should call onBackCancelled() when an active callback is removed from
-                // dispatcher.
-                mProgressAnimator.removeOnBackCancelledFinishCallback();
-                mProgressAnimator.removeOnBackInvokedFinishCallback();
-                sendCancelledIfInProgress(callback);
-                mHandler.post(mProgressAnimator::reset);
-                setTopOnBackInvokedCallback(getTopCallback());
-            }
+            removeCallbackInternal(callback);
+        }
+    }
+
+    private void removeCallbackInternal(@NonNull OnBackInvokedCallback callback) {
+        OnBackInvokedCallback previousTopCallback = getTopCallback();
+        Integer priority = mAllCallbacks.get(callback);
+        ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+        callbacks.remove(callback);
+        if (callbacks.isEmpty()) {
+            mOnBackInvokedCallbacks.remove(priority);
+        }
+        mAllCallbacks.remove(callback);
+        // Re-populate the top callback to WM if the removed callback was previously the top
+        // one.
+        if (previousTopCallback == callback) {
+            // We should call onBackCancelled() when an active callback is removed from
+            // dispatcher.
+            mProgressAnimator.removeOnBackCancelledFinishCallback();
+            mProgressAnimator.removeOnBackInvokedFinishCallback();
+            sendCancelledIfInProgress(callback);
+            mHandler.post(mProgressAnimator::reset);
+            setTopOnBackInvokedCallback(getTopCallback());
         }
     }
 
@@ -304,6 +344,7 @@
             mHandler.post(mProgressAnimator::reset);
             mAllCallbacks.clear();
             mOnBackInvokedCallbacks.clear();
+            mSystemNavigationObserverCallback = null;
         }
     }
 
@@ -315,6 +356,25 @@
         }
     }
 
+    /**
+     * Tries to call {@link OnBackInvokedCallback#onBackInvoked} on the system navigation observer
+     * callback (if one is set and if the top-most regular callback has
+     * {@link OnBackInvokedDispatcher#PRIORITY_SYSTEM})
+     */
+    public void tryInvokeSystemNavigationObserverCallback() {
+        OnBackInvokedCallback topCallback = getTopCallback();
+        Integer callbackPriority = mAllCallbacks.getOrDefault(topCallback, null);
+        if (callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) {
+            invokeSystemNavigationObserverCallback();
+        }
+    }
+
+    private void invokeSystemNavigationObserverCallback() {
+        if (mSystemNavigationObserverCallback != null) {
+            mSystemNavigationObserverCallback.onBackInvoked();
+        }
+    }
+
     private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {
         if (mWindowSession == null || mWindow == null) {
             return;
@@ -324,7 +384,9 @@
             if (callback != null) {
                 int priority = mAllCallbacks.get(callback);
                 final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(callback,
-                        mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme);
+                        mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme,
+                        this::invokeSystemNavigationObserverCallback,
+                        /*isSystemCallback*/ priority == PRIORITY_SYSTEM);
                 callbackInfo = new OnBackInvokedCallbackInfo(
                         iCallback,
                         priority,
@@ -416,18 +478,26 @@
         private final Handler mHandler;
         @NonNull
         private final BooleanSupplier mOnKeyPreIme;
+        @NonNull
+        private final Runnable mSystemNavigationObserverCallbackRunnable;
+        private final boolean mIsSystemCallback;
 
         OnBackInvokedCallbackWrapper(
                 @NonNull OnBackInvokedCallback callback,
                 @NonNull BackTouchTracker touchTracker,
                 @NonNull BackProgressAnimator progressAnimator,
                 @NonNull Handler handler,
-                @NonNull BooleanSupplier onKeyPreIme) {
+                @NonNull BooleanSupplier onKeyPreIme,
+                @NonNull Runnable systemNavigationObserverCallbackRunnable,
+                boolean isSystemCallback
+        ) {
             mCallback = new WeakReference<>(callback);
             mTouchTracker = touchTracker;
             mProgressAnimator = progressAnimator;
             mHandler = handler;
             mOnKeyPreIme = onKeyPreIme;
+            mSystemNavigationObserverCallbackRunnable = systemNavigationObserverCallbackRunnable;
+            mIsSystemCallback = isSystemCallback;
         }
 
         @Override
@@ -494,9 +564,17 @@
                 OnBackAnimationCallback animationCallback = getBackAnimationCallback();
                 if (animationCallback != null
                         && !(callback instanceof ImeBackAnimationController)) {
-                    mProgressAnimator.onBackInvoked(callback::onBackInvoked);
+                    mProgressAnimator.onBackInvoked(() -> {
+                        if (mIsSystemCallback) {
+                            mSystemNavigationObserverCallbackRunnable.run();
+                        }
+                        callback.onBackInvoked();
+                    });
                 } else {
                     mProgressAnimator.reset();
+                    if (mIsSystemCallback) {
+                        mSystemNavigationObserverCallbackRunnable.run();
+                    }
                     callback.onBackInvoked();
                 }
             });
@@ -597,9 +675,18 @@
                                 + " application manifest.");
                 return false;
             }
-            if (priority < 0) {
-                throw new IllegalArgumentException("Application registered OnBackInvokedCallback "
-                        + "cannot have negative priority. Priority: " + priority);
+            if (predictiveBackPrioritySystemNavigationObserver()) {
+                if (priority < 0 && priority != PRIORITY_SYSTEM_NAVIGATION_OBSERVER) {
+                    throw new IllegalArgumentException("Application registered "
+                            + "OnBackInvokedCallback cannot have negative priority. Priority: "
+                            + priority);
+                }
+            } else {
+                if (priority < 0) {
+                    throw new IllegalArgumentException("Application registered "
+                            + "OnBackInvokedCallback cannot have negative priority. Priority: "
+                            + priority);
+                }
             }
             Objects.requireNonNull(callback);
             return true;
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 70ac12f..b22aa22 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -162,6 +162,13 @@
 }
 
 flag {
+    name: "enable_a11y_metrics"
+    namespace: "lse_desktop_experience"
+    description: "Whether to enable log collection for a11y actions in desktop windowing mode"
+    bug: "341319597"
+}
+
+flag {
     name: "enable_caption_compat_inset_force_consumption"
     namespace: "lse_desktop_experience"
     description: "Enables force-consumption of caption bar insets for immersive apps in freeform"
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 086063f..c9b93c9 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -307,3 +307,11 @@
   bug: "364930619"
   is_fixed_read_only: true
 }
+
+flag {
+    name: "predictive_back_priority_system_navigation_observer"
+    namespace: "systemui"
+    description: "PRIORITY_SYSTEM_NAVIGATION_OBSERVER predictive back API extension"
+    is_fixed_read_only: true
+    bug: "362938401"
+}
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index fef5e83..4aebde5 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -26,6 +26,7 @@
 import android.os.RemoteException;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -37,38 +38,40 @@
      * @deprecated Legacy system channel, which is no longer used,
      */
     @Deprecated public static String VIRTUAL_KEYBOARD  = "VIRTUAL_KEYBOARD";
-    public static String PHYSICAL_KEYBOARD = "PHYSICAL_KEYBOARD";
-    public static String SECURITY = "SECURITY";
-    public static String CAR_MODE = "CAR_MODE";
-    public static String ACCOUNT = "ACCOUNT";
-    public static String DEVELOPER = "DEVELOPER";
-    public static String DEVELOPER_IMPORTANT = "DEVELOPER_IMPORTANT";
-    public static String UPDATES = "UPDATES";
-    public static String NETWORK_STATUS = "NETWORK_STATUS";
-    public static String NETWORK_ALERTS = "NETWORK_ALERTS";
-    public static String NETWORK_AVAILABLE = "NETWORK_AVAILABLE";
-    public static String VPN = "VPN";
+    public static final String PHYSICAL_KEYBOARD = "PHYSICAL_KEYBOARD";
+    public static final String SECURITY = "SECURITY";
+    public static final String CAR_MODE = "CAR_MODE";
+    public static final String ACCOUNT = "ACCOUNT";
+    public static final String DEVELOPER = "DEVELOPER";
+    public static final String DEVELOPER_IMPORTANT = "DEVELOPER_IMPORTANT";
+    public static final String UPDATES = "UPDATES";
+    public static final String NETWORK_STATUS = "NETWORK_STATUS";
+    public static final String NETWORK_ALERTS = "NETWORK_ALERTS";
+    public static final String NETWORK_AVAILABLE = "NETWORK_AVAILABLE";
+    public static final String VPN = "VPN";
     /**
      * @deprecated Legacy device admin channel with low importance which is no longer used,
      *  Use the high importance {@link #DEVICE_ADMIN} channel instead.
      */
-    @Deprecated public static String DEVICE_ADMIN_DEPRECATED = "DEVICE_ADMIN";
-    public static String DEVICE_ADMIN = "DEVICE_ADMIN_ALERTS";
-    public static String ALERTS = "ALERTS";
-    public static String RETAIL_MODE = "RETAIL_MODE";
-    public static String USB = "USB";
-    public static String FOREGROUND_SERVICE = "FOREGROUND_SERVICE";
-    public static String HEAVY_WEIGHT_APP = "HEAVY_WEIGHT_APP";
+    @Deprecated public static final String DEVICE_ADMIN_DEPRECATED = "DEVICE_ADMIN";
+    public static final String DEVICE_ADMIN = "DEVICE_ADMIN_ALERTS";
+    public static final String ALERTS = "ALERTS";
+    public static final String RETAIL_MODE = "RETAIL_MODE";
+    public static final String USB = "USB";
+    public static final String FOREGROUND_SERVICE = "FOREGROUND_SERVICE";
+    public static final String HEAVY_WEIGHT_APP = "HEAVY_WEIGHT_APP";
     /**
      * @deprecated Legacy system changes channel with low importance which is no longer used,
      *  Use the default importance {@link #SYSTEM_CHANGES} channel instead.
      */
-    @Deprecated public static String SYSTEM_CHANGES_DEPRECATED = "SYSTEM_CHANGES";
-    public static String SYSTEM_CHANGES = "SYSTEM_CHANGES_ALERTS";
-    public static String DO_NOT_DISTURB = "DO_NOT_DISTURB";
-    public static String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION";
-    public static String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY";
-    public static String ABUSIVE_BACKGROUND_APPS = "ABUSIVE_BACKGROUND_APPS";
+    @Deprecated public static final String SYSTEM_CHANGES_DEPRECATED = "SYSTEM_CHANGES";
+    public static final String SYSTEM_CHANGES = "SYSTEM_CHANGES_ALERTS";
+    public static final String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION";
+    public static final String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY";
+    public static final String ABUSIVE_BACKGROUND_APPS = "ABUSIVE_BACKGROUND_APPS";
+
+    @VisibleForTesting
+    static final String OBSOLETE_DO_NOT_DISTURB = "DO_NOT_DISTURB";
 
     public static void createAll(Context context) {
         final NotificationManager nm = context.getSystemService(NotificationManager.class);
@@ -193,11 +196,6 @@
                 .build());
         channelsList.add(systemChanges);
 
-        NotificationChannel dndChanges = new NotificationChannel(DO_NOT_DISTURB,
-                context.getString(R.string.notification_channel_do_not_disturb),
-                NotificationManager.IMPORTANCE_LOW);
-        channelsList.add(dndChanges);
-
         final NotificationChannel newFeaturePrompt = new NotificationChannel(
                 ACCESSIBILITY_MAGNIFICATION,
                 context.getString(R.string.notification_channel_accessibility_magnification),
@@ -218,6 +216,9 @@
         channelsList.add(abusiveBackgroundAppsChannel);
 
         nm.createNotificationChannels(channelsList);
+
+        // Delete channels created by previous Android versions that are no longer used.
+        nm.deleteNotificationChannel(OBSOLETE_DO_NOT_DISTURB);
     }
 
     private static String getDeviceAdminNotificationChannelName(Context context) {
diff --git a/core/java/com/android/internal/protolog/TEST_MAPPING b/core/java/com/android/internal/protolog/TEST_MAPPING
index 37d57ee..b51d19d 100644
--- a/core/java/com/android/internal/protolog/TEST_MAPPING
+++ b/core/java/com/android/internal/protolog/TEST_MAPPING
@@ -1,6 +1,9 @@
 {
   "postsubmit": [
     {
+      "name": "TracingTests"
+    },
+    {
       "name": "ProtologPerfTests"
     }
   ]
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 9797d96..4d2195d 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -73,6 +73,12 @@
 
     srcs: [
         "android_animation_PropertyValuesHolder.cpp",
+        "android_database_CursorWindow.cpp",
+        "android_database_SQLiteCommon.cpp",
+        "android_database_SQLiteConnection.cpp",
+        "android_database_SQLiteGlobal.cpp",
+        "android_database_SQLiteDebug.cpp",
+        "android_database_SQLiteRawStatement.cpp",
         "android_os_SystemClock.cpp",
         "android_os_SystemProperties.cpp",
         "android_os_Trace.cpp",
@@ -157,12 +163,6 @@
                 "android_opengl_GLES31.cpp",
                 "android_opengl_GLES31Ext.cpp",
                 "android_opengl_GLES32.cpp",
-                "android_database_CursorWindow.cpp",
-                "android_database_SQLiteCommon.cpp",
-                "android_database_SQLiteConnection.cpp",
-                "android_database_SQLiteGlobal.cpp",
-                "android_database_SQLiteDebug.cpp",
-                "android_database_SQLiteRawStatement.cpp",
                 "android_graphics_GraphicBuffer.cpp",
                 "android_graphics_SurfaceTexture.cpp",
                 "android_view_CompositionSamplingListener.cpp",
@@ -427,6 +427,7 @@
                 "libnativehelper_jvm",
                 "libpiex",
                 "libpng",
+                "libsqlite",
                 "libtiff_directory",
                 "libui-types",
                 "libutils",
@@ -442,12 +443,6 @@
         host_linux: {
             srcs: [
                 "android_content_res_ApkAssets.cpp",
-                "android_database_CursorWindow.cpp",
-                "android_database_SQLiteCommon.cpp",
-                "android_database_SQLiteConnection.cpp",
-                "android_database_SQLiteGlobal.cpp",
-                "android_database_SQLiteDebug.cpp",
-                "android_database_SQLiteRawStatement.cpp",
                 "android_hardware_input_InputApplicationHandle.cpp",
                 "android_os_MessageQueue.cpp",
                 "android_os_Parcel.cpp",
@@ -463,7 +458,6 @@
             ],
             static_libs: [
                 "libbinderthreadstateutils",
-                "libsqlite",
                 "libgui_window_info_static",
             ],
             shared_libs: [
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index c0e9215..18c3146 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -38,7 +38,9 @@
 #define LOG_NDEBUG 1
 
 #include <androidfw/CursorWindow.h>
+#ifdef __linux__
 #include "android_os_Parcel.h"
+#endif
 #include "android_util_Binder.h"
 #include "android_database_SQLiteCommon.h"
 
@@ -111,6 +113,7 @@
     return 0;
 }
 
+#ifdef __linux__
 static jlong nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
     Parcel* parcel = parcelForJavaObject(env, parcelObj);
 
@@ -128,6 +131,7 @@
             window->getNumRows(), window->getNumColumns(), window);
     return reinterpret_cast<jlong>(window);
 }
+#endif
 
 static void nativeDispose(JNIEnv* env, jclass clazz, jlong windowPtr) {
     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
@@ -142,6 +146,7 @@
     return env->NewStringUTF(window->name().c_str());
 }
 
+#ifdef __linux__
 static void nativeWriteToParcel(JNIEnv * env, jclass clazz, jlong windowPtr,
         jobject parcelObj) {
     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
@@ -154,6 +159,7 @@
         jniThrowRuntimeException(env, msg.c_str());
     }
 }
+#endif
 
 static void nativeClear(JNIEnv * env, jclass clazz, jlong windowPtr) {
     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
@@ -520,55 +526,35 @@
     return true;
 }
 
-static const JNINativeMethod sMethods[] =
-{
-    /* name, signature, funcPtr */
-    { "nativeCreate", "(Ljava/lang/String;I)J",
-            (void*)nativeCreate },
-    { "nativeCreateFromParcel", "(Landroid/os/Parcel;)J",
-            (void*)nativeCreateFromParcel },
-    { "nativeDispose", "(J)V",
-            (void*)nativeDispose },
-    { "nativeWriteToParcel", "(JLandroid/os/Parcel;)V",
-            (void*)nativeWriteToParcel },
+static const JNINativeMethod sMethods[] = {
+        /* name, signature, funcPtr */
+        {"nativeCreate", "(Ljava/lang/String;I)J", (void*)nativeCreate},
+        {"nativeDispose", "(J)V", (void*)nativeDispose},
+#ifdef __linux__
+        {"nativeCreateFromParcel", "(Landroid/os/Parcel;)J", (void*)nativeCreateFromParcel},
+        {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+#endif
+        {"nativeGetName", "(J)Ljava/lang/String;", (void*)nativeGetName},
+        {"nativeGetBlob", "(JII)[B", (void*)nativeGetBlob},
+        {"nativeGetString", "(JII)Ljava/lang/String;", (void*)nativeGetString},
+        {"nativeCopyStringToBuffer", "(JIILandroid/database/CharArrayBuffer;)V",
+         (void*)nativeCopyStringToBuffer},
+        {"nativePutBlob", "(J[BII)Z", (void*)nativePutBlob},
+        {"nativePutString", "(JLjava/lang/String;II)Z", (void*)nativePutString},
 
-    { "nativeGetName", "(J)Ljava/lang/String;",
-            (void*)nativeGetName },
-    { "nativeGetBlob", "(JII)[B",
-            (void*)nativeGetBlob },
-    { "nativeGetString", "(JII)Ljava/lang/String;",
-            (void*)nativeGetString },
-    { "nativeCopyStringToBuffer", "(JIILandroid/database/CharArrayBuffer;)V",
-            (void*)nativeCopyStringToBuffer },
-    { "nativePutBlob", "(J[BII)Z",
-            (void*)nativePutBlob },
-    { "nativePutString", "(JLjava/lang/String;II)Z",
-            (void*)nativePutString },
+        // ------- @FastNative below here ----------------------
+        {"nativeClear", "(J)V", (void*)nativeClear},
+        {"nativeGetNumRows", "(J)I", (void*)nativeGetNumRows},
+        {"nativeSetNumColumns", "(JI)Z", (void*)nativeSetNumColumns},
+        {"nativeAllocRow", "(J)Z", (void*)nativeAllocRow},
+        {"nativeFreeLastRow", "(J)V", (void*)nativeFreeLastRow},
+        {"nativeGetType", "(JII)I", (void*)nativeGetType},
+        {"nativeGetLong", "(JII)J", (void*)nativeGetLong},
+        {"nativeGetDouble", "(JII)D", (void*)nativeGetDouble},
 
-    // ------- @FastNative below here ----------------------
-    { "nativeClear", "(J)V",
-            (void*)nativeClear },
-    { "nativeGetNumRows", "(J)I",
-            (void*)nativeGetNumRows },
-    { "nativeSetNumColumns", "(JI)Z",
-            (void*)nativeSetNumColumns },
-    { "nativeAllocRow", "(J)Z",
-            (void*)nativeAllocRow },
-    { "nativeFreeLastRow", "(J)V",
-            (void*)nativeFreeLastRow },
-    { "nativeGetType", "(JII)I",
-            (void*)nativeGetType },
-    { "nativeGetLong", "(JII)J",
-            (void*)nativeGetLong },
-    { "nativeGetDouble", "(JII)D",
-            (void*)nativeGetDouble },
-
-    { "nativePutLong", "(JJII)Z",
-            (void*)nativePutLong },
-    { "nativePutDouble", "(JDII)Z",
-            (void*)nativePutDouble },
-    { "nativePutNull", "(JII)Z",
-            (void*)nativePutNull },
+        {"nativePutLong", "(JJII)Z", (void*)nativePutLong},
+        {"nativePutDouble", "(JDII)Z", (void*)nativePutDouble},
+        {"nativePutNull", "(JII)Z", (void*)nativePutNull},
 };
 
 int register_android_database_CursorWindow(JNIEnv* env)
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 17c89f8..71ba214 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -48,6 +48,7 @@
 #include <ui/DisplayMode.h>
 #include <ui/DisplayedFrameStats.h>
 #include <ui/DynamicDisplayInfo.h>
+#include <ui/FloatRect.h>
 #include <ui/FrameStats.h>
 #include <ui/GraphicTypes.h>
 #include <ui/HdrCapabilities.h>
@@ -992,6 +993,15 @@
     transaction->setCrop(ctrl, crop);
 }
 
+static void nativeSetCrop(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+                          jfloat l, jfloat t, jfloat r, jfloat b) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    FloatRect crop(l, t, r, b);
+    transaction->setCrop(ctrl, crop);
+}
+
 static void nativeSetCornerRadius(JNIEnv* env, jclass clazz, jlong transactionObj,
          jlong nativeObject, jfloat cornerRadius) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -2347,6 +2357,8 @@
             (void*)nativeSetFrameRateSelectionPriority },
     {"nativeSetWindowCrop", "(JJIIII)V",
             (void*)nativeSetWindowCrop },
+    {"nativeSetCrop", "(JJFFFF)V",
+            (void*)nativeSetCrop },
     {"nativeSetCornerRadius", "(JJF)V",
             (void*)nativeSetCornerRadius },
     {"nativeSetBackgroundBlurRadius", "(JJI)V",
diff --git a/core/jni/jni_wrappers.h b/core/jni/jni_wrappers.h
index 21b5b13..e3e17ee 100644
--- a/core/jni/jni_wrappers.h
+++ b/core/jni/jni_wrappers.h
@@ -79,13 +79,14 @@
     jniMethodFormat = value;
 }
 
-// Potentially translates the given JNINativeMethods if setJniMethodFormat has been set.
-// Has no effect otherwise
-inline const JNINativeMethod* maybeRenameJniMethods(const JNINativeMethod* gMethods,
-                                                    int numMethods) {
+// Register the native methods, potenially applying the jniMethodFormat if it has been set.
+static inline int jniRegisterMaybeRenamedNativeMethods(JNIEnv* env, const char* className,
+                                                       const JNINativeMethod* gMethods,
+                                                       int numMethods) {
     if (jniMethodFormat.empty()) {
-        return gMethods;
+        return jniRegisterNativeMethods(env, className, gMethods, numMethods);
     }
+
     // Make a copy of gMethods with reformatted method names.
     JNINativeMethod* modifiedMethods = new JNINativeMethod[numMethods];
     LOG_ALWAYS_FATAL_IF(!modifiedMethods, "Failed to allocate a copy of the JNI methods");
@@ -103,13 +104,17 @@
         std::strcpy(modifiedNameChars, modifiedName.c_str());
         modifiedMethods[i].name = modifiedNameChars;
     }
-    return modifiedMethods;
+    int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods);
+    for (int i = 0; i < numMethods; i++) {
+        delete[] modifiedMethods[i].name;
+    }
+    delete[] modifiedMethods;
+    return res;
 }
 
 static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
                                        const JNINativeMethod* gMethods, int numMethods) {
-    const JNINativeMethod* modifiedMethods = maybeRenameJniMethods(gMethods, numMethods);
-    int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods);
+    int res = jniRegisterMaybeRenamedNativeMethods(env, className, gMethods, numMethods);
     LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
     return res;
 }
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 19f8299..88b3e1c 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -115,6 +115,9 @@
 #ifdef __linux__
         {"android.content.res.ApkAssets", REG_JNI(register_android_content_res_ApkAssets)},
         {"android.content.res.AssetManager", REG_JNI(register_android_content_AssetManager)},
+        {"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)},
+        {"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)},
+#endif
         {"android.database.CursorWindow", REG_JNI(register_android_database_CursorWindow)},
         {"android.database.sqlite.SQLiteConnection",
          REG_JNI(register_android_database_SQLiteConnection)},
@@ -122,9 +125,6 @@
         {"android.database.sqlite.SQLiteDebug", REG_JNI(register_android_database_SQLiteDebug)},
         {"android.database.sqlite.SQLiteRawStatement",
          REG_JNI(register_android_database_SQLiteRawStatement)},
-#endif
-        {"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)},
-        {"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)},
 #ifdef __linux__
         {"android.os.Binder", REG_JNI(register_android_os_Binder)},
         {"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ed33ede..f067b51 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -792,6 +792,7 @@
     <protected-broadcast android:name="com.android.cellbroadcastreceiver.GET_LATEST_CB_AREA_INFO" />
     <protected-broadcast android:name="com.android.internal.telephony.ACTION_CARRIER_CERTIFICATE_DOWNLOAD" />
     <protected-broadcast android:name="com.android.internal.telephony.action.COUNTRY_OVERRIDE" />
+    <protected-broadcast android:name="com.android.internal.telephony.action.SILENCE_WIFI_CALLING_NOTIFICATION"/>
     <protected-broadcast android:name="com.android.internal.telephony.OPEN_DEFAULT_SMS_APP" />
     <protected-broadcast android:name="com.android.internal.telephony.ACTION_TEST_OVERRIDE_CARRIER_ID" />
     <protected-broadcast android:name="android.telephony.action.SIM_CARD_STATE_CHANGED" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1a3a30d..b90ee2b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1529,6 +1529,11 @@
         factory reset. -->
     <bool name="config_enableCredentialFactoryResetProtection">true</bool>
 
+    <!-- If true, then work around broken Weaver HALs that don't work reliably before the device has
+         fully booted. Setting this to true weakens a security feature; it should be done only when
+         necessary, though it is still better than not using Weaver at all. -->
+    <bool name="config_disableWeaverOnUnsecuredUsers">false</bool>
+
     <!-- Control the behavior when the user long presses the home button.
             0 - Nothing
             1 - Launch all apps intent
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d634210..7aca535 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5818,16 +5818,6 @@
 
     <!-- Title for the notification channel notifying user of settings system changes. [CHAR LIMIT=NONE] -->
     <string name="notification_channel_system_changes">System changes</string>
-    <!-- Title for the notification channel notifying user of do not disturb system changes (i.e. Do Not Disturb has changed). [CHAR LIMIT=NONE] -->
-    <string name="notification_channel_do_not_disturb">Do Not Disturb</string>
-    <!-- Title of notification indicating do not disturb visual interruption settings have changed when upgrading to P -->
-    <string name="zen_upgrade_notification_visd_title">New: Do Not Disturb is hiding notifications</string>
-    <!-- Content of notification indicating users can tap on the notification to go to dnd behavior settings -->
-    <string name="zen_upgrade_notification_visd_content">Tap to learn more and change.</string>
-    <!-- Title of notification indicating do not disturb settings have changed when upgrading to P -->
-    <string name="zen_upgrade_notification_title">Do Not Disturb has changed</string>
-    <!-- Content of notification indicating users can tap on the notification to go to dnd behavior settings -->
-    <string name="zen_upgrade_notification_content">Tap to check what\'s blocked.</string>
 
     <!-- Notification permission informational notification text -->
     <!-- Title for notification inviting users to review their notification settings [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0ccef91..c50c336 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3937,7 +3937,6 @@
   <java-symbol type="string" name="notification_channel_usb" />
   <java-symbol type="string" name="notification_channel_heavy_weight_app" />
   <java-symbol type="string" name="notification_channel_system_changes" />
-  <java-symbol type="string" name="notification_channel_do_not_disturb" />
   <java-symbol type="string" name="notification_channel_accessibility_magnification" />
   <java-symbol type="string" name="notification_channel_accessibility_security_policy" />
   <java-symbol type="string" name="notification_channel_display" />
@@ -3989,6 +3988,7 @@
   <java-symbol type="string" name="foreground_service_multiple_separator" />
 
   <java-symbol type="bool" name="config_enableCredentialFactoryResetProtection" />
+  <java-symbol type="bool" name="config_disableWeaverOnUnsecuredUsers" />
 
   <!-- ETWS primary messages -->
   <java-symbol type="string" name="etws_primary_default_message_earthquake" />
@@ -4164,11 +4164,6 @@
   <!-- For Wear devices -->
   <java-symbol type="array" name="config_wearActivityModeRadios" />
 
-  <java-symbol type="string" name="zen_upgrade_notification_title" />
-  <java-symbol type="string" name="zen_upgrade_notification_content" />
-  <java-symbol type="string" name="zen_upgrade_notification_visd_title" />
-  <java-symbol type="string" name="zen_upgrade_notification_visd_content" />
-
   <java-symbol type="string" name="review_notification_settings_title" />
   <java-symbol type="string" name="review_notification_settings_text" />
   <java-symbol type="string" name="review_notification_settings_remind_me_action" />
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index e9b137c..48e2620 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -2301,13 +2301,13 @@
 
     @Test
     @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
-    public void progressStyle_onProgressStepChange_visiblyDifferent() {
+    public void progressStyle_onProgressPointChange_visiblyDifferent() {
         final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
                 .setStyle(new Notification.ProgressStyle()
-                        .addProgressStep(new Notification.ProgressStyle.Step(10)));
+                        .addProgressPoint(new Notification.ProgressStyle.Point(10)));
         final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
                 .setStyle(new Notification.ProgressStyle()
-                        .addProgressStep(new Notification.ProgressStyle.Step(12)));
+                        .addProgressPoint(new Notification.ProgressStyle.Point(12)));
 
         assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
                 .isTrue();
@@ -2315,13 +2315,13 @@
 
     @Test
     @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
-    public void indeterminateProgressStyle_onProgressStepChange_visiblyNotDifferent() {
+    public void indeterminateProgressStyle_onProgressPointChange_visiblyNotDifferent() {
         final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
                 .setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
-                        .addProgressStep(new Notification.ProgressStyle.Step(10)));
+                        .addProgressPoint(new Notification.ProgressStyle.Point(10)));
         final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
                 .setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
-                        .addProgressStep(new Notification.ProgressStyle.Step(12)));
+                        .addProgressPoint(new Notification.ProgressStyle.Point(12)));
 
         assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
                 .isFalse();
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 3eefe04..b16c237 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -119,7 +119,7 @@
             }
 
             @Override
-            protected DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments daj) {
+            public DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments daj) {
                 return mDisplayMetricsMap.get(displayId);
             }
         };
@@ -470,6 +470,48 @@
 
     @Test
     @SmallTest
+    public void testResourceConfigurationAppliedWhenOverrideDoesNotExist() {
+        final int width = 240;
+        final int height = 360;
+        final float densityDpi = mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).densityDpi;
+        final int widthDp = (int) (width / densityDpi + 0.5f);
+        final int heightDp = (int) (height / densityDpi + 0.5f);
+
+        final int overrideWidth = 480;
+        final int overrideHeight = 720;
+        final int overrideWidthDp = (int) (overrideWidth / densityDpi + 0.5f);
+        final int overrideHeightDp = (int) (height / densityDpi + 0.5f);
+
+        // The method to be tested is overridden for other tests to provide a setup environment.
+        // Create a new one for this test only.
+        final ResourcesManager resourcesManager = new ResourcesManager();
+
+        Configuration newConfig = new Configuration();
+        newConfig.windowConfiguration.setAppBounds(0, 0, width, height);
+        newConfig.screenWidthDp = widthDp;
+        newConfig.screenHeightDp = heightDp;
+        resourcesManager.applyConfigurationToResources(newConfig, null);
+
+        assertEquals(width, resourcesManager.getDisplayMetrics(Display.DEFAULT_DISPLAY,
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS).widthPixels);
+        assertEquals(height, resourcesManager.getDisplayMetrics(Display.DEFAULT_DISPLAY,
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS).heightPixels);
+
+        Configuration overrideConfig = new Configuration();
+        overrideConfig.windowConfiguration.setAppBounds(0, 0, overrideWidth, overrideHeight);
+        overrideConfig.screenWidthDp = overrideWidthDp;
+        overrideConfig.screenHeightDp = overrideHeightDp;
+
+        final DisplayAdjustments daj = new DisplayAdjustments(overrideConfig);
+
+        assertEquals(overrideWidth, resourcesManager.getDisplayMetrics(
+                Display.DEFAULT_DISPLAY, daj).widthPixels);
+        assertEquals(overrideHeight, resourcesManager.getDisplayMetrics(
+                Display.DEFAULT_DISPLAY, daj).heightPixels);
+    }
+
+    @Test
+    @SmallTest
     @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
     @DisabledOnRavenwood(blockedBy = PackageManager.class)
     public void testNewResourcesWithOutdatedImplAfterResourcePathsRegistration()
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 0bda0ff..0a4c5e6 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -18,6 +18,9 @@
 
 import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
 import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY;
+import static android.window.OnBackInvokedDispatcher.PRIORITY_SYSTEM_NAVIGATION_OBSERVER;
+
+import static com.android.window.flags.Flags.FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -39,6 +42,10 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.view.IWindow;
 import android.view.IWindowSession;
 import android.view.ImeBackAnimationController;
@@ -80,6 +87,8 @@
 
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Mock
     private IWindowSession mWindowSession;
@@ -145,7 +154,8 @@
         assertEquals("No setOnBackInvokedCallbackInfo", mCallbackInfoCalls, actual);
     }
 
-    private void assertCallbacksSize(int expectedDefault, int expectedOverlay) {
+    private void assertCallbacksSize(int expectedDefault, int expectedOverlay,
+            int expectedObserver) {
         ArrayList<OnBackInvokedCallback> callbacksDefault = mDispatcher
                 .mOnBackInvokedCallbacks.get(PRIORITY_DEFAULT);
         int actualSizeDefault = callbacksDefault != null ? callbacksDefault.size() : 0;
@@ -155,6 +165,10 @@
                 .mOnBackInvokedCallbacks.get(PRIORITY_OVERLAY);
         int actualSizeOverlay = callbacksOverlay != null ? callbacksOverlay.size() : 0;
         assertEquals("mOnBackInvokedCallbacks OVERLAY size", expectedOverlay, actualSizeOverlay);
+
+        int actualSizeObserver = mDispatcher.mSystemNavigationObserverCallback == null ? 0 : 1;
+        assertEquals("mOnBackInvokedCallbacks SYSTEM_NAVIGATION_OBSERVER size", expectedObserver,
+                actualSizeObserver);
     }
 
     private void assertTopCallback(OnBackInvokedCallback expectedCallback) {
@@ -164,13 +178,13 @@
     @Test
     public void registerCallback_samePriority_sameCallback() throws RemoteException {
         mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
-        assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+        assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
         assertSetCallbackInfo();
         assertTopCallback(mCallback1);
 
         // The callback is removed and added again
         mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
-        assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+        assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
         assertSetCallbackInfo();
         assertTopCallback(mCallback1);
 
@@ -182,13 +196,13 @@
     @Test
     public void registerCallback_samePriority_differentCallback() throws RemoteException {
         mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
-        assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+        assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
         assertSetCallbackInfo();
         assertTopCallback(mCallback1);
 
         // The new callback becomes the TopCallback
         mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
-        assertCallbacksSize(/* default */ 2, /* overlay */ 0);
+        assertCallbacksSize(/* default */ 2, /* overlay */ 0, /* observer */ 0);
         assertSetCallbackInfo();
         assertTopCallback(mCallback2);
 
@@ -201,13 +215,13 @@
     @Test
     public void registerCallback_differentPriority_sameCallback() throws RemoteException {
         mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
-        assertCallbacksSize(/* default */ 0, /* overlay */ 1);
+        assertCallbacksSize(/* default */ 0, /* overlay */ 1, /* observer */ 0);
         assertSetCallbackInfo();
         assertTopCallback(mCallback1);
 
         // The callback is moved to the new priority list
         mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
-        assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+        assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
         assertSetCallbackInfo();
         assertTopCallback(mCallback1);
 
@@ -220,13 +234,13 @@
     public void registerCallback_differentPriority_differentCallback() throws RemoteException {
         mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
         assertSetCallbackInfo();
-        assertCallbacksSize(/* default */ 0, /* overlay */ 1);
+        assertCallbacksSize(/* default */ 0, /* overlay */ 1, /* observer */ 0);
         assertTopCallback(mCallback1);
 
         // The callback with higher priority is still the TopCallback
         mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
         assertNoSetCallbackInfo();
-        assertCallbacksSize(/* default */ 1, /* overlay */ 1);
+        assertCallbacksSize(/* default */ 1, /* overlay */ 1, /* observer */ 0);
         assertTopCallback(mCallback1);
 
         waitForIdle();
@@ -238,22 +252,22 @@
     @Test
     public void registerCallback_sameInstanceAddedTwice() throws RemoteException {
         mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
-        assertCallbacksSize(/* default */ 0, /* overlay */ 1);
+        assertCallbacksSize(/* default */ 0, /* overlay */ 1, /* observer */ 0);
         assertSetCallbackInfo();
         assertTopCallback(mCallback1);
 
         mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
-        assertCallbacksSize(/* default */ 1, /* overlay */ 1);
+        assertCallbacksSize(/* default */ 1, /* overlay */ 1, /* observer */ 0);
         assertNoSetCallbackInfo();
         assertTopCallback(mCallback1);
 
         mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
-        assertCallbacksSize(/* default */ 2, /* overlay */ 0);
+        assertCallbacksSize(/* default */ 2, /* overlay */ 0, /* observer */ 0);
         assertSetCallbackInfo();
         assertTopCallback(mCallback1);
 
         mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback2);
-        assertCallbacksSize(/* default */ 1, /* overlay */ 1);
+        assertCallbacksSize(/* default */ 1, /* overlay */ 1, /* observer */ 0);
         assertSetCallbackInfo();
         assertTopCallback(mCallback2);
 
@@ -570,6 +584,102 @@
         assertFalse(mDispatcher.mProgressAnimator.isBackAnimationInProgress());
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    @RequiresFlagsDisabled(FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER)
+    public void testNoUiCallback_registrationFailsWithoutFlaggedApiEnabled() {
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER)
+    public void testNoUiCallback_invokedWithSystemCallback() throws RemoteException {
+        mDispatcher.registerSystemOnBackInvokedCallback(mCallback1);
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2);
+
+        assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 1);
+        OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
+        assertTopCallback(mCallback1);
+
+        callbackInfo.getCallback().onBackStarted(mBackEvent);
+        waitForIdle();
+        verify(mCallback1).onBackStarted(any());
+        verify(mCallback2, never()).onBackStarted(any());
+
+        callbackInfo.getCallback().onBackProgressed(mBackEvent);
+        waitForIdle();
+        verify(mCallback1).onBackProgressed(any());
+        verify(mCallback2, never()).onBackProgressed(any());
+
+        callbackInfo.getCallback().onBackCancelled();
+        waitForIdle();
+        verify(mCallback1, timeout(1000)).onBackCancelled();
+        verify(mCallback2, never()).onBackCancelled();
+
+        // start new gesture to test onBackInvoked case
+        callbackInfo.getCallback().onBackStarted(mBackEvent);
+        callbackInfo.getCallback().onBackInvoked();
+        waitForIdle();
+        verify(mCallback1, timeout(1000)).onBackInvoked();
+        verify(mCallback2).onBackInvoked();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER)
+    public void testNoUiCallback_notInvokedWithNonSystemCallback() throws RemoteException {
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2);
+
+        assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 1);
+        OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
+        assertTopCallback(mCallback1);
+
+        callbackInfo.getCallback().onBackStarted(mBackEvent);
+        waitForIdle();
+        verify(mCallback1).onBackStarted(any());
+        verify(mCallback2, never()).onBackStarted(any());
+
+        callbackInfo.getCallback().onBackProgressed(mBackEvent);
+        waitForIdle();
+        verify(mCallback1).onBackProgressed(any());
+        verify(mCallback2, never()).onBackProgressed(any());
+
+        callbackInfo.getCallback().onBackCancelled();
+        waitForIdle();
+        verify(mCallback1, timeout(1000)).onBackCancelled();
+        verify(mCallback2, never()).onBackCancelled();
+
+        // start new gesture to test onBackInvoked case
+        callbackInfo.getCallback().onBackStarted(mBackEvent);
+        callbackInfo.getCallback().onBackInvoked();
+        waitForIdle();
+        verify(mCallback1, timeout(1000)).onBackInvoked();
+        verify(mCallback2, never()).onBackInvoked();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER)
+    public void testNoUiCallback_reregistrations() {
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback1);
+        assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 1);
+        assertEquals(mCallback1, mDispatcher.mSystemNavigationObserverCallback);
+
+        // test reregistration of observer-callback as observer-callback
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2);
+        assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 1);
+        assertEquals(mCallback2, mDispatcher.mSystemNavigationObserverCallback);
+
+        // test reregistration of observer-callback as regular callback
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
+        assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
+
+        // test reregistration of regular callback as observer-callback
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2);
+        assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 1);
+
+        mDispatcher.unregisterOnBackInvokedCallback(mCallback2);
+        assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 0);
+    }
+
     private BackMotionEvent backMotionEventFrom(float progress) {
         return new BackMotionEvent(
                 /* touchX = */ 0,
@@ -585,13 +695,13 @@
     private void verifyImeCallackRegistrations() throws RemoteException {
         // verify default callback is replaced with ImeBackAnimationController
         mDispatcher.registerOnBackInvokedCallbackUnchecked(mDefaultImeCallback, PRIORITY_DEFAULT);
-        assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+        assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0);
         assertSetCallbackInfo();
         assertTopCallback(mImeBackAnimationController);
 
         // verify regular ime callback is successfully registered
         mDispatcher.registerOnBackInvokedCallbackUnchecked(mImeCallback, PRIORITY_DEFAULT);
-        assertCallbacksSize(/* default */ 2, /* overlay */ 0);
+        assertCallbacksSize(/* default */ 2, /* overlay */ 0, /* observer */ 0);
         assertSetCallbackInfo();
         assertTopCallback(mImeCallback);
     }
diff --git a/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java b/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java
new file mode 100644
index 0000000..0bf406c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.notification;
+
+import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS;
+import static com.android.internal.notification.SystemNotificationChannels.ACCESSIBILITY_MAGNIFICATION;
+import static com.android.internal.notification.SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY;
+import static com.android.internal.notification.SystemNotificationChannels.ACCOUNT;
+import static com.android.internal.notification.SystemNotificationChannels.ALERTS;
+import static com.android.internal.notification.SystemNotificationChannels.CAR_MODE;
+import static com.android.internal.notification.SystemNotificationChannels.DEVELOPER;
+import static com.android.internal.notification.SystemNotificationChannels.DEVELOPER_IMPORTANT;
+import static com.android.internal.notification.SystemNotificationChannels.DEVICE_ADMIN;
+import static com.android.internal.notification.SystemNotificationChannels.FOREGROUND_SERVICE;
+import static com.android.internal.notification.SystemNotificationChannels.HEAVY_WEIGHT_APP;
+import static com.android.internal.notification.SystemNotificationChannels.NETWORK_ALERTS;
+import static com.android.internal.notification.SystemNotificationChannels.NETWORK_AVAILABLE;
+import static com.android.internal.notification.SystemNotificationChannels.NETWORK_STATUS;
+import static com.android.internal.notification.SystemNotificationChannels.OBSOLETE_DO_NOT_DISTURB;
+import static com.android.internal.notification.SystemNotificationChannels.PHYSICAL_KEYBOARD;
+import static com.android.internal.notification.SystemNotificationChannels.RETAIL_MODE;
+import static com.android.internal.notification.SystemNotificationChannels.SECURITY;
+import static com.android.internal.notification.SystemNotificationChannels.SYSTEM_CHANGES;
+import static com.android.internal.notification.SystemNotificationChannels.UPDATES;
+import static com.android.internal.notification.SystemNotificationChannels.USB;
+import static com.android.internal.notification.SystemNotificationChannels.VPN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.testing.TestableContext;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class SystemNotificationChannelsTest {
+
+    @Rule public TestableContext mContext = new TestableContext(
+            ApplicationProvider.getApplicationContext());
+
+    @Mock private NotificationManager mNm;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext.addMockSystemService(NotificationManager.class, mNm);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void createAll_createsExpectedChannels() {
+        ArgumentCaptor<List<NotificationChannel>> createdChannelsCaptor =
+                ArgumentCaptor.forClass(List.class);
+
+        SystemNotificationChannels.createAll(mContext);
+
+        verify(mNm).createNotificationChannels(createdChannelsCaptor.capture());
+        List<NotificationChannel> createdChannels = createdChannelsCaptor.getValue();
+        assertThat(createdChannels.stream().map(NotificationChannel::getId).toList())
+                .containsExactly(PHYSICAL_KEYBOARD, SECURITY, CAR_MODE, ACCOUNT, DEVELOPER,
+                        DEVELOPER_IMPORTANT, UPDATES, NETWORK_STATUS, NETWORK_ALERTS,
+                        NETWORK_AVAILABLE, VPN, DEVICE_ADMIN, ALERTS, RETAIL_MODE, USB,
+                        FOREGROUND_SERVICE, HEAVY_WEIGHT_APP, SYSTEM_CHANGES,
+                        ACCESSIBILITY_MAGNIFICATION, ACCESSIBILITY_SECURITY_POLICY,
+                        ABUSIVE_BACKGROUND_APPS);
+    }
+
+    @Test
+    public void createAll_deletesObsoleteChannels() {
+        ArgumentCaptor<String> deletedChannelCaptor = ArgumentCaptor.forClass(String.class);
+
+        SystemNotificationChannels.createAll(mContext);
+
+        verify(mNm, atLeastOnce()).deleteNotificationChannel(deletedChannelCaptor.capture());
+        List<String> deletedChannels = deletedChannelCaptor.getAllValues();
+        assertThat(deletedChannels).containsExactly(OBSOLETE_DO_NOT_DISTURB);
+    }
+}
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
index 9887c27..af26bd0 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
@@ -130,10 +130,6 @@
                             .onDescendantOf("android.content.Context")
                             .withNameMatching(
                                     Pattern.compile("^send(Ordered|Sticky)?Broadcast.*AsUser.*$")));
-    private static final Matcher<ExpressionTree> SEND_PENDING_INTENT = methodInvocation(
-            instanceMethod()
-                    .onDescendantOf("android.app.PendingIntent")
-                    .named("send"));
 
     private static final Matcher<ExpressionTree> INTENT_SET_ACTION = methodInvocation(
             instanceMethod().onDescendantOf("android.content.Intent").named("setAction"));
diff --git a/libs/WindowManager/Shell/res/color/open_by_default_settings_dialog_radio_button_color.xml b/libs/WindowManager/Shell/res/color/open_by_default_settings_dialog_radio_button_color.xml
new file mode 100644
index 0000000..0f9b28a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/open_by_default_settings_dialog_radio_button_color.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_checked="true"
+        android:color="?androidprv:attr/materialColorPrimaryContainer"/>
+    <item android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+</selector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_by_default_settings.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_by_default_settings.xml
new file mode 100644
index 0000000..4070c3d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_by_default_settings.xml
@@ -0,0 +1,25 @@
+<?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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+    <path android:fillColor="@android:color/black"
+        android:pathData="M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_background.xml b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_background.xml
new file mode 100644
index 0000000..4eb2271
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<shape android:shape="rectangle"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <solid android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+    <corners android:radius="28dp"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_dismiss_button_background.xml b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_dismiss_button_background.xml
new file mode 100644
index 0000000..2b2e9df
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_dismiss_button_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+    <solid android:color="?androidprv:attr/materialColorPrimary"/>
+    <corners android:radius="50dp"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_radio_buttons_background.xml b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_radio_buttons_background.xml
new file mode 100644
index 0000000..1ac952b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_radio_buttons_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<shape android:shape="rectangle"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/open_by_default_settings_dialog_radio_button_color"/>
+    <corners android:radius="16dp"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 6913e54..aeb734e 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -165,17 +165,28 @@
         android:layout_height="@dimen/desktop_mode_handle_menu_open_in_browser_pill_height"
         android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
         android:layout_marginStart="1dp"
-        android:orientation="vertical"
+        android:orientation="horizontal"
         android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
         android:background="@drawable/desktop_mode_decor_handle_menu_background">
 
         <Button
             android:id="@+id/open_in_browser_button"
+            android:layout_weight="1"
             android:contentDescription="@string/open_in_browser_text"
             android:text="@string/open_in_browser_text"
             android:drawableStart="@drawable/desktop_mode_ic_handle_menu_open_in_browser"
             android:drawableTint="?androidprv:attr/materialColorOnSurface"
             style="@style/DesktopModeHandleMenuActionButton"/>
+
+        <ImageButton
+            android:id="@+id/open_by_default_button"
+            android:layout_width="20dp"
+            android:layout_height="20dp"
+            android:layout_gravity="end|center_vertical"
+            android:layout_marginEnd="16dp"
+            android:contentDescription="@string/open_by_default_settings_text"
+            android:src="@drawable/desktop_mode_ic_handle_menu_open_by_default_settings"
+            android:tint="?androidprv:attr/materialColorOnSurface"/>
     </LinearLayout>
 </LinearLayout>
 
diff --git a/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml b/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml
new file mode 100644
index 0000000..8ff382b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml
@@ -0,0 +1,130 @@
+<?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.
+  -->
+
+<com.android.wm.shell.apptoweb.OpenByDefaultDialogView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    style="@style/LetterboxDialog">
+
+    <!-- The background of the top-level layout acts as the background dim. -->
+    <FrameLayout
+        android:id="@+id/open_by_default_dialog_container"
+        android:layout_width="@dimen/open_by_default_settings_dialog_width"
+        android:layout_height="wrap_content"
+        android:background="@drawable/open_by_default_settings_dialog_background"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent">
+
+        <!-- The ScrollView should only wrap the content of the dialog, otherwise the background
+             corner radius will be cut off when scrolling to the top/bottom. -->
+        <ScrollView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <LinearLayout
+                android:padding="24dp"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical">
+
+                <ImageView
+                    android:id="@+id/application_icon"
+                    android:layout_width="32dp"
+                    android:layout_height="32dp"
+                    android:layout_gravity="center_horizontal"
+                    android:importantForAccessibility="no"
+                    android:layout_marginTop="24dp"
+                    android:layout_marginBottom="16dp"
+                    android:scaleType="centerCrop"/>
+
+                <TextView
+                    android:id="@+id/application_name"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_horizontal"
+                    android:layout_marginBottom="8dp"
+                    android:lineHeight="32dp"
+                    android:textFontWeight="400"
+                    android:textSize="24sp"
+                    android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+                    tools:text="Gmail" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textSize="12sp"
+                    android:textFontWeight="400"
+                    android:lineHeight="16dp"
+                    android:layout_gravity="center_horizontal"
+                    android:layout_marginBottom="16dp"
+                    android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+                    android:text="@string/open_by_default_dialog_subheader_text"/>
+
+                <RadioGroup
+                    android:id="@+id/open_by_default_radio_group"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_horizontal">
+                    <RadioButton
+                        android:id="@+id/open_in_app_button"
+                        android:layout_width="@dimen/open_by_default_settings_dialog_radio_button_width"
+                        android:layout_height="@dimen/open_by_default_settings_dialog_radio_button_height"
+                        android:paddingStart="20dp"
+                        android:paddingEnd="0dp"
+                        android:layout_marginHorizontal="16dp"
+                        android:layout_marginBottom="4dp"
+                        android:text="@string/open_by_default_dialog_in_app_text"
+                        android:textFontWeight="500"
+                        android:textSize="16sp"
+                        android:lineHeight="24dp"
+                        android:background="@drawable/open_by_default_settings_dialog_radio_buttons_background"/>
+                    <RadioButton
+                        android:id="@+id/open_in_browser_button"
+                        android:layout_width="@dimen/open_by_default_settings_dialog_radio_button_width"
+                        android:layout_height="@dimen/open_by_default_settings_dialog_radio_button_height"
+                        android:paddingStart="20dp"
+                        android:paddingEnd="0dp"
+                        android:layout_marginStart="16dp"
+                        android:text="@string/open_by_default_dialog_in_browser_text"
+                        android:textFontWeight="500"
+                        android:textSize="16sp"
+                        android:lineHeight="24dp"
+                        android:background="@drawable/open_by_default_settings_dialog_radio_buttons_background"/>
+                </RadioGroup>
+
+                <Button
+                    android:id="@+id/open_by_default_settings_dialog_dismiss_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="36dp"
+                    android:text="@string/open_by_default_dialog_dismiss_button_text"
+                    android:layout_gravity="end"
+                    android:layout_marginHorizontal="24dp"
+                    android:layout_marginTop="32dp"
+                    android:layout_marginBottom="24dp"
+                    android:textSize="14sp"
+                    android:textFontWeight="500"
+                    android:textColor="?androidprv:attr/materialColorOnPrimary"
+                    android:background="@drawable/open_by_default_settings_dialog_dismiss_button_background"/>
+            </LinearLayout>
+        </ScrollView>
+    </FrameLayout>
+</com.android.wm.shell.apptoweb.OpenByDefaultDialogView>
+
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index c7109f5..1f15651 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -624,4 +624,12 @@
     <!-- The offset from the left edge of the entering page for the cross-activity animation -->
     <dimen name="cross_activity_back_entering_start_offset">96dp</dimen>
 
+    <!-- The open by default settings dialog menu width. -->
+    <dimen name="open_by_default_settings_dialog_width">348dp</dimen>
+    <!-- The open by default settings dialog menu height. -->
+    <dimen name="open_by_default_settings_dialog_height">380dp</dimen>
+    <!-- The height of radio buttons in the open by default settings dialog. -->
+    <dimen name="open_by_default_settings_dialog_radio_button_height">64dp</dimen>
+    <!-- The width of radio buttons in the open by default settings dialog. -->
+    <dimen name="open_by_default_settings_dialog_radio_button_width">316dp</dimen>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 56f25da..5ef8432 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -323,4 +323,15 @@
     <string name="desktop_mode_maximize_menu_snap_left_button_text">Snap left</string>
     <!-- Accessibility text for the Maximize Menu's snap right button [CHAR LIMIT=NONE] -->
     <string name="desktop_mode_maximize_menu_snap_right_button_text">Snap right</string>
+
+    <!-- Accessibility text for open by default settings button [CHAR LIMIT=NONE] -->
+    <string name="open_by_default_settings_text">Open by default settings</string>
+    <!-- Subheader for open by default menu string. -->
+    <string name="open_by_default_dialog_subheader_text">Choose how to open web links for this app</string>
+    <!-- Text for open by default settings dialog option. -->
+    <string name="open_by_default_dialog_in_app_text">In the app</string>
+    <!-- Text for open by default settings dialog option. -->
+    <string name="open_by_default_dialog_in_browser_text">In your browser</string>
+    <!-- Text for open by default settings dialog dismiss button. -->
+    <string name="open_by_default_dialog_dismiss_button_text">OK</string>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt
new file mode 100644
index 0000000..4926cbd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.apptoweb
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.TaskInfo
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.PixelFormat
+import android.view.LayoutInflater
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
+import android.view.WindowlessWindowManager
+import android.widget.ImageView
+import android.widget.TextView
+import android.window.TaskConstants
+import com.android.wm.shell.R
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
+import java.util.function.Supplier
+
+
+/**
+ * Window manager for the open by default settings dialog
+ */
+internal class OpenByDefaultDialog(
+    private val context: Context,
+    private val taskInfo: TaskInfo,
+    private val taskSurface: SurfaceControl,
+    private val displayController: DisplayController,
+    private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>,
+    private val listener: DialogLifecycleListener,
+    appIconBitmap: Bitmap?,
+    appName: CharSequence?
+) {
+    private lateinit var dialog: OpenByDefaultDialogView
+    private lateinit var viewHost: SurfaceControlViewHost
+    private lateinit var dialogSurfaceControl: SurfaceControl
+    private var dialogContainer: AdditionalViewHostViewContainer? = null
+    private lateinit var appIconView: ImageView
+    private lateinit var appNameView: TextView
+
+    init {
+        createDialog()
+        bindAppInfo(appIconBitmap, appName)
+    }
+
+    /** Creates an open by default settings dialog. */
+    fun createDialog() {
+        val t = SurfaceControl.Transaction()
+        val taskBounds = taskInfo.configuration.windowConfiguration.bounds
+
+        dialog = LayoutInflater.from(context)
+            .inflate(
+                R.layout.open_by_default_settings_dialog,
+                null /* root */
+            ) as OpenByDefaultDialogView
+        appIconView = dialog.requireViewById(R.id.application_icon)
+        appNameView = dialog.requireViewById(R.id.application_name)
+
+        val display = displayController.getDisplay(taskInfo.displayId)
+        val builder: SurfaceControl.Builder = SurfaceControl.Builder()
+        dialogSurfaceControl = builder
+            .setName("Open by Default Dialog of Task=" + taskInfo.taskId)
+            .setContainerLayer()
+            .setParent(taskSurface)
+            .setCallsite("OpenByDefaultDialog#createDialog")
+            .build()
+        t.setPosition(dialogSurfaceControl, 0f, 0f)
+            .setWindowCrop(dialogSurfaceControl, taskBounds.width(), taskBounds.height())
+            .setLayer(dialogSurfaceControl, TaskConstants.TASK_CHILD_LAYER_SETTINGS_DIALOG)
+            .show(dialogSurfaceControl)
+        val lp = WindowManager.LayoutParams(
+            taskBounds.width(),
+            taskBounds.height(),
+            TYPE_APPLICATION_PANEL,
+            FLAG_NOT_FOCUSABLE or FLAG_NOT_TOUCH_MODAL,
+            PixelFormat.TRANSLUCENT)
+        lp.title = "Open by default settings dialog of task=" + taskInfo.taskId
+        lp.setTrustedOverlay()
+        val windowManager = WindowlessWindowManager(
+            taskInfo.configuration,
+            dialogSurfaceControl, null /* hostInputToken */
+        )
+        viewHost = SurfaceControlViewHost(context, display, windowManager, "Dialog").apply {
+            setView(dialog, lp)
+            rootSurfaceControl.applyTransactionOnDraw(t)
+        }
+        dialogContainer = AdditionalViewHostViewContainer(
+            dialogSurfaceControl, viewHost, surfaceControlTransactionSupplier)
+
+        dialog.setDismissOnClickListener{
+            closeMenu()
+        }
+
+        listener.onDialogCreated()
+    }
+
+    private fun closeMenu() {
+        dialogContainer?.releaseView()
+        dialogContainer = null
+        listener.onDialogDismissed()
+    }
+
+     private fun bindAppInfo(
+        appIconBitmap: Bitmap?,
+        appName: CharSequence?
+    ) {
+        appIconView.setImageBitmap(appIconBitmap)
+        appNameView.text = appName
+    }
+
+    /**
+     * Relayout the dialog to the new task bounds.
+     */
+    fun relayout(
+        taskInfo: RunningTaskInfo,
+    ) {
+        val t = surfaceControlTransactionSupplier.get()
+        val taskBounds = taskInfo.configuration.windowConfiguration.bounds
+        t.setWindowCrop(dialogSurfaceControl, taskBounds.width(), taskBounds.height())
+        viewHost.rootSurfaceControl.applyTransactionOnDraw(t)
+        viewHost.relayout(taskBounds.width(), taskBounds.height())
+    }
+
+    /**
+     * Defines interface for classes that can listen to lifecycle events of open by default settings
+     * dialog.
+     */
+    interface DialogLifecycleListener {
+        /** Called when open by default dialog view has been created. */
+        fun onDialogCreated()
+
+        /** Called when open by default dialog view has been released. */
+        fun onDialogDismissed()
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt
new file mode 100644
index 0000000..d03a38e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.apptoweb
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.View
+import android.widget.Button
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.android.wm.shell.R
+
+/** View for open by default settings dialog for an application which allows the user to change
+ * where links will open by default, in the default browser or in the application. */
+class OpenByDefaultDialogView @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) {
+
+    private lateinit var dialogContainer: View
+    private lateinit var backgroundDim: Drawable
+
+    fun setDismissOnClickListener(callback: (View) -> Unit) {
+        val dismissButton = dialogContainer.requireViewById<Button>(
+            R.id.open_by_default_settings_dialog_dismiss_button)
+        dismissButton.setOnClickListener(callback)
+        // Clicks on the background dim should also dismiss the dialog.
+        setOnClickListener(callback)
+        // We add a no-op on-click listener to the dialog container so that clicks on it won't
+        // propagate to the listener of the layout (which represents the background dim).
+        dialogContainer.setOnClickListener { }
+    }
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        dialogContainer = requireViewById(R.id.open_by_default_dialog_container)
+        backgroundDim = background.mutate()
+        backgroundDim.alpha = 128
+    }
+}
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 af4a0c5..a8a8c2a 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
@@ -274,7 +274,8 @@
     private final DragAndDropController mDragAndDropController;
     /** Used to send bubble events to launcher. */
     private Bubbles.BubbleStateListener mBubbleStateListener;
-
+    /** Used to track previous navigation mode to detect switch to buttons navigation. */
+    private boolean mIsPrevNavModeGestures;
     /** Used to send updates to the views from {@link #mBubbleDataListener}. */
     private BubbleViewCallback mBubbleViewCallback;
 
@@ -356,6 +357,7 @@
             }
         };
         mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this);
+        mIsPrevNavModeGestures = ContextUtils.isGestureNavigationMode(mContext);
     }
 
     private void registerOneHandedState(OneHandedController oneHanded) {
@@ -589,6 +591,13 @@
      */
     private void sendInitialListenerUpdate() {
         if (mBubbleStateListener != null) {
+            boolean isCurrentNavModeGestures = ContextUtils.isGestureNavigationMode(mContext);
+            if (mIsPrevNavModeGestures && !isCurrentNavModeGestures) {
+                BubbleBarLocation navButtonsLocation = ContextUtils.isRtl(mContext)
+                        ? BubbleBarLocation.RIGHT : BubbleBarLocation.LEFT;
+                mBubblePositioner.setBubbleBarLocation(navButtonsLocation);
+            }
+            mIsPrevNavModeGestures = isCurrentNavModeGestures;
             BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar();
             mBubbleStateListener.onBubbleStateChange(update);
         }
@@ -2001,6 +2010,10 @@
             // in bubble bar mode, let the request to show the expanded view come from launcher.
             // only collapse here if we're collapsing.
             if (mLayerView != null && !isExpanded) {
+                if (mBubblePositioner.isImeVisible()) {
+                    // If we're collapsing, hide the IME
+                    hideCurrentInputMethod();
+                }
                 mLayerView.collapse();
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 2795881..35a0d07 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -61,7 +61,6 @@
 import android.view.ViewPropertyAnimator;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
-import android.view.WindowManagerPolicyConstants;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.widget.FrameLayout;
@@ -91,10 +90,10 @@
 import com.android.wm.shell.bubbles.animation.StackAnimationController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.shared.bubbles.DismissView;
-import com.android.wm.shell.shared.bubbles.RelativeTouchListener;
 import com.android.wm.shell.shared.animation.Interpolators;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.bubbles.DismissView;
+import com.android.wm.shell.shared.bubbles.RelativeTouchListener;
 import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import java.io.PrintWriter;
@@ -2276,7 +2275,7 @@
     void startMonitoringSwipeUpGesture() {
         stopMonitoringSwipeUpGestureInternal();
 
-        if (isGestureNavEnabled()) {
+        if (ContextUtils.isGestureNavigationMode(mContext)) {
             mBubblesNavBarGestureTracker = new BubblesNavBarGestureTracker(mContext, mPositioner);
             mBubblesNavBarGestureTracker.start(mSwipeUpListener);
             setOnTouchListener(mContainerSwipeListener);
@@ -2311,12 +2310,6 @@
         }
     }
 
-    private boolean isGestureNavEnabled() {
-        return mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_navBarInteractionMode)
-                == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
-    }
-
     /**
      * Stop monitoring for swipe up gesture
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ContextUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ContextUtils.kt
new file mode 100644
index 0000000..0b36f45
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ContextUtils.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.content.Context
+import android.view.View
+import android.view.WindowManagerPolicyConstants
+import com.android.internal.R
+
+/** Simplifies accessing context fields. */
+object ContextUtils {
+
+    /** Gets navigation mode. */
+    @JvmStatic
+    val Context.navigationMode: Int
+        get() = resources.getInteger(R.integer.config_navBarInteractionMode)
+
+    /** Returns whether the navigation mode is gestures. */
+    @JvmStatic
+    val Context.isGestureNavigationMode: Boolean
+        get() = navigationMode == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+
+    /** Returns whether layout direction is rtl. */
+    @JvmStatic
+    val Context.isRtl: Boolean
+        get() = resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index ec235a5..2a90017 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -353,6 +353,11 @@
         if (isDragging != mIsDragging) {
             mIsDragging = isDragging;
             updateSamplingState();
+
+            if (isDragging && mPositioner.isImeVisible()) {
+                // Hide the IME when dragging begins
+                mManager.hideCurrentInputMethod();
+            }
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 0047ec5..38087c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -157,6 +157,14 @@
         }
     }
 
+    private void dispatchImeRequested(int displayId, boolean isRequested) {
+        synchronized (mPositionProcessors) {
+            for (ImePositionProcessor pp : mPositionProcessors) {
+                pp.onImeRequested(displayId, isRequested);
+            }
+        }
+    }
+
     @ImePositionProcessor.ImeAnimationFlags
     private int dispatchStartPositioning(int displayId, int hiddenTop, int shownTop,
             boolean show, boolean isFloating, SurfaceControl.Transaction t) {
@@ -398,6 +406,8 @@
         public void setImeInputTargetRequestedVisibility(boolean visible) {
             if (android.view.inputmethod.Flags.refactorInsetsController()) {
                 mImeRequestedVisible = visible;
+                dispatchImeRequested(mDisplayId, mImeRequestedVisible);
+
                 // In the case that the IME becomes visible, but we have the control with leash
                 // already (e.g., when focussing an editText in activity B, while and editText in
                 // activity A is focussed), we will not get a call of #insetsControlChanged, and
@@ -446,6 +456,8 @@
             if (imeSource == null || mImeSourceControl == null) {
                 return;
             }
+            // TODO(b/353463205): For hide: this still has the statsToken from the previous show
+            //  request
             final var statsToken = mImeSourceControl.getImeStatsToken();
 
             startAnimation(show, forceRestart, statsToken);
@@ -706,6 +718,14 @@
         }
 
         /**
+         * Called when the IME was requested by an app
+         *
+         * @param isRequested {@code true} if the IME was requested to be visible
+         */
+        default void onImeRequested(int displayId, boolean isRequested) {
+        }
+
+        /**
          * Called when the IME position is starting to animate.
          *
          * @param hiddenTop  The y position of the top of the IME surface when it is hidden.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 4b55fd0..83ffaf4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -1106,6 +1106,11 @@
         default void onDoubleTappedDivider() {
         }
 
+        /**
+         * Sets the excludedInsetsTypes for the IME in the root WindowContainer.
+         */
+        void setExcludeImeInsets(boolean exclude);
+
         /** Returns split position of the token. */
         @SplitPosition
         int getSplitItemPosition(WindowContainerToken token);
@@ -1305,6 +1310,14 @@
         }
 
         @Override
+        public void onImeRequested(int displayId, boolean isRequested) {
+            if (displayId != mDisplayId) return;
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "IME was set to requested=%s",
+                    isRequested);
+            mSplitLayoutHandler.setExcludeImeInsets(true);
+        }
+
+        @Override
         public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
                 boolean showing, boolean isFloating, SurfaceControl.Transaction t) {
             if (displayId != mDisplayId || !mInitialized) {
@@ -1356,6 +1369,12 @@
             setDividerInteractive(!mImeShown || !mHasImeFocus || isFloating, true,
                     "onImeStartPositioning");
 
+            if (android.view.inputmethod.Flags.refactorInsetsController()) {
+                if (mImeShown) {
+                    mSplitLayoutHandler.setExcludeImeInsets(false);
+                }
+            }
+
             return mTargetYOffset != mLastYOffset ? IME_ANIMATION_NO_ALPHA : 0;
         }
 
@@ -1374,6 +1393,16 @@
                     "Split IME animation ending, canceled=%b", cancel);
             onProgress(1.0f);
             mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
+            if (android.view.inputmethod.Flags.refactorInsetsController()) {
+                if (!mImeShown) {
+                    // The IME hide animation is started immediately and at that point, the IME
+                    // insets are not yet set to hidden. Therefore only resetting the
+                    // excludedTypes at the end of the animation. Note: InsetsPolicy will only
+                    // set the IME height to zero, when it is visible. When it becomes invisible,
+                    // we dispatch the insets (the height there is zero as well)
+                    mSplitLayoutHandler.setExcludeImeInsets(false);
+                }
+            }
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 4227a6e..2a5a519 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -87,7 +87,7 @@
 import com.android.wm.shell.compatui.impl.DefaultCompatUIRepository;
 import com.android.wm.shell.compatui.impl.DefaultComponentIdGenerator;
 import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
@@ -267,7 +267,7 @@
             Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
             Lazy<AccessibilityManager> accessibilityManager,
             CompatUIRepository compatUIRepository,
-            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+            Optional<DesktopRepository> desktopRepository,
             @NonNull CompatUIState compatUIState,
             @NonNull CompatUIComponentIdGenerator componentIdGenerator,
             @NonNull CompatUIComponentFactory compatUIComponentFactory,
@@ -281,7 +281,7 @@
                             componentIdGenerator, compatUIComponentFactory, mainExecutor));
         }
         final IntPredicate inDesktopModePredicate =
-                desktopModeTaskRepository.<IntPredicate>map(modeTaskRepository -> displayId ->
+                desktopRepository.<IntPredicate>map(modeTaskRepository -> displayId ->
                         modeTaskRepository.getVisibleTaskCount(displayId) > 0)
                             .orElseGet(() -> displayId -> false);
         return Optional.of(
@@ -707,14 +707,14 @@
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
             ActivityTaskManager activityTaskManager,
-            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+            Optional<DesktopRepository> desktopRepository,
             TaskStackTransitionObserver taskStackTransitionObserver,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
         return Optional.ofNullable(
                 RecentTasksController.create(context, shellInit, shellController,
                         shellCommandHandler, taskStackListener, activityTaskManager,
-                        desktopModeTaskRepository, taskStackTransitionObserver, mainExecutor));
+                        desktopRepository, taskStackTransitionObserver, mainExecutor));
     }
 
     @BindsOptionalOf
@@ -1003,16 +1003,16 @@
 
     @BindsOptionalOf
     @DynamicOverride
-    abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository();
+    abstract DesktopRepository optionalDesktopRepository();
 
     @WMSingleton
     @Provides
-    static Optional<DesktopModeTaskRepository> provideDesktopTaskRepository(Context context,
-            @DynamicOverride Optional<Lazy<DesktopModeTaskRepository>> desktopModeTaskRepository) {
+    static Optional<DesktopRepository> provideDesktopRepository(Context context,
+            @DynamicOverride Optional<Lazy<DesktopRepository>> desktopRepository) {
         // Use optional-of-lazy for the dependency that this provider relies on.
         // Lazy ensures that this provider will not be the cause the dependency is created
         // when it will not be returned due to the condition below.
-        return desktopModeTaskRepository.flatMap((lazy) -> {
+        return desktopRepository.flatMap((lazy) -> {
             if (DesktopModeStatus.canEnterDesktopMode(context)) {
                 return Optional.of(lazy.get());
             }
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 7293956..0dca97c 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
@@ -69,7 +69,7 @@
 import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler;
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
 import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
 import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver;
@@ -245,7 +245,7 @@
             IWindowManager windowManager,
             ShellCommandHandler shellCommandHandler,
             ShellTaskOrganizer taskOrganizer,
-            @DynamicOverride DesktopModeTaskRepository desktopRepository,
+            @DynamicOverride DesktopRepository desktopRepository,
             DisplayController displayController,
             ShellController shellController,
             DisplayInsetsController displayInsetsController,
@@ -352,7 +352,7 @@
             Context context,
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+            Optional<DesktopRepository> desktopRepository,
             LaunchAdjacentController launchAdjacentController,
             WindowDecorViewModel windowDecorViewModel) {
         // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
@@ -361,7 +361,7 @@
                 ? shellInit
                 : null;
         return new FreeformTaskListener(context, init, shellTaskOrganizer,
-                desktopModeTaskRepository, launchAdjacentController, windowDecorViewModel);
+                desktopRepository, launchAdjacentController, windowDecorViewModel);
     }
 
     @WMSingleton
@@ -620,7 +620,7 @@
             DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler,
             ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
             DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
-            @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+            @DynamicOverride DesktopRepository desktopRepository,
             DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
             LaunchAdjacentController launchAdjacentController,
             RecentsTransitionHandler recentsTransitionHandler,
@@ -636,7 +636,7 @@
                 returnToDragStartAnimator, enterDesktopTransitionHandler,
                 exitDesktopTransitionHandler, desktopModeDragAndDropTransitionHandler,
                 toggleResizeDesktopTaskTransitionHandler,
-                dragToDesktopTransitionHandler, desktopModeTaskRepository,
+                dragToDesktopTransitionHandler, desktopRepository,
                 desktopModeLoggerTransitionObserver, launchAdjacentController,
                 recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter,
                 recentTasksController.orElse(null), interactionJankMonitor, mainHandler);
@@ -647,7 +647,7 @@
     static Optional<DesktopTasksLimiter> provideDesktopTasksLimiter(
             Context context,
             Transitions transitions,
-            @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+            @DynamicOverride DesktopRepository desktopRepository,
             ShellTaskOrganizer shellTaskOrganizer,
             InteractionJankMonitor interactionJankMonitor,
             @ShellMainThread Handler handler) {
@@ -660,7 +660,7 @@
         return Optional.of(
                 new DesktopTasksLimiter(
                         transitions,
-                        desktopModeTaskRepository,
+                        desktopRepository,
                         shellTaskOrganizer,
                         maxTaskLimit,
                         interactionJankMonitor,
@@ -739,13 +739,14 @@
     @WMSingleton
     @Provides
     @DynamicOverride
-    static DesktopModeTaskRepository provideDesktopModeTaskRepository(
+
+    static DesktopRepository provideDesktopRepository(
             Context context,
             ShellInit shellInit,
             DesktopPersistentRepository desktopPersistentRepository,
             @ShellMainThread CoroutineScope mainScope
     ) {
-        return new DesktopModeTaskRepository(context, shellInit, desktopPersistentRepository,
+        return new DesktopRepository(context, shellInit, desktopPersistentRepository,
                 mainScope);
     }
 
@@ -757,12 +758,12 @@
             ShellTaskOrganizer shellTaskOrganizer,
             TaskStackListenerImpl taskStackListener,
             ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
-            @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository
+            @DynamicOverride DesktopRepository desktopRepository
     ) {
         if (DesktopModeStatus.canEnterDesktopMode(context)) {
             return Optional.of(new DesktopActivityOrientationChangeHandler(
                     context, shellInit, shellTaskOrganizer, taskStackListener,
-                    toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository));
+                    toggleResizeDesktopTaskTransitionHandler, desktopRepository));
         }
         return Optional.empty();
     }
@@ -771,12 +772,12 @@
     @Provides
     static Optional<DesktopTasksTransitionObserver> provideDesktopTasksTransitionObserver(
             Context context,
-            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+            Optional<DesktopRepository> desktopRepository,
             Transitions transitions,
             ShellTaskOrganizer shellTaskOrganizer,
             ShellInit shellInit
     ) {
-        return desktopModeTaskRepository.flatMap(repository ->
+        return desktopRepository.flatMap(repository ->
                 Optional.of(new DesktopTasksTransitionObserver(
                         context, repository, transitions, shellTaskOrganizer, shellInit))
         );
@@ -787,7 +788,7 @@
     static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler(
             Context context,
             Transitions transitions,
-            @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+            @DynamicOverride DesktopRepository desktopRepository,
             FreeformTaskTransitionHandler freeformTaskTransitionHandler,
             CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
             InteractionJankMonitor interactionJankMonitor,
@@ -801,7 +802,7 @@
                 new DesktopMixedTransitionHandler(
                         context,
                         transitions,
-                        desktopModeTaskRepository,
+                        desktopRepository,
                         freeformTaskTransitionHandler,
                         closeDesktopTaskTransitionHandler,
                         interactionJankMonitor,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 3464fef..702552e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -77,10 +77,12 @@
             PipTaskListener pipTaskListener,
             @NonNull PipScheduler pipScheduler,
             @NonNull PipTransitionState pipStackListenerController,
+            @NonNull PipDisplayLayoutState pipDisplayLayoutState,
             @NonNull PipUiStateChangeController pipUiStateChangeController) {
         return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
                 pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
-                pipScheduler, pipStackListenerController, pipUiStateChangeController);
+                pipScheduler, pipStackListenerController, pipDisplayLayoutState,
+                pipUiStateChangeController);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
index 59e0068..606aa6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
@@ -39,7 +39,7 @@
     private val shellTaskOrganizer: ShellTaskOrganizer,
     private val taskStackListener: TaskStackListenerImpl,
     private val resizeHandler: ToggleResizeDesktopTaskTransitionHandler,
-    private val taskRepository: DesktopModeTaskRepository,
+    private val taskRepository: DesktopRepository,
 ) {
 
     init {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index ec3f8c5..4350199 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -40,7 +40,7 @@
 class DesktopMixedTransitionHandler(
     private val context: Context,
     private val transitions: Transitions,
-    private val desktopTaskRepository: DesktopModeTaskRepository,
+    private val desktopRepository: DesktopRepository,
     private val freeformTaskTransitionHandler: FreeformTaskTransitionHandler,
     private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler,
     private val interactionJankMonitor: InteractionJankMonitor,
@@ -138,7 +138,7 @@
 
     private fun isLastDesktopTask(change: TransitionInfo.Change): Boolean =
         change.taskInfo?.let {
-            desktopTaskRepository.getActiveNonMinimizedTaskCount(it.displayId) == 1
+            desktopRepository.getActiveNonMinimizedTaskCount(it.displayId) == 1
         } ?: false
 
     private fun findCloseDesktopTaskChange(info: TransitionInfo): TransitionInfo.Change? {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index cca7500..73e55b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -36,7 +36,7 @@
      * @param listener the listener to add.
      * @param callbackExecutor the executor to call the listener on.
      */
-    void addVisibleTasksListener(DesktopModeTaskRepository.VisibleTasksListener listener,
+    void addVisibleTasksListener(DesktopRepository.VisibleTasksListener listener,
             Executor callbackExecutor);
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index b1b7d05..5a277316f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -19,6 +19,7 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.protolog.ProtoLog
 import com.android.internal.util.FrameworkStatsLog
+import com.android.window.flags.Flags
 import com.android.wm.shell.EventLogTags
 import com.android.wm.shell.protolog.ShellProtoLogGroup
 
@@ -79,7 +80,8 @@
         )
         logTaskUpdate(
             FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED,
-            sessionId, taskUpdate)
+            sessionId, taskUpdate
+        )
     }
 
     /**
@@ -95,7 +97,8 @@
         )
         logTaskUpdate(
             FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED,
-            sessionId, taskUpdate)
+            sessionId, taskUpdate
+        )
     }
 
     /**
@@ -111,7 +114,46 @@
         )
         logTaskUpdate(
             FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
-            sessionId, taskUpdate)
+            sessionId, taskUpdate
+        )
+    }
+
+    /**
+     * Logs that a task resize event is starting with [taskSizeUpdate] within a
+     * Desktop mode [sessionId].
+     */
+    fun logTaskResizingStarted(sessionId: Int, taskSizeUpdate: TaskSizeUpdate) {
+        if (!Flags.enableResizingMetrics()) return
+
+        ProtoLog.v(
+            ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+            "DesktopModeLogger: Logging task resize is starting, session: %s taskId: %s",
+            sessionId,
+            taskSizeUpdate.instanceId
+        )
+        logTaskSizeUpdated(
+            FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE,
+            sessionId, taskSizeUpdate
+        )
+    }
+
+    /**
+     * Logs that a task resize event is ending with [taskSizeUpdate] within a
+     * Desktop mode [sessionId].
+     */
+    fun logTaskResizingEnded(sessionId: Int, taskSizeUpdate: TaskSizeUpdate) {
+        if (!Flags.enableResizingMetrics()) return
+
+        ProtoLog.v(
+            ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+            "DesktopModeLogger: Logging task resize is ending, session: %s taskId: %s",
+            sessionId,
+            taskSizeUpdate.instanceId
+        )
+        logTaskSizeUpdated(
+            FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE,
+            sessionId, taskSizeUpdate
+        )
     }
 
     private fun logTaskUpdate(taskEvent: Int, sessionId: Int, taskUpdate: TaskUpdate) {
@@ -162,6 +204,34 @@
         )
     }
 
+    private fun logTaskSizeUpdated(
+        resizingStage: Int,
+        sessionId: Int,
+        taskSizeUpdate: TaskSizeUpdate
+    ) {
+        FrameworkStatsLog.write(
+            DESKTOP_MODE_TASK_SIZE_UPDATED_ATOM_ID,
+            /* resize_trigger */
+            taskSizeUpdate.resizeTrigger?.trigger ?: ResizeTrigger.UNKNOWN_RESIZE_TRIGGER.trigger,
+            /* resizing_stage */
+            resizingStage,
+            /* input_method */
+            taskSizeUpdate.inputMethod?.method ?: InputMethod.UNKNOWN_INPUT_METHOD.method,
+            /* desktop_mode_session_id */
+            sessionId,
+            /* instance_id */
+            taskSizeUpdate.instanceId,
+            /* uid */
+            taskSizeUpdate.uid,
+            /* task_height */
+            taskSizeUpdate.taskHeight,
+            /* task_width */
+            taskSizeUpdate.taskWidth,
+            /* display_area */
+            taskSizeUpdate.displayArea
+        )
+    }
+
     companion object {
         /**
          * Describes a task position and dimensions.
@@ -188,13 +258,35 @@
             val visibleTaskCount: Int,
         )
 
+        /**
+         * Describes a task size update (resizing, snapping or maximizing to
+         * stable bounds).
+         *
+         * @property resizeTrigger the trigger for task resize
+         * @property inputMethod the input method for resizing this task
+         * @property instanceId instance id of the task
+         * @property uid uid of the app associated with the task
+         * @property taskHeight height of the task in dp
+         * @property taskWidth width of the task in dp
+         * @property displayArea the display size of the screen in dp
+         */
+        data class TaskSizeUpdate(
+            val resizeTrigger: ResizeTrigger? = null,
+            val inputMethod: InputMethod? = null,
+            val instanceId: Int,
+            val uid: Int,
+            val taskHeight: Int,
+            val taskWidth: Int,
+            val displayArea: Int,
+        )
+
         // Default value used when the task was not minimized.
         @VisibleForTesting
         const val UNSET_MINIMIZE_REASON =
             FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__UNSET_MINIMIZE
 
         /** The reason a task was minimized. */
-        enum class MinimizeReason (val reason: Int) {
+        enum class MinimizeReason(val reason: Int) {
             TASK_LIMIT(
                 FrameworkStatsLog
                     .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT
@@ -211,7 +303,7 @@
             FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNSET_UNMINIMIZE
 
         /** The reason a task was unminimized. */
-        enum class UnminimizeReason (val reason: Int) {
+        enum class UnminimizeReason(val reason: Int) {
             UNKNOWN(
                 FrameworkStatsLog
                     .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_UNKNOWN
@@ -275,8 +367,88 @@
             SCREEN_OFF(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__SCREEN_OFF)
         }
 
+        /**
+         * Enum ResizeTrigger mapped to the ResizeTrigger definition in
+         * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
+         */
+        enum class ResizeTrigger(val trigger: Int) {
+            UNKNOWN_RESIZE_TRIGGER(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER
+            ),
+            CORNER(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER
+            ),
+            EDGE(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__EDGE_RESIZE_TRIGGER
+            ),
+            TILING_DIVIDER(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__TILING_DIVIDER_RESIZE_TRIGGER
+            ),
+            MAXIMIZE_BUTTON(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__MAXIMIZE_BUTTON_RESIZE_TRIGGER
+            ),
+            DOUBLE_TAP_APP_HEADER(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DOUBLE_TAP_APP_HEADER_RESIZE_TRIGGER
+            ),
+            DRAG_LEFT(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DRAG_LEFT_RESIZE_TRIGGER
+            ),
+            DRAG_RIGHT(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DRAG_RIGHT_RESIZE_TRIGGER
+            ),
+            SNAP_LEFT_MENU(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__SNAP_LEFT_MENU_RESIZE_TRIGGER
+            ),
+            SNAP_RIGHT_MENU(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__SNAP_RIGHT_MENU_RESIZE_TRIGGER
+            ),
+        }
+
+        /**
+         * Enum InputMethod mapped to the InputMethod definition in
+         * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
+         */
+        enum class InputMethod(val method: Int) {
+            UNKNOWN_INPUT_METHOD(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD
+            ),
+            TOUCH(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__TOUCH_INPUT_METHOD
+            ),
+            STYLUS(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__STYLUS_INPUT_METHOD
+            ),
+            MOUSE(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__MOUSE_INPUT_METHOD
+            ),
+            TOUCHPAD(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__TOUCHPAD_INPUT_METHOD
+            ),
+            KEYBOARD(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__KEYBOARD_INPUT_METHOD
+            ),
+        }
+
         private const val DESKTOP_MODE_ATOM_ID = FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED
         private const val DESKTOP_MODE_TASK_UPDATE_ATOM_ID =
             FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE
+        private const val DESKTOP_MODE_TASK_SIZE_UPDATED_ATOM_ID =
+            FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 7261919..52b92a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -21,6 +21,12 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.RectEvaluator;
@@ -125,7 +131,7 @@
         mContext = context;
         mTaskSurface = taskSurface;
         mRootTdaOrganizer = taskDisplayAreaOrganizer;
-        mCurrentType = IndicatorType.NO_INDICATOR;
+        mCurrentType = NO_INDICATOR;
         mDragStartState = dragStartState;
     }
 
@@ -136,8 +142,16 @@
     @NonNull
     IndicatorType updateIndicatorType(PointF inputCoordinates) {
         final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+        // Perform a quick check first: any input off the left edge of the display should be split
+        // left, and split right for the right edge. This is universal across all drag event types.
+        if (inputCoordinates.x < 0) return TO_SPLIT_LEFT_INDICATOR;
+        if (inputCoordinates.x > layout.width()) return TO_SPLIT_RIGHT_INDICATOR;
         // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
-        IndicatorType result = IndicatorType.NO_INDICATOR;
+        // In drags not originating on a freeform caption, we should default to a TO_DESKTOP
+        // indicator.
+        IndicatorType result = mDragStartState == DragStartState.FROM_FREEFORM
+                ? NO_INDICATOR
+                : TO_DESKTOP_INDICATOR;
         final int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.desktop_mode_transition_region_thickness);
         // Because drags in freeform use task position for indicator calculation, we need to
@@ -149,10 +163,8 @@
                 captionHeight);
         final Region splitRightRegion = calculateSplitRightRegion(layout, transitionAreaWidth,
                 captionHeight);
-        final Region toDesktopRegion = calculateToDesktopRegion(layout, splitLeftRegion,
-                splitRightRegion, fullscreenRegion);
         if (fullscreenRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
-            result = IndicatorType.TO_FULLSCREEN_INDICATOR;
+            result = TO_FULLSCREEN_INDICATOR;
         }
         if (splitLeftRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
             result = IndicatorType.TO_SPLIT_LEFT_INDICATOR;
@@ -160,9 +172,6 @@
         if (splitRightRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
             result = IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
         }
-        if (toDesktopRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
-            result = IndicatorType.TO_DESKTOP_INDICATOR;
-        }
         if (mDragStartState != DragStartState.DRAGGED_INTENT) {
             transitionIndicator(result);
         }
@@ -182,7 +191,7 @@
                     R.dimen.desktop_mode_fullscreen_region_scale);
             final float toFullscreenWidth = (layout.width() * toFullscreenScale);
             region.union(new Rect((int) ((layout.width() / 2f) - (toFullscreenWidth / 2f)),
-                    -captionHeight,
+                    Short.MIN_VALUE,
                     (int) ((layout.width() / 2f) + (toFullscreenWidth / 2f)),
                     transitionHeight));
         }
@@ -192,7 +201,7 @@
                 || mDragStartState == DragStartState.DRAGGED_INTENT
         ) {
             region.union(new Rect(0,
-                    -captionHeight,
+                    Short.MIN_VALUE,
                     layout.width(),
                     transitionHeight));
         }
@@ -200,21 +209,6 @@
     }
 
     @VisibleForTesting
-    Region calculateToDesktopRegion(DisplayLayout layout,
-            Region splitLeftRegion, Region splitRightRegion,
-            Region toFullscreenRegion) {
-        final Region region = new Region();
-        // If in desktop, we need no region. Otherwise it's the same for all windowing modes.
-        if (mDragStartState != DragStartState.FROM_FREEFORM) {
-            region.union(new Rect(0, 0, layout.width(), layout.height()));
-            region.op(splitLeftRegion, Region.Op.DIFFERENCE);
-            region.op(splitRightRegion, Region.Op.DIFFERENCE);
-            region.op(toFullscreenRegion, Region.Op.DIFFERENCE);
-        }
-        return region;
-    }
-
-    @VisibleForTesting
     Region calculateSplitLeftRegion(DisplayLayout layout,
             int transitionEdgeWidth, int captionHeight) {
         final Region region = new Region();
@@ -311,7 +305,7 @@
                 }
             });
         }
-        mCurrentType = IndicatorType.NO_INDICATOR;
+        mCurrentType = NO_INDICATOR;
     }
 
     /**
@@ -322,9 +316,9 @@
         if (mView == null) {
             createView();
         }
-        if (mCurrentType == IndicatorType.NO_INDICATOR) {
+        if (mCurrentType == NO_INDICATOR) {
             fadeInIndicator(newType);
-        } else if (newType == IndicatorType.NO_INDICATOR) {
+        } else if (newType == NO_INDICATOR) {
             fadeOutIndicator(null /* finishCallback */);
         } else {
             final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
rename to libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 985224e..7b2a5d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -42,8 +42,8 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 
-/** Tracks task data for Desktop Mode. */
-class DesktopModeTaskRepository (
+/** Tracks desktop data for Android Desktop Windowing. */
+class DesktopRepository (
     private val context: Context,
     shellInit: ShellInit,
     private val persistentRepository: DesktopPersistentRepository,
@@ -467,10 +467,9 @@
         }
     }
 
-
     internal fun dump(pw: PrintWriter, prefix: String) {
         val innerPrefix = "$prefix  "
-        pw.println("${prefix}DesktopModeTaskRepository")
+        pw.println("${prefix}DesktopRepository")
         dumpDesktopTaskData(pw, innerPrefix)
         pw.println("${innerPrefix}activeTasksListeners=${activeTasksListeners.size}")
         pw.println("${innerPrefix}visibleTasksListeners=${visibleTasksListeners.size}")
@@ -512,10 +511,9 @@
     }
 
     companion object {
-        private const val TAG = "DesktopModeTaskRepository"
+        private const val TAG = "DesktopRepository"
     }
 }
 
 private fun <T> Iterable<T>.toDumpString(): String =
     joinToString(separator = ", ", prefix = "[", postfix = "]")
-
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index b8bb73b..2303e71 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -73,7 +73,7 @@
 import com.android.wm.shell.common.SingleInstanceRemoteListener
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
 import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
@@ -134,7 +134,7 @@
     private val desktopModeDragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler,
     private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
     private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
-    private val taskRepository: DesktopModeTaskRepository,
+    private val taskRepository: DesktopRepository,
     private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver,
     private val launchAdjacentController: LaunchAdjacentController,
     private val recentsTransitionHandler: RecentsTransitionHandler,
@@ -406,7 +406,7 @@
         interactionJankMonitor.begin(taskSurface, context, handler,
             CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
         dragToDesktopTransitionHandler.startDragToDesktopTransition(
-            taskInfo.taskId,
+            taskInfo,
             dragToDesktopValueAnimator
         )
     }
@@ -1594,7 +1594,7 @@
             return
         }
 
-        val indicator = visualIndicator ?: return
+        val indicator = getVisualIndicator() ?: return
         val indicatorType =
             indicator.updateIndicatorType(
                 PointF(inputCoordinate.x, currentDragBounds.top.toFloat()),
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 37ad0c9..37bec21 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
@@ -44,7 +44,7 @@
  */
 class DesktopTasksLimiter (
         transitions: Transitions,
-        private val taskRepository: DesktopModeTaskRepository,
+        private val taskRepository: DesktopRepository,
         private val shellTaskOrganizer: ShellTaskOrganizer,
         private val maxTasksLimit: Int,
         private val interactionJankMonitor: InteractionJankMonitor,
@@ -160,7 +160,7 @@
     }
 
     @VisibleForTesting
-    inner class LeftoverMinimizedTasksRemover : DesktopModeTaskRepository.ActiveTasksListener {
+    inner class LeftoverMinimizedTasksRemover : DesktopRepository.ActiveTasksListener {
         override fun onActiveTasksChanged(displayId: Int) {
             // If back navigation is enabled, we shouldn't remove the leftover tasks
             if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index b20c9fc..e086e40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -36,12 +36,12 @@
 
 /**
  * A [Transitions.TransitionObserver] that observes shell transitions and updates the
- * [DesktopModeTaskRepository] state TODO: b/332682201 This observes transitions related to desktop
+ * [DesktopRepository] state TODO: b/332682201 This observes transitions related to desktop
  * mode and other transitions that originate both within and outside shell.
  */
 class DesktopTasksTransitionObserver(
     private val context: Context,
-    private val desktopModeTaskRepository: DesktopModeTaskRepository,
+    private val desktopRepository: DesktopRepository,
     private val transitions: Transitions,
     private val shellTaskOrganizer: ShellTaskOrganizer,
     shellInit: ShellInit
@@ -83,10 +83,10 @@
             val taskInfo = change.taskInfo
             if (taskInfo == null || taskInfo.taskId == -1) continue
 
-            if (desktopModeTaskRepository.isActiveTask(taskInfo.taskId)
+            if (desktopRepository.isActiveTask(taskInfo.taskId)
                 && taskInfo.windowingMode != WINDOWING_MODE_FREEFORM
             ) {
-                desktopModeTaskRepository.removeFreeformTask(
+                desktopRepository.removeFreeformTask(
                     taskInfo.displayId,
                     taskInfo.taskId
                 )
@@ -104,11 +104,11 @@
                     continue
                 }
 
-                if (desktopModeTaskRepository.getVisibleTaskCount(taskInfo.displayId) > 0 &&
+                if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) > 0 &&
                     change.mode == TRANSIT_TO_BACK &&
                     taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
                 ) {
-                    desktopModeTaskRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
+                    desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
                 }
             }
         }
@@ -135,7 +135,7 @@
                 if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) {
                     when (change.mode) {
                         WindowManager.TRANSIT_OPEN -> {
-                            desktopModeTaskRepository.wallpaperActivityToken = taskInfo.token
+                            desktopRepository.wallpaperActivityToken = taskInfo.token
                             // After the task for the wallpaper is created, set it non-trimmable.
                             // This is important to prevent recents from trimming and removing the
                             // task.
@@ -145,7 +145,7 @@
                             )
                         }
                         WindowManager.TRANSIT_CLOSE ->
-                            desktopModeTaskRepository.wallpaperActivityToken = null
+                            desktopRepository.wallpaperActivityToken = null
                         else -> {}
                     }
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 2bc01b2..8e264b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -109,8 +109,8 @@
      * after one of the "end" or "cancel" transitions is merged into this transition.
      */
     fun startDragToDesktopTransition(
-        taskId: Int,
-        dragToDesktopAnimator: MoveToDesktopAnimator,
+        taskInfo: RunningTaskInfo,
+        dragToDesktopAnimator: MoveToDesktopAnimator
     ) {
         if (inProgress) {
             ProtoLog.v(
@@ -137,23 +137,26 @@
             )
         val wct = WindowContainerTransaction()
         wct.sendPendingIntent(pendingIntent, launchHomeIntent, Bundle())
+        // The home launch done above will result in an attempt to move the task to pip if
+        // applicable, resulting in a broken state. Prevent that here.
+        wct.setDoNotPip(taskInfo.token)
         val startTransitionToken =
             transitions.startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this)
 
         transitionState =
-            if (isSplitTask(taskId)) {
+            if (isSplitTask(taskInfo.taskId)) {
                 val otherTask =
-                    getOtherSplitTask(taskId)
+                    getOtherSplitTask(taskInfo.taskId)
                         ?: throw IllegalStateException("Expected split task to have a counterpart.")
                 TransitionState.FromSplit(
-                    draggedTaskId = taskId,
+                    draggedTaskId = taskInfo.taskId,
                     dragAnimator = dragToDesktopAnimator,
                     startTransitionToken = startTransitionToken,
                     otherSplitTask = otherTask
                 )
             } else {
                 TransitionState.FromFullscreen(
-                    draggedTaskId = taskId,
+                    draggedTaskId = taskInfo.taskId,
                     dragAnimator = dragToDesktopAnimator,
                     startTransitionToken = startTransitionToken
                 )
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 7f7f105..73f7011 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -29,7 +29,7 @@
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.LaunchAdjacentController;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInit;
@@ -49,7 +49,7 @@
 
     private final Context mContext;
     private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
+    private final Optional<DesktopRepository> mDesktopRepository;
     private final WindowDecorViewModel mWindowDecorationViewModel;
     private final LaunchAdjacentController mLaunchAdjacentController;
 
@@ -64,13 +64,13 @@
             Context context,
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+            Optional<DesktopRepository> desktopRepository,
             LaunchAdjacentController launchAdjacentController,
             WindowDecorViewModel windowDecorationViewModel) {
         mContext = context;
         mShellTaskOrganizer = shellTaskOrganizer;
         mWindowDecorationViewModel = windowDecorationViewModel;
-        mDesktopModeTaskRepository = desktopModeTaskRepository;
+        mDesktopRepository = desktopRepository;
         mLaunchAdjacentController = launchAdjacentController;
         if (shellInit != null) {
             shellInit.addInitCallback(this::onInit, this);
@@ -102,7 +102,7 @@
         }
 
         if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
-            mDesktopModeTaskRepository.ifPresent(repository -> {
+            mDesktopRepository.ifPresent(repository -> {
                 repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId);
                 if (taskInfo.isVisible) {
                     repository.addActiveTask(taskInfo.displayId, taskInfo.taskId);
@@ -121,7 +121,7 @@
         mTasks.remove(taskInfo.taskId);
 
         if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
-            mDesktopModeTaskRepository.ifPresent(repository -> {
+            mDesktopRepository.ifPresent(repository -> {
                 // TODO: b/370038902 - Handle Activity#finishAndRemoveTask.
                 if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()
                         || repository.isClosingTask(taskInfo.taskId)) {
@@ -150,7 +150,7 @@
         mWindowDecorationViewModel.onTaskInfoChanged(taskInfo);
         state.mTaskInfo = taskInfo;
         if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
-            mDesktopModeTaskRepository.ifPresent(repository -> {
+            mDesktopRepository.ifPresent(repository -> {
                 if (taskInfo.isVisible) {
                     repository.addActiveTask(taskInfo.displayId, taskInfo.taskId);
                 } else if (repository.isClosingTask(taskInfo.taskId)) {
@@ -182,7 +182,7 @@
                 "Freeform Task Focus Changed: #%d focused=%b",
                 taskInfo.taskId, taskInfo.isFocused);
         if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused) {
-            mDesktopModeTaskRepository.ifPresent(repository -> {
+            mDesktopRepository.ifPresent(repository -> {
                 repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId);
             });
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index e9c4c14..73be8db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -324,10 +324,16 @@
             int launcherRotation, Rect hotseatKeepClearArea) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "getSwipePipToHomeBounds: %s", componentName);
-        // preemptively add the keep clear area for Hotseat, so that it is taken into account
-        // when calculating the entry destination bounds of PiP window
+        // Preemptively add the keep clear area for Hotseat, so that it is taken into account
+        // when calculating the entry destination bounds of PiP window.
         mPipBoundsState.setNamedUnrestrictedKeepClearArea(
                 PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, hotseatKeepClearArea);
+
+        // Set the display layout rotation early to calculate final orientation bounds that
+        // the animator expects, this will also be used to detect the fixed rotation when
+        // Shell resolves the type of the animation we are undergoing.
+        mPipDisplayLayoutState.rotateTo(launcherRotation);
+
         mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, pictureInPictureParams,
                 mPipBoundsAlgorithm);
         return mPipBoundsAlgorithm.getEntryDestinationBounds();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index dc0bc78..62a60fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.pip2.phone;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.Surface.ROTATION_270;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_PIP;
@@ -33,6 +34,7 @@
 import android.app.ActivityManager;
 import android.app.PictureInPictureParams;
 import android.content.Context;
+import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -49,6 +51,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.PipMenuController;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.pip.PipTransitionController;
@@ -82,7 +85,7 @@
      * The fixed start delay in ms when fading out the content overlay from bounds animation.
      * The fadeout animation is guaranteed to start after the client has drawn under the new config.
      */
-    private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 400;
+    private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 500;
 
     //
     // Dependencies
@@ -92,6 +95,7 @@
     private final PipTaskListener mPipTaskListener;
     private final PipScheduler mPipScheduler;
     private final PipTransitionState mPipTransitionState;
+    private final PipDisplayLayoutState mPipDisplayLayoutState;
 
     //
     // Transition caches
@@ -124,6 +128,7 @@
             PipTaskListener pipTaskListener,
             PipScheduler pipScheduler,
             PipTransitionState pipTransitionState,
+            PipDisplayLayoutState pipDisplayLayoutState,
             PipUiStateChangeController pipUiStateChangeController) {
         super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
                 pipBoundsAlgorithm);
@@ -134,6 +139,7 @@
         mPipScheduler.setPipTransitionController(this);
         mPipTransitionState = pipTransitionState;
         mPipTransitionState.addPipTransitionStateChangedListener(this);
+        mPipDisplayLayoutState = pipDisplayLayoutState;
     }
 
     @Override
@@ -321,11 +327,30 @@
                             (destinationBounds.width() - overlaySize) / 2f,
                             (destinationBounds.height() - overlaySize) / 2f);
         }
-
         startTransaction.merge(finishTransaction);
+
+        final int startRotation = pipChange.getStartRotation();
+        final int endRotation = mPipDisplayLayoutState.getRotation();
+        if (endRotation != startRotation) {
+            boolean isClockwise = (endRotation - startRotation) == -ROTATION_270;
+
+            // Display bounds were already updated to represent the final orientation,
+            // so we just need to readjust the origin, and perform rotation about (0, 0).
+            Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
+            int originTranslateX = isClockwise ? 0 : -displayBounds.width();
+            int originTranslateY = isClockwise ? -displayBounds.height() : 0;
+
+            Matrix transformTensor = new Matrix();
+            final float[] matrixTmp = new float[9];
+            transformTensor.setTranslate(originTranslateX + destinationBounds.left,
+                    originTranslateY + destinationBounds.top);
+            final float degrees = (endRotation - startRotation) * 90f;
+            transformTensor.postRotate(degrees);
+            startTransaction.setMatrix(pipLeash, transformTensor, matrixTmp);
+        }
         startTransaction.apply();
         finishCallback.onTransitionFinished(null /* finishWct */);
-        onClientDrawAtTransitionEnd();
+        finishInner();
         return true;
     }
 
@@ -397,7 +422,7 @@
                 sourceRectHint, PipEnterExitAnimator.BOUNDS_ENTER, Surface.ROTATION_0);
 
         tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
-                this::onClientDrawAtTransitionEnd);
+                this::finishInner);
         finishWct.setBoundsChangeTransaction(pipTaskToken, tx);
 
         animator.setAnimationEndCallback(() ->
@@ -430,7 +455,7 @@
         animator.setAnimationEndCallback(() -> {
             finishCallback.onTransitionFinished(null);
             // This should update the pip transition state accordingly after we stop playing.
-            onClientDrawAtTransitionEnd();
+            finishInner();
         });
 
         animator.start();
@@ -605,7 +630,7 @@
     // Miscellaneous callbacks and listeners
     //
 
-    private void onClientDrawAtTransitionEnd() {
+    private void finishInner() {
         if (mPipTransitionState.getSwipePipToHomeOverlay() != null) {
             startOverlayFadeoutAnimation();
         } else if (mPipTransitionState.getState() == PipTransitionState.ENTERING_PIP) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 03ff1aa..95cb3df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -32,6 +32,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Color;
+import android.graphics.Point;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -46,13 +47,14 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.GroupedRecentTaskInfo;
 import com.android.wm.shell.shared.annotations.ExternalThread;
@@ -79,14 +81,14 @@
  * Manages the recent task list from the system, caching it as necessary.
  */
 public class RecentTasksController implements TaskStackListenerCallback,
-        RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.ActiveTasksListener,
+        RemoteCallable<RecentTasksController>, DesktopRepository.ActiveTasksListener,
         TaskStackTransitionObserver.TaskStackTransitionObserverListener {
     private static final String TAG = RecentTasksController.class.getSimpleName();
 
     private final Context mContext;
     private final ShellController mShellController;
     private final ShellCommandHandler mShellCommandHandler;
-    private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
+    private final Optional<DesktopRepository> mDesktopRepository;
     private final ShellExecutor mMainExecutor;
     private final TaskStackListenerImpl mTaskStackListener;
     private final RecentTasksImpl mImpl = new RecentTasksImpl();
@@ -119,7 +121,7 @@
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
             ActivityTaskManager activityTaskManager,
-            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+            Optional<DesktopRepository> desktopRepository,
             TaskStackTransitionObserver taskStackTransitionObserver,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
@@ -127,7 +129,7 @@
             return null;
         }
         return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
-                taskStackListener, activityTaskManager, desktopModeTaskRepository,
+                taskStackListener, activityTaskManager, desktopRepository,
                 taskStackTransitionObserver, mainExecutor);
     }
 
@@ -137,7 +139,7 @@
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
             ActivityTaskManager activityTaskManager,
-            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+            Optional<DesktopRepository> desktopRepository,
             TaskStackTransitionObserver taskStackTransitionObserver,
             ShellExecutor mainExecutor) {
         mContext = context;
@@ -146,7 +148,7 @@
         mActivityTaskManager = activityTaskManager;
         mPcFeatureEnabled = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
         mTaskStackListener = taskStackListener;
-        mDesktopModeTaskRepository = desktopModeTaskRepository;
+        mDesktopRepository = desktopRepository;
         mTaskStackTransitionObserver = taskStackTransitionObserver;
         mMainExecutor = mainExecutor;
         shellInit.addInitCallback(this::onInit, this);
@@ -166,7 +168,7 @@
                 this::createExternalInterface, this);
         mShellCommandHandler.addDumpCallback(this::dump, this);
         mTaskStackListener.addListener(this);
-        mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this));
+        mDesktopRepository.ifPresent(it -> it.addActiveTaskListener(this));
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             mTaskStackTransitionObserver.addTaskStackTransitionObserverListener(this,
                     mMainExecutor);
@@ -415,14 +417,24 @@
             }
 
             if (DesktopModeStatus.canEnterDesktopMode(mContext)
-                    && mDesktopModeTaskRepository.isPresent()
-                    && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
+                    && mDesktopRepository.isPresent()
+                    && mDesktopRepository.get().isActiveTask(taskInfo.taskId)) {
                 // Freeform tasks will be added as a separate entry
                 if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
                     mostRecentFreeformTaskIndex = recentTasks.size();
                 }
+                // If task has their app bounds set to null which happens after reboot, set the
+                // app bounds to persisted lastFullscreenBounds. Also set the position in parent
+                // to the top left of the bounds.
+                if (Flags.enableDesktopWindowingPersistence()
+                        && taskInfo.configuration.windowConfiguration.getAppBounds() == null) {
+                    taskInfo.configuration.windowConfiguration.setAppBounds(
+                            taskInfo.lastNonFullscreenBounds);
+                    taskInfo.positionInParent = new Point(taskInfo.lastNonFullscreenBounds.left,
+                            taskInfo.lastNonFullscreenBounds.top);
+                }
                 freeformTasks.add(taskInfo);
-                if (mDesktopModeTaskRepository.get().isMinimizedTask(taskInfo.taskId)) {
+                if (mDesktopRepository.get().isMinimizedTask(taskInfo.taskId)) {
                     minimizedFreeformTasks.add(taskInfo.taskId);
                 }
                 continue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e8eb10c..e527c02 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -901,6 +901,23 @@
         setEnterInstanceId(instanceId);
     }
 
+
+    @Override
+    public void setExcludeImeInsets(boolean exclude) {
+        if (android.view.inputmethod.Flags.refactorInsetsController()) {
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            if (mRootTaskInfo == null) {
+                ProtoLog.e(WM_SHELL_SPLIT_SCREEN, "setExcludeImeInsets: mRootTaskInfo is null");
+                return;
+            }
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+                    "setExcludeImeInsets: root taskId=%s exclude=%s",
+                    mRootTaskInfo.taskId, exclude);
+            wct.setExcludeImeInsets(mRootTaskInfo.token, exclude);
+            mTaskOrganizer.applyTransaction(wct);
+        }
+    }
+
     /**
      * Checks if either of the apps in the desired split launch is currently in Pip. If so, it will
      * launch the non-pipped app as a fullscreen app, otherwise no-op.
@@ -1717,6 +1734,7 @@
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
         wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
+
         // Make the stages adjacent to each other so they occlude what's behind them.
         wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
         setRootForceTranslucent(true, wct);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
index 30d7245..e61929f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -141,10 +141,13 @@
             pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
             pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
                     finishCB);
+            // make a new finishTransaction because pip's startEnterAnimation "consumes" it so
+            // we need a separate one to send over to launcher.
+            SurfaceControl.Transaction otherFinishT = new SurfaceControl.Transaction();
             // Dispatch the rest of the transition normally. This will most-likely be taken by
             // recents or default handler.
             mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse,
-                    otherStartT, finishTransaction, finishCB, mixedHandler);
+                    otherStartT, otherFinishT, finishCB, mixedHandler);
         } else {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  Not leaving split, so just "
                     + "forward animation to Pip-Handler.");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 3330f96..bf175b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -103,7 +103,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
@@ -155,7 +155,7 @@
     private final ActivityTaskManager mActivityTaskManager;
     private final ShellCommandHandler mShellCommandHandler;
     private final ShellTaskOrganizer mTaskOrganizer;
-    private final DesktopModeTaskRepository mDesktopRepository;
+    private final DesktopRepository mDesktopRepository;
     private final ShellController mShellController;
     private final Context mContext;
     private final @ShellMainThread Handler mMainHandler;
@@ -227,7 +227,7 @@
             ShellCommandHandler shellCommandHandler,
             IWindowManager windowManager,
             ShellTaskOrganizer taskOrganizer,
-            DesktopModeTaskRepository desktopRepository,
+            DesktopRepository desktopRepository,
             DisplayController displayController,
             ShellController shellController,
             DisplayInsetsController displayInsetsController,
@@ -288,7 +288,7 @@
             ShellCommandHandler shellCommandHandler,
             IWindowManager windowManager,
             ShellTaskOrganizer taskOrganizer,
-            DesktopModeTaskRepository desktopRepository,
+            DesktopRepository desktopRepository,
             DisplayController displayController,
             ShellController shellController,
             DisplayInsetsController displayInsetsController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 5daa3ee..c7e8422 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -87,13 +87,14 @@
 import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
 import com.android.wm.shell.apptoweb.AppToWebUtils;
 import com.android.wm.shell.apptoweb.AssistContentRequester;
+import com.android.wm.shell.apptoweb.OpenByDefaultDialog;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.MultiInstanceHelper;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.desktopmode.CaptionState;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -163,6 +164,8 @@
 
     private MaximizeMenu mMaximizeMenu;
 
+    private OpenByDefaultDialog mOpenByDefaultDialog;
+
     private ResizeVeil mResizeVeil;
     private Bitmap mAppIconBitmap;
     private Bitmap mResizeVeilBitmap;
@@ -193,14 +196,14 @@
     private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired;
     private final MultiInstanceHelper mMultiInstanceHelper;
     private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
-    private final DesktopModeTaskRepository mDesktopRepository;
+    private final DesktopRepository mDesktopRepository;
 
     DesktopModeWindowDecoration(
             Context context,
             @NonNull Context userContext,
             DisplayController displayController,
             SplitScreenController splitScreenController,
-            DesktopModeTaskRepository desktopRepository,
+            DesktopRepository desktopRepository,
             ShellTaskOrganizer taskOrganizer,
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
@@ -232,7 +235,7 @@
             @NonNull Context userContext,
             DisplayController displayController,
             SplitScreenController splitScreenController,
-            DesktopModeTaskRepository desktopRepository,
+            DesktopRepository desktopRepository,
             ShellTaskOrganizer taskOrganizer,
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
@@ -448,6 +451,10 @@
             mHandleMenu.relayout(startT, mResult.mCaptionX);
         }
 
+        if (isOpenByDefaultDialogActive()) {
+            mOpenByDefaultDialog.relayout(taskInfo);
+        }
+
         final boolean inFullImmersive = mDesktopRepository
                 .isTaskInFullImmersiveState(taskInfo.taskId);
         updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
@@ -824,6 +831,7 @@
                         WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(),
                         false /* ignoreVisibility */);
                 relayoutParams.mCaptionTopPadding = systemBarInsets.top;
+                relayoutParams.mIsInsetSource = false;
             }
             // Report occluding elements as bounding rects to the insets system so that apps can
             // draw in the empty space in the center:
@@ -939,6 +947,33 @@
         return mHandleMenu != null;
     }
 
+    boolean isOpenByDefaultDialogActive() {
+        return mOpenByDefaultDialog != null;
+    }
+
+    void createOpenByDefaultDialog() {
+        mOpenByDefaultDialog = new OpenByDefaultDialog(
+                mContext,
+                mTaskInfo,
+                mTaskSurface,
+                mDisplayController,
+                mSurfaceControlTransactionSupplier,
+                new OpenByDefaultDialog.DialogLifecycleListener() {
+                    @Override
+                    public void onDialogCreated() {
+                        closeHandleMenu();
+                    }
+
+                    @Override
+                    public void onDialogDismissed() {
+                        mOpenByDefaultDialog = null;
+                    }
+                },
+                mAppIconBitmap,
+                mAppName
+        );
+    }
+
     boolean shouldResizeListenerHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
         return mDragResizeListener != null && mDragResizeListener.shouldHandleEvent(e, offset);
     }
@@ -1235,6 +1270,12 @@
                     onCapturedLinkExpired();
                     return Unit.INSTANCE;
                 },
+                /* onOpenByDefaultClickListener= */ () -> {
+                    if (!isOpenByDefaultDialogActive()) {
+                        createOpenByDefaultDialog();
+                    }
+                    return Unit.INSTANCE;
+                },
                 /* onCloseMenuClickListener= */ () -> {
                     closeHandleMenu();
                     return Unit.INSTANCE;
@@ -1590,7 +1631,7 @@
                 @NonNull Context userContext,
                 DisplayController displayController,
                 SplitScreenController splitScreenController,
-                DesktopModeTaskRepository desktopRepository,
+                DesktopRepository desktopRepository,
                 ShellTaskOrganizer taskOrganizer,
                 ActivityManager.RunningTaskInfo taskInfo,
                 SurfaceControl taskSurface,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 98fef47..2e32703 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -120,6 +120,7 @@
         onNewWindowClickListener: () -> Unit,
         onManageWindowsClickListener: () -> Unit,
         openInBrowserClickListener: (Intent) -> Unit,
+        onOpenByDefaultClickListener: () -> Unit,
         onCloseMenuClickListener: () -> Unit,
         onOutsideTouchListener: () -> Unit,
     ) {
@@ -135,6 +136,7 @@
             onNewWindowClickListener = onNewWindowClickListener,
             onManageWindowsClickListener = onManageWindowsClickListener,
             openInBrowserClickListener = openInBrowserClickListener,
+            onOpenByDefaultClickListener = onOpenByDefaultClickListener,
             onCloseMenuClickListener = onCloseMenuClickListener,
             onOutsideTouchListener = onOutsideTouchListener,
         )
@@ -153,6 +155,7 @@
         onNewWindowClickListener: () -> Unit,
         onManageWindowsClickListener: () -> Unit,
         openInBrowserClickListener: (Intent) -> Unit,
+        onOpenByDefaultClickListener: () -> Unit,
         onCloseMenuClickListener: () -> Unit,
         onOutsideTouchListener: () -> Unit
     ) {
@@ -174,6 +177,7 @@
             this.onOpenInBrowserClickListener = {
                 openInBrowserClickListener.invoke(openInBrowserIntent!!)
             }
+            this.onOpenByDefaultClickListener = onOpenByDefaultClickListener
             this.onCloseMenuClickListener = onCloseMenuClickListener
             this.onOutsideTouchListener = onOutsideTouchListener
         }
@@ -448,7 +452,8 @@
         private val openInBrowserPill = rootView.requireViewById<View>(R.id.open_in_browser_pill)
         private val browserBtn = openInBrowserPill.requireViewById<Button>(
             R.id.open_in_browser_button)
-
+        private val openByDefaultBtn = openInBrowserPill.requireViewById<ImageButton>(
+            R.id.open_by_default_button)
         private val decorThemeUtil = DecorThemeUtil(context)
         private val animator = HandleMenuAnimator(rootView, menuWidth, captionHeight.toFloat())
 
@@ -461,6 +466,7 @@
         var onNewWindowClickListener: (() -> Unit)? = null
         var onManageWindowsClickListener: (() -> Unit)? = null
         var onOpenInBrowserClickListener: (() -> Unit)? = null
+        var onOpenByDefaultClickListener: (() -> Unit)? = null
         var onCloseMenuClickListener: (() -> Unit)? = null
         var onOutsideTouchListener: (() -> Unit)? = null
 
@@ -469,6 +475,9 @@
             splitscreenBtn.setOnClickListener { onToSplitScreenClickListener?.invoke() }
             desktopBtn.setOnClickListener { onToDesktopClickListener?.invoke() }
             browserBtn.setOnClickListener { onOpenInBrowserClickListener?.invoke() }
+            openByDefaultBtn.setOnClickListener {
+                onOpenByDefaultClickListener?.invoke()
+            }
             collapseMenuButton.setOnClickListener { onCloseMenuClickListener?.invoke() }
             newWindowBtn.setOnClickListener { onNewWindowClickListener?.invoke() }
             manageWindowBtn.setOnClickListener { onManageWindowsClickListener?.invoke() }
@@ -634,6 +643,8 @@
                 setTextColor(style.textColor)
                 compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
             }
+
+            openByDefaultBtn.imageTintList = ColorStateList.valueOf(style.textColor)
         }
 
         private data class MenuStyle(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 000beba1..f8aed41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -346,7 +346,7 @@
 
     private void updateCaptionInsets(RelayoutParams params, WindowContainerTransaction wct,
             RelayoutResult<T> outResult, Rect taskBounds) {
-        if (!mIsCaptionVisible) {
+        if (!mIsCaptionVisible || !params.mIsInsetSource) {
             if (mWindowDecorationInsets != null) {
                 mWindowDecorationInsets.remove(wct);
                 mWindowDecorationInsets = null;
@@ -724,6 +724,7 @@
         int mCaptionWidthId;
         final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>();
         int mInputFeatures;
+        boolean mIsInsetSource = true;
         @InsetsSource.Flags int mInsetSourceFlags;
 
         int mShadowRadiusId;
@@ -743,6 +744,7 @@
             mCaptionWidthId = Resources.ID_NULL;
             mOccludingCaptionElements.clear();
             mInputFeatures = 0;
+            mIsInsetSource = true;
             mInsetSourceFlags = 0;
 
             mShadowRadiusId = Resources.ID_NULL;
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
index 29a9f10..7412c1d 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
@@ -15,7 +15,7 @@
 //
 
 package {
-    default_team: "trendy_team_app_compat",
+    default_team: "trendy_team_lse_app_compat",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
index d03d779..d1bf6ac 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
@@ -183,12 +183,6 @@
     }
 
     /** {@inheritDoc} */
-    @FlakyTest(bugId = 312446524)
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    /** {@inheritDoc} */
     @Test
     @FlakyTest(bugId = 336510055)
     override fun entireScreenCovered() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index f8f0db9..0373bbd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -26,6 +26,8 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -34,6 +36,12 @@
 
 import android.graphics.Insets;
 import android.graphics.Point;
+import android.os.Looper;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.IWindowManager;
 import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
@@ -47,6 +55,7 @@
 import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -61,11 +70,16 @@
  */
 @SmallTest
 public class DisplayImeControllerTest extends ShellTestCase {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Mock
     private SurfaceControl.Transaction mT;
     @Mock
     private ShellInit mShellInit;
+    @Mock
+    private IWindowManager mWm;
+    private DisplayImeController mDisplayImeController;
     private DisplayImeController.PerDisplay mPerDisplay;
     private Executor mExecutor;
 
@@ -73,7 +87,8 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mExecutor = spy(Runnable::run);
-        mPerDisplay = new DisplayImeController(null, mShellInit, null, null, new TransactionPool() {
+        mDisplayImeController = new DisplayImeController(mWm, mShellInit, null, null,
+                new TransactionPool() {
             @Override
             public SurfaceControl.Transaction acquire() {
                 return mT;
@@ -84,8 +99,10 @@
             }
         }, mExecutor) {
             @Override
-            void removeImeSurface(int displayId) { }
-        }.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
+            void removeImeSurface(int displayId) {
+            }
+        };
+        mPerDisplay = mDisplayImeController.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
     }
 
     @Test
@@ -95,12 +112,14 @@
 
     @Test
     public void insetsControlChanged_schedulesNoWorkOnExecutor() {
+        Looper.prepare();
         mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
         verifyZeroInteractions(mExecutor);
     }
 
     @Test
     public void insetsChanged_schedulesNoWorkOnExecutor() {
+        Looper.prepare();
         mPerDisplay.insetsChanged(insetsStateWithIme(false));
         verifyZeroInteractions(mExecutor);
     }
@@ -117,7 +136,10 @@
         verifyZeroInteractions(mExecutor);
     }
 
+    // With the refactor, the control's isInitiallyVisible is used to apply to the IME, therefore
+    // this test is obsolete
     @Test
+    @RequiresFlagsDisabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void reappliesVisibilityToChangedLeash() {
         verifyZeroInteractions(mT);
         mPerDisplay.mImeShowing = false;
@@ -136,6 +158,7 @@
 
     @Test
     public void insetsControlChanged_updateImeSourceControl() {
+        Looper.prepare();
         mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
         assertNotNull(mPerDisplay.mImeSourceControl);
 
@@ -143,6 +166,19 @@
         assertNull(mPerDisplay.mImeSourceControl);
     }
 
+    @Test
+    @RequiresFlagsEnabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+    public void setImeInputTargetRequestedVisibility_invokeOnImeRequested() {
+        var mockPp = mock(DisplayImeController.ImePositionProcessor.class);
+        mDisplayImeController.addPositionProcessor(mockPp);
+
+        mPerDisplay.setImeInputTargetRequestedVisibility(true);
+        verify(mockPp).onImeRequested(anyInt(), eq(true));
+
+        mPerDisplay.setImeInputTargetRequestedVisibility(false);
+        verify(mockPp).onImeRequested(anyInt(), eq(false));
+    }
+
     private InsetsSourceControl[] insetsSourceControl() {
         return new InsetsSourceControl[]{
                 new InsetsSourceControl(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index 628c9cdd..3e9c732 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -98,7 +98,7 @@
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var handler: DesktopActivityOrientationChangeHandler
     private lateinit var shellInit: ShellInit
-    private lateinit var taskRepository: DesktopModeTaskRepository
+    private lateinit var taskRepository: DesktopRepository
     private lateinit var testScope: CoroutineScope
     // Mock running tasks are registered here so we can get the list from mock shell task organizer.
     private val runningTasks = mutableListOf<RunningTaskInfo>()
@@ -116,7 +116,7 @@
         testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
         shellInit = spy(ShellInit(testExecutor))
         taskRepository =
-            DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
+            DesktopRepository(context, shellInit, persistentRepository, testScope)
         whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
         whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
         whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index 2b60200..07de0716 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -60,7 +60,7 @@
 class DesktopMixedTransitionHandlerTest : ShellTestCase() {
 
     @Mock lateinit var transitions: Transitions
-    @Mock lateinit var desktopTaskRepository: DesktopModeTaskRepository
+    @Mock lateinit var desktopRepository: DesktopRepository
     @Mock lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
     @Mock lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler
     @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
@@ -75,7 +75,7 @@
             DesktopMixedTransitionHandler(
                 context,
                 transitions,
-                desktopTaskRepository,
+                desktopRepository,
                 freeformTaskTransitionHandler,
                 closeDesktopTaskTransitionHandler,
                 interactionJankMonitor,
@@ -144,7 +144,7 @@
     fun startAnimation_withClosingDesktopTask_callsCloseTaskHandler() {
         val transition = mock<IBinder>()
         val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM))
-        whenever(desktopTaskRepository.getActiveNonMinimizedTaskCount(any())).thenReturn(2)
+        whenever(desktopRepository.getActiveNonMinimizedTaskCount(any())).thenReturn(2)
         whenever(
                 closeDesktopTaskTransitionHandler.startAnimation(any(), any(), any(), any(), any())
             )
@@ -167,7 +167,7 @@
     fun startAnimation_withClosingLastDesktopTask_dispatchesTransition() {
         val transition = mock<IBinder>()
         val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM))
-        whenever(desktopTaskRepository.getActiveNonMinimizedTaskCount(any())).thenReturn(1)
+        whenever(desktopRepository.getActiveNonMinimizedTaskCount(any())).thenReturn(1)
         whenever(transitions.dispatchTransition(any(), any(), any(), any(), any(), any()))
             .thenReturn(mock())
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index 6a5719b..d7a132d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -16,15 +16,22 @@
 
 package com.android.wm.shell.desktopmode
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
 import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
 import com.android.internal.util.FrameworkStatsLog
 import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.window.flags.Flags
 import com.android.wm.shell.EventLogTags
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskSizeUpdate
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_MINIMIZE_REASON
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON
@@ -41,11 +48,15 @@
     private val desktopModeEventLogger = DesktopModeEventLogger()
 
     @JvmField
-    @Rule
+    @Rule(order = 0)
     val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
             .mockStatic(FrameworkStatsLog::class.java)
             .mockStatic(EventLogTags::class.java).build()!!
 
+    @JvmField
+    @Rule(order = 1)
+    val setFlagsRule = SetFlagsRule()
+
     @Test
     fun logSessionEnter_enterReason() = runBlocking {
         desktopModeEventLogger.logSessionEnter(sessionId = SESSION_ID, EnterReason.UNKNOWN_ENTER)
@@ -328,7 +339,65 @@
         }
     }
 
-    companion object {
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_RESIZING_METRICS)
+    fun logTaskResizingStarted_logsTaskSizeUpdatedWithStartResizingStage() = runBlocking {
+        desktopModeEventLogger.logTaskResizingStarted(sessionId = SESSION_ID, createTaskSizeUpdate())
+
+        verify {
+            FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+                /* resize_trigger */
+                eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER),
+                /* resizing_stage */
+                eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE),
+                /* input_method */
+                eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD),
+                /* desktop_mode_session_id */
+                eq(SESSION_ID),
+                /* instance_id */
+                eq(TASK_SIZE_UPDATE.instanceId),
+                /* uid */
+                eq(TASK_SIZE_UPDATE.uid),
+                /* task_height */
+                eq(TASK_SIZE_UPDATE.taskHeight),
+                /* task_width */
+                eq(TASK_SIZE_UPDATE.taskWidth),
+                /* display_area */
+                eq(TASK_SIZE_UPDATE.displayArea),
+            )
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_RESIZING_METRICS)
+    fun logTaskResizingEnded_logsTaskSizeUpdatedWithEndResizingStage() = runBlocking {
+        desktopModeEventLogger.logTaskResizingEnded(sessionId = SESSION_ID, createTaskSizeUpdate())
+
+        verify {
+            FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+                /* resize_trigger */
+                eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER),
+                /* resizing_stage */
+                eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE),
+                /* input_method */
+                eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD),
+                /* desktop_mode_session_id */
+                eq(SESSION_ID),
+                /* instance_id */
+                eq(TASK_SIZE_UPDATE.instanceId),
+                /* uid */
+                eq(TASK_SIZE_UPDATE.uid),
+                /* task_height */
+                eq(TASK_SIZE_UPDATE.taskHeight),
+                /* task_width */
+                eq(TASK_SIZE_UPDATE.taskWidth),
+                /* display_area */
+                eq(TASK_SIZE_UPDATE.displayArea),
+            )
+        }
+    }
+
+    private companion object {
         private const val SESSION_ID = 1
         private const val TASK_ID = 1
         private const val TASK_UID = 1
@@ -337,12 +406,36 @@
         private const val TASK_HEIGHT = 100
         private const val TASK_WIDTH = 100
         private const val TASK_COUNT = 1
+        private const val DISPLAY_AREA = 1000
 
         private val TASK_UPDATE = TaskUpdate(
             TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y,
             visibleTaskCount = TASK_COUNT,
         )
 
+        private val TASK_SIZE_UPDATE = TaskSizeUpdate(
+            resizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER,
+            inputMethod = InputMethod.UNKNOWN_INPUT_METHOD,
+            TASK_ID,
+            TASK_UID,
+            TASK_HEIGHT,
+            TASK_WIDTH,
+            DISPLAY_AREA,
+        )
+
+        private fun createTaskSizeUpdate(
+            resizeTrigger: ResizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER,
+            inputMethod: InputMethod = InputMethod.UNKNOWN_INPUT_METHOD,
+        ) = TaskSizeUpdate(
+            resizeTrigger,
+            inputMethod,
+            TASK_ID,
+            TASK_UID,
+            TASK_HEIGHT,
+            TASK_WIDTH,
+            DISPLAY_AREA,
+        )
+
         private fun createTaskUpdate(
             minimizeReason: MinimizeReason? = null,
             unminimizeReason: UnminimizeReason? = null,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index 2b7f86f..935e6d0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -17,8 +17,8 @@
 package com.android.wm.shell.desktopmode
 
 import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.PointF
 import android.graphics.Rect
-import android.graphics.Region
 import android.testing.AndroidTestingRunner
 import android.view.SurfaceControl
 import androidx.test.filters.SmallTest
@@ -33,6 +33,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.kotlin.whenever
 
@@ -58,13 +59,15 @@
         whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
         whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
         whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS)
+        whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
     }
 
     @Test
     fun testFullscreenRegionCalculation() {
         createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
         var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
-        assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
+        assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400,
+            2 * STABLE_INSETS.top))
 
         createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
         testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
@@ -75,17 +78,19 @@
         val toFullscreenWidth = displayLayout.width() * toFullscreenScale
         assertThat(testRegion.bounds).isEqualTo(Rect(
             (DISPLAY_BOUNDS.width() / 2f - toFullscreenWidth / 2f).toInt(),
-            -50,
+            Short.MIN_VALUE.toInt(),
             (DISPLAY_BOUNDS.width() / 2f + toFullscreenWidth / 2f).toInt(),
             transitionHeight))
 
         createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
         testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
-        assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
+        assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400,
+            2 * STABLE_INSETS.top))
 
         createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
         testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
-        assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+        assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400,
+            transitionHeight))
     }
 
     @Test
@@ -133,22 +138,19 @@
     }
 
     @Test
-    fun testToDesktopRegionCalculation() {
+    fun testDefaultIndicators() {
         createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
-        val fullscreenRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
-            CAPTION_HEIGHT)
-        val splitLeftRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
-            TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
-        val splitRightRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
-            TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
-        val desktopRegion = visualIndicator.calculateToDesktopRegion(displayLayout,
-            splitLeftRegion, splitRightRegion, fullscreenRegion)
-        var testRegion = Region()
-        testRegion.union(DISPLAY_BOUNDS)
-        testRegion.op(splitLeftRegion, Region.Op.DIFFERENCE)
-        testRegion.op(splitRightRegion, Region.Op.DIFFERENCE)
-        testRegion.op(fullscreenRegion, Region.Op.DIFFERENCE)
-        assertThat(desktopRegion).isEqualTo(testRegion)
+        var result = visualIndicator.updateIndicatorType(PointF(-10000f, 500f))
+        assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
+        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+        result = visualIndicator.updateIndicatorType(PointF(10000f, 500f))
+        assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
+        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
+        result = visualIndicator.updateIndicatorType(PointF(500f, 10000f))
+        assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
+        result = visualIndicator.updateIndicatorType(PointF(500f, 10000f))
+        assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
     }
 
     private fun createVisualIndicator(dragStartState: DesktopModeVisualIndicator.DragStartState) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 97ceecc..55b9724 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -56,9 +56,9 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @ExperimentalCoroutinesApi
-class DesktopModeTaskRepositoryTest : ShellTestCase() {
+class DesktopRepositoryTest : ShellTestCase() {
 
-    private lateinit var repo: DesktopModeTaskRepository
+    private lateinit var repo: DesktopRepository
     private lateinit var shellInit: ShellInit
     private lateinit var datastoreScope: CoroutineScope
 
@@ -71,7 +71,7 @@
         datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
         shellInit = spy(ShellInit(testExecutor))
 
-        repo = DesktopModeTaskRepository(context, shellInit, persistentRepository, datastoreScope)
+        repo = DesktopRepository(context, shellInit, persistentRepository, datastoreScope)
         whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
             Desktop.getDefaultInstance()
         )
@@ -940,7 +940,7 @@
         assertThat(repo.isTaskInFullImmersiveState(taskId = 2)).isTrue()
     }
 
-    class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
+    class TestListener : DesktopRepository.ActiveTasksListener {
         var activeChangesOnDefaultDisplay = 0
         var activeChangesOnSecondaryDisplay = 0
         override fun onActiveTasksChanged(displayId: Int) {
@@ -952,7 +952,7 @@
         }
     }
 
-    class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener {
+    class TestVisibilityListener : DesktopRepository.VisibleTasksListener {
         var visibleTasksCountOnDefaultDisplay = 0
         var visibleTasksCountOnSecondaryDisplay = 0
 
@@ -980,4 +980,4 @@
         private const val DEFAULT_USER_ID = 1000
         private const val DEFAULT_DESKTOP_ID = 0
     }
-}
+}
\ No newline at end of file
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 2ddb1ac..0ccd160 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
@@ -201,7 +201,7 @@
   private lateinit var mockitoSession: StaticMockitoSession
   private lateinit var controller: DesktopTasksController
   private lateinit var shellInit: ShellInit
-  private lateinit var taskRepository: DesktopModeTaskRepository
+  private lateinit var taskRepository: DesktopRepository
   private lateinit var desktopTasksLimiter: DesktopTasksLimiter
   private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
   private lateinit var testScope: CoroutineScope
@@ -232,7 +232,7 @@
 
     testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
     shellInit = spy(ShellInit(testExecutor))
-    taskRepository = DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
+    taskRepository = DesktopRepository(context, shellInit, persistentRepository, testScope)
     desktopTasksLimiter =
         DesktopTasksLimiter(
             transitions,
@@ -2643,13 +2643,17 @@
   @Test
   fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() {
     val task = setUpFreeformTask()
+    val spyController = spy(controller)
     val mockSurface = mock(SurfaceControl::class.java)
     val mockDisplayLayout = mock(DisplayLayout::class.java)
     whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
     whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
-    controller.onDragPositioningMove(task, mockSurface, 200f, Rect(100, -100, 500, 1000))
+    spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, -100, 500, 1000))
 
-    controller.onDragPositioningEnd(
+    whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+      .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+    spyController.onDragPositioningEnd(
         task,
         mockSurface,
         Point(100, -100), /* position */
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 bdcb459..596b76d 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
@@ -92,7 +92,7 @@
 
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var desktopTasksLimiter: DesktopTasksLimiter
-    private lateinit var desktopTaskRepo: DesktopModeTaskRepository
+    private lateinit var desktopTaskRepo: DesktopRepository
     private lateinit var shellInit: ShellInit
     private lateinit var testScope: CoroutineScope
 
@@ -106,7 +106,7 @@
         testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
 
         desktopTaskRepo =
-            DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
+            DesktopRepository(context, shellInit, persistentRepository, testScope)
         desktopTasksLimiter =
             DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT,
                 interactionJankMonitor, mContext, handler)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index 42fcc83..598df34 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -62,7 +62,7 @@
     private val transitions = mock<Transitions>()
     private val context = mock<Context>()
     private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
-    private val taskRepository = mock<DesktopModeTaskRepository>()
+    private val taskRepository = mock<DesktopRepository>()
 
     private lateinit var transitionObserver: DesktopTasksTransitionObserver
     private lateinit var shellInit: ShellInit
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index d9387d2..230f7e6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -581,7 +581,7 @@
                 )
             )
             .thenReturn(token)
-        handler.startDragToDesktopTransition(task.taskId, dragAnimator)
+        handler.startDragToDesktopTransition(task, dragAnimator)
         return token
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
index 5596ad7..1e105d9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
@@ -46,6 +46,7 @@
 import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.test.setMain
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -61,10 +62,7 @@
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
-/**
- * Tests of [AppHandleEducationController]
- * Usage: atest AppHandleEducationControllerTest
- */
+/** Tests of [AppHandleEducationController] Usage: atest AppHandleEducationControllerTest */
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -220,6 +218,7 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+  @Ignore("b/371527084: revisit testcase after refactoring original logic")
   fun showWindowingImageButtonTooltip_appHandleExpanded_shouldCallShowEducationTooltipTwice() =
       testScope.runTest {
         // After first tooltip is dismissed, app handle is expanded. Should show second education
@@ -237,6 +236,7 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+  @Ignore("b/371527084: revisit testcase after refactoring original logic")
   fun showWindowingImageButtonTooltip_appHandleExpandedAfterTimeout_shouldCallShowEducationTooltipOnce() =
       testScope.runTest {
         // After first tooltip is dismissed, app handle is expanded after timeout. Should not show
@@ -258,6 +258,7 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+  @Ignore("b/371527084: revisit testcase after refactoring original logic")
   fun showWindowingImageButtonTooltip_appHandleExpandedTwice_shouldCallShowEducationTooltipTwice() =
       testScope.runTest {
         // After first tooltip is dismissed, app handle is expanded twice. Should show second
@@ -279,6 +280,7 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+  @Ignore("b/371527084: revisit testcase after refactoring original logic")
   fun showWindowingImageButtonTooltip_appHandleNotExpanded_shouldCallShowEducationTooltipOnce() =
       testScope.runTest {
         // After first tooltip is dismissed, app handle is not expanded. Should not show second
@@ -296,6 +298,7 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+  @Ignore("b/371527084: revisit testcase after refactoring original logic")
   fun showExitWindowingButtonTooltip_appHeaderVisible_shouldCallShowEducationTooltipThrice() =
       testScope.runTest {
         // After first two tooltips are dismissed, app header is visible. Should show third
@@ -313,6 +316,7 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+  @Ignore("b/371527084: revisit testcase after refactoring original logic")
   fun showExitWindowingButtonTooltip_appHeaderVisibleAfterTimeout_shouldCallShowEducationTooltipTwice() =
       testScope.runTest {
         // After first two tooltips are dismissed, app header is visible after timeout. Should not
@@ -334,6 +338,7 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+  @Ignore("b/371527084: revisit testcase after refactoring original logic")
   fun showExitWindowingButtonTooltip_appHeaderVisibleTwice_shouldCallShowEducationTooltipThrice() =
       testScope.runTest {
         // After first two tooltips are dismissed, app header is visible twice. Should show third
@@ -354,6 +359,7 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+  @Ignore("b/371527084: revisit testcase after refactoring original logic")
   fun showExitWindowingButtonTooltip_appHeaderExpanded_shouldCallShowEducationTooltipTwice() =
       testScope.runTest {
         // After first two tooltips are dismissed, app header is visible but expanded. Should not
@@ -393,6 +399,7 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+  @Ignore("b/371527084: revisit testcase after refactoring original logic")
   fun setAppHandleEducationTooltipCallbacks_onWindowingImageButtonTooltipClicked_callbackInvoked() =
       testScope.runTest {
         // After first tooltip is dismissed, app handle is expanded. Should show second education
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 3b2c7e6..956ef14 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -41,7 +41,7 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.common.LaunchAdjacentController;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -73,7 +73,7 @@
     @Mock
     private SurfaceControl mMockSurfaceControl;
     @Mock
-    private DesktopModeTaskRepository mDesktopModeTaskRepository;
+    private DesktopRepository mDesktopRepository;
     @Mock
     private LaunchAdjacentController mLaunchAdjacentController;
     private FreeformTaskListener mFreeformTaskListener;
@@ -89,7 +89,7 @@
                 mContext,
                 mShellInit,
                 mTaskOrganizer,
-                Optional.of(mDesktopModeTaskRepository),
+                Optional.of(mDesktopRepository),
                 mLaunchAdjacentController,
                 mWindowDecorViewModel);
     }
@@ -102,7 +102,7 @@
 
         mFreeformTaskListener.onFocusTaskChanged(task);
 
-        verify(mDesktopModeTaskRepository)
+        verify(mDesktopRepository)
             .addOrMoveFreeformTaskToTop(task.displayId, task.taskId);
     }
 
@@ -114,7 +114,7 @@
 
         mFreeformTaskListener.onFocusTaskChanged(fullscreenTask);
 
-        verify(mDesktopModeTaskRepository, never())
+        verify(mDesktopRepository, never())
                 .addOrMoveFreeformTaskToTop(fullscreenTask.displayId, fullscreenTask.taskId);
     }
 
@@ -156,7 +156,7 @@
         task.displayId = INVALID_DISPLAY;
         mFreeformTaskListener.onTaskVanished(task);
 
-        verify(mDesktopModeTaskRepository).minimizeTask(task.displayId, task.taskId);
+        verify(mDesktopRepository).minimizeTask(task.displayId, task.taskId);
     }
 
     @Test
@@ -168,13 +168,13 @@
 
         mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
 
-        when(mDesktopModeTaskRepository.isClosingTask(task.taskId)).thenReturn(true);
+        when(mDesktopRepository.isClosingTask(task.taskId)).thenReturn(true);
         task.isVisible = false;
         task.displayId = INVALID_DISPLAY;
         mFreeformTaskListener.onTaskVanished(task);
 
-        verify(mDesktopModeTaskRepository, never()).minimizeTask(task.displayId, task.taskId);
-        verify(mDesktopModeTaskRepository).removeFreeformTask(task.displayId, task.taskId);
+        verify(mDesktopRepository, never()).minimizeTask(task.displayId, task.taskId);
+        verify(mDesktopRepository).removeFreeformTask(task.displayId, task.taskId);
     }
 
     @After
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 753d4cd..9b73d53 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
 
 import static org.junit.Assert.assertEquals;
@@ -50,6 +51,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.platform.test.annotations.DisableFlags;
@@ -68,7 +70,7 @@
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.shared.GroupedRecentTaskInfo;
 import com.android.wm.shell.shared.ShellSharedConstants;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -107,7 +109,7 @@
     @Mock
     private ShellCommandHandler mShellCommandHandler;
     @Mock
-    private DesktopModeTaskRepository mDesktopModeTaskRepository;
+    private DesktopRepository mDesktopRepository;
     @Mock
     private ActivityTaskManager mActivityTaskManager;
     @Mock
@@ -144,7 +146,7 @@
                 mDisplayInsetsController, mMainExecutor));
         mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
                 mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
-                Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver,
+                Optional.of(mDesktopRepository), mTaskStackTransitionObserver,
                 mMainExecutor);
         mRecentTasksController = spy(mRecentTasksControllerReal);
         mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
@@ -303,8 +305,8 @@
         ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
         setRawList(t1, t2, t3, t4);
 
-        when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
-        when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
+        when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+        when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
 
         ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
                 MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -342,8 +344,8 @@
                 new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_2_50_50);
         mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, pair1Bounds);
 
-        when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
-        when(mDesktopModeTaskRepository.isActiveTask(5)).thenReturn(true);
+        when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
+        when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
 
         ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
                 MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -382,8 +384,8 @@
         ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
         setRawList(t1, t2, t3, t4);
 
-        when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
-        when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
+        when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+        when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
 
         ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
                 MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -410,10 +412,10 @@
         ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
         setRawList(t1, t2, t3, t4, t5);
 
-        when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
-        when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
-        when(mDesktopModeTaskRepository.isActiveTask(5)).thenReturn(true);
-        when(mDesktopModeTaskRepository.isMinimizedTask(3)).thenReturn(true);
+        when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+        when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
+        when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
+        when(mDesktopRepository.isMinimizedTask(3)).thenReturn(true);
 
         ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
                 MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -441,6 +443,40 @@
     }
 
     @Test
+    @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+    public void testGetRecentTasks_hasDesktopTasks_persistenceEnabled_freeformTaskHaveBoundsSet() {
+        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+
+        t1.lastNonFullscreenBounds = new Rect(100, 200, 300, 400);
+        t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450);
+        setRawList(t1, t2);
+
+        when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+        when(mDesktopRepository.isActiveTask(2)).thenReturn(true);
+
+        ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
+                MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+
+        assertEquals(1, recentTasks.size());
+        GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
+
+        // Check bounds
+        assertEquals(t1.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get(
+                0).configuration.windowConfiguration.getAppBounds());
+        assertEquals(t2.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get(
+                1).configuration.windowConfiguration.getAppBounds());
+
+        // Check position in parent
+        assertEquals(new Point(t1.lastNonFullscreenBounds.left,
+                        t1.lastNonFullscreenBounds.top),
+                freeformGroup.getTaskInfoList().get(0).positionInParent);
+        assertEquals(new Point(t2.lastNonFullscreenBounds.left,
+                        t2.lastNonFullscreenBounds.top),
+                freeformGroup.getTaskInfoList().get(1).positionInParent);
+    }
+
+    @Test
     public void testRemovedTaskRemovesSplit() {
         ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
         ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
@@ -623,6 +659,7 @@
     private ActivityManager.RecentTaskInfo makeTaskInfo(int taskId) {
         ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo();
         info.taskId = taskId;
+        info.lastNonFullscreenBounds = new Rect();
         return info;
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index 769acf7..0effc3e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -48,7 +48,7 @@
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
@@ -82,7 +82,7 @@
     @Mock
     private ShellCommandHandler mShellCommandHandler;
     @Mock
-    private DesktopModeTaskRepository mDesktopModeTaskRepository;
+    private DesktopRepository mDesktopRepository;
     @Mock
     private ActivityTaskManager mActivityTaskManager;
     @Mock
@@ -120,7 +120,7 @@
                 mDisplayInsetsController, mMainExecutor));
         mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
                 mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
-                Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver,
+                Optional.of(mDesktopRepository), mTaskStackTransitionObserver,
                 mMainExecutor);
         mRecentTasksController = spy(mRecentTasksControllerReal);
         mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 5ae4ca8..83bd15b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -87,7 +87,7 @@
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository
+import com.android.wm.shell.desktopmode.DesktopRepository
 import com.android.wm.shell.desktopmode.DesktopTasksController
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
 import com.android.wm.shell.desktopmode.DesktopTasksLimiter
@@ -161,7 +161,7 @@
     @Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer
     @Mock private lateinit var mockDisplayController: DisplayController
     @Mock private lateinit var mockSplitScreenController: SplitScreenController
-    @Mock private lateinit var mockDesktopRepository: DesktopModeTaskRepository
+    @Mock private lateinit var mockDesktopRepository: DesktopRepository
     @Mock private lateinit var mockDisplayLayout: DisplayLayout
     @Mock private lateinit var displayInsetsController: DisplayInsetsController
     @Mock private lateinit var mockSyncQueue: SyncTransactionQueue
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 3e7f3bd..35be80e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -106,7 +106,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.desktopmode.CaptionState;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -163,7 +163,7 @@
     @Mock
     private ShellTaskOrganizer mMockShellTaskOrganizer;
     @Mock
-    private DesktopModeTaskRepository mMockDesktopRepository;
+    private DesktopRepository mMockDesktopRepository;
     @Mock
     private Choreographer mMockChoreographer;
     @Mock
@@ -619,6 +619,27 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+    public void updateRelayoutParams_header_notAnInsetsSourceInFullyImmersive() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        final RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false,
+                /* isStatusBarVisible */ true,
+                /* isKeyguardVisibleAndOccluded */ false,
+                /* inFullImmersiveMode */ true,
+                new InsetsState());
+
+        assertThat(relayoutParams.mIsInsetSource).isFalse();
+    }
+
+    @Test
     @DisableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
     public void updateRelayoutParams_header_statusBarInvisible_captionVisible() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
@@ -1025,6 +1046,7 @@
                 any(),
                 openInBrowserCaptor.capture(),
                 any(),
+                any(),
                 any()
         );
         openInBrowserCaptor.getValue().invoke(new Intent(Intent.ACTION_MAIN, TEST_URI1));
@@ -1053,6 +1075,7 @@
                 any(),
                 openInBrowserCaptor.capture(),
                 any(),
+                any(),
                 any()
         );
 
@@ -1103,6 +1126,7 @@
                 any(),
                 any(),
                 any(),
+                any(),
                 closeClickListener.capture(),
                 any()
         );
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index cabd472..1820133 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -240,7 +240,7 @@
             null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
             captionX = captionX
         )
-        handleMenu.show(mock(), mock(), mock(), mock(), mock(), mock(), mock(), mock())
+        handleMenu.show(mock(), mock(), mock(), mock(), mock(), mock(), mock(), mock(), mock())
         return handleMenu
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 94cabc4..54dd15ba 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -650,6 +650,57 @@
     }
 
     @Test
+    public void testRelayout_notAnInsetsSource_doesNotAddInsets() {
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setVisible(true)
+                .setBounds(new Rect(0, 0, 1000, 1000))
+                .build();
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+        mRelayoutParams.mIsInsetSource = false;
+        windowDecor.relayout(taskInfo);
+
+        // Never added.
+        verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
+                eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
+        verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
+                eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
+    }
+
+    @Test
+    public void testRelayout_notAnInsetsSource_hadInsetsBefore_removesInsets() {
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setVisible(true)
+                .setBounds(new Rect(0, 0, 1000, 1000))
+                .build();
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+        mRelayoutParams.mIsCaptionVisible = true;
+        mRelayoutParams.mIsInsetSource = true;
+        windowDecor.relayout(taskInfo);
+
+        mRelayoutParams.mIsCaptionVisible = true;
+        mRelayoutParams.mIsInsetSource = false;
+        windowDecor.relayout(taskInfo);
+
+        // Insets should be removed.
+        verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+                eq(0) /* index */, eq(captionBar()));
+        verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+                eq(0) /* index */, eq(mandatorySystemGestures()));
+    }
+
+    @Test
     public void testClose_withExistingInsets_insetsRemoved() {
         final Display defaultDisplay = mock(Display.class);
         doReturn(defaultDisplay).when(mMockDisplayController)
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index a39f30b..385fbfe 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -75,6 +75,7 @@
         "BigBufferStream.cpp",
         "ChunkIterator.cpp",
         "ConfigDescription.cpp",
+        "CursorWindow.cpp",
         "FileStream.cpp",
         "Idmap.cpp",
         "LoadedArsc.cpp",
@@ -113,7 +114,6 @@
             srcs: [
                 "BackupData.cpp",
                 "BackupHelpers.cpp",
-                "CursorWindow.cpp",
             ],
             shared_libs: [
                 "libbase",
@@ -147,11 +147,6 @@
                 "libz",
             ],
         },
-        host_linux: {
-            srcs: [
-                "CursorWindow.cpp",
-            ],
-        },
         windows: {
             enabled: true,
         },
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index cbb1e8f..abf2b0a 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -139,6 +139,7 @@
     return UNKNOWN_ERROR;
 }
 
+#ifdef __linux__
 status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outWindow) {
     *outWindow = nullptr;
 
@@ -240,6 +241,7 @@
 fail_silent:
     return UNKNOWN_ERROR;
 }
+#endif
 
 status_t CursorWindow::clear() {
     if (mReadOnly) {
diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h
index c2eac12..0996355 100644
--- a/libs/androidfw/include/androidfw/CursorWindow.h
+++ b/libs/androidfw/include/androidfw/CursorWindow.h
@@ -23,7 +23,9 @@
 #include <string>
 
 #include "android-base/stringprintf.h"
+#ifdef __linux__
 #include "binder/Parcel.h"
+#endif
 #include "utils/String8.h"
 
 #include "android-base/mapped_file.h"
@@ -82,9 +84,11 @@
     ~CursorWindow();
 
     static status_t create(const String8& name, size_t size, CursorWindow** outCursorWindow);
+#ifdef __linux__
     static status_t createFromParcel(Parcel* parcel, CursorWindow** outCursorWindow);
 
     status_t writeToParcel(Parcel* parcel);
+#endif
 
     inline String8 name() { return mName; }
     inline size_t size() { return mSize; }
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index bb0fc41..bc269fe 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -16,7 +16,7 @@
     ctor public AppFunctionService();
     method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
     method @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
-    method @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+    method @Deprecated @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
     field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
     field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
   }
@@ -45,12 +45,12 @@
     method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
     field public static final String PROPERTY_RETURN_VALUE = "returnValue";
     field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
+    field public static final int RESULT_CANCELLED = 6; // 0x6
     field public static final int RESULT_DENIED = 1; // 0x1
-    field public static final int RESULT_DISABLED = 6; // 0x6
+    field public static final int RESULT_DISABLED = 5; // 0x5
     field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
     field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
     field public static final int RESULT_OK = 0; // 0x0
-    field public static final int RESULT_TIMED_OUT = 5; // 0x5
   }
 
 }
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
index 6023c97..6e91de6 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
@@ -26,6 +26,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.CancellationSignal;
+import android.util.Log;
 
 import java.util.function.Consumer;
 
@@ -143,7 +144,11 @@
      */
     @MainThread
     @Deprecated
-    public abstract void onExecuteFunction(
+    public void onExecuteFunction(
             @NonNull ExecuteAppFunctionRequest request,
-            @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+            @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+        Log.w(
+                "AppFunctionService",
+                "Calling deprecated default implementation of onExecuteFunction");
+    }
 }
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
index c7ce95b..d87fec79 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
@@ -73,11 +73,14 @@
      */
     public static final int RESULT_INVALID_ARGUMENT = 4;
 
-    /** The operation was timed out. */
-    public static final int RESULT_TIMED_OUT = 5;
-
     /** The caller tried to execute a disabled app function. */
-    public static final int RESULT_DISABLED = 6;
+    public static final int RESULT_DISABLED = 5;
+
+    /**
+     * The operation was cancelled. Use this error code to report that a cancellation is done after
+     * receiving a cancellation signal.
+     */
+    public static final int RESULT_CANCELLED = 6;
 
     /** The result code of the app function execution. */
     @ResultCode private final int mResultCode;
@@ -236,7 +239,6 @@
                     RESULT_APP_UNKNOWN_ERROR,
                     RESULT_INTERNAL_ERROR,
                     RESULT_INVALID_ARGUMENT,
-                    RESULT_TIMED_OUT,
                     RESULT_DISABLED
             })
     @Retention(RetentionPolicy.SOURCE)
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index 27add35..4305196 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -141,6 +141,13 @@
         return;
     }
 
+    if (!RenderThread::isCurrent()) {
+        // releaseQueueOwnership needs to run on RenderThread to prevent multithread calling
+        // setBackendTextureState will operate skia resource cache which need single owner
+        RenderThread::getInstance().queue().post([this, context]() { releaseQueueOwnership(context); });
+        return;
+    }
+
     LOG_ALWAYS_FATAL_IF(Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan);
     if (mBackendTexture.isValid()) {
         // Passing in VK_IMAGE_LAYOUT_UNDEFINED means we keep the old layout.
diff --git a/libs/hwui/Gainmap.cpp b/libs/hwui/Gainmap.cpp
index 30f401e..ea955e2 100644
--- a/libs/hwui/Gainmap.cpp
+++ b/libs/hwui/Gainmap.cpp
@@ -15,12 +15,37 @@
  */
 #include "Gainmap.h"
 
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkColorFilter.h>
+#include <SkImagePriv.h>
+#include <SkPaint.h>
+
+#include "HardwareBitmapUploader.h"
+
 namespace android::uirenderer {
 
 sp<Gainmap> Gainmap::allocateHardwareGainmap(const sp<Gainmap>& srcGainmap) {
     auto gainmap = sp<Gainmap>::make();
     gainmap->info = srcGainmap->info;
-    const SkBitmap skSrcBitmap = srcGainmap->bitmap->getSkBitmap();
+    SkBitmap skSrcBitmap = srcGainmap->bitmap->getSkBitmap();
+    if (skSrcBitmap.info().colorType() == kAlpha_8_SkColorType &&
+        !HardwareBitmapUploader::hasAlpha8Support()) {
+        // The regular Bitmap::allocateHardwareBitmap will do a conversion that preserves channels,
+        // so alpha8 maps to the alpha channel of rgba. However, for gainmaps we will interpret
+        // the data of an rgba buffer differently as we'll only look at the rgb channels
+        // So we need to map alpha8 to rgbx_8888 essentially
+        SkBitmap bitmap;
+        bitmap.allocPixels(skSrcBitmap.info().makeColorType(kN32_SkColorType));
+        SkCanvas canvas(bitmap);
+        SkPaint paint;
+        const float alphaToOpaque[] = {0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+                                       0, 0, 0, 1, 0, 0, 0, 0, 0, 255};
+        paint.setColorFilter(SkColorFilters::Matrix(alphaToOpaque, SkColorFilters::Clamp::kNo));
+        canvas.drawImage(SkMakeImageFromRasterBitmap(skSrcBitmap, kNever_SkCopyPixelsMode), 0, 0,
+                         SkSamplingOptions{}, &paint);
+        skSrcBitmap = bitmap;
+    }
     sk_sp<Bitmap> skBitmap(Bitmap::allocateHardwareBitmap(skSrcBitmap));
     if (!skBitmap.get()) {
         return nullptr;
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 588463c..e074a27 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -501,18 +501,13 @@
 SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination, bool isShared) {
     ATRACE_CALL();
     SkGainmapInfo gainmapInfo;
-    std::unique_ptr<SkStream> gainmapStream;
+    std::unique_ptr<SkAndroidCodec> gainmapCodec;
     {
-        ATRACE_NAME("getAndroidGainmap");
-        if (!mCodec->getAndroidGainmap(&gainmapInfo, &gainmapStream)) {
+        ATRACE_NAME("getGainmapAndroidCodec");
+        if (!mCodec->getGainmapAndroidCodec(&gainmapInfo, &gainmapCodec)) {
             return SkCodec::kSuccess;
         }
     }
-    auto gainmapCodec = SkAndroidCodec::MakeFromStream(std::move(gainmapStream));
-    if (!gainmapCodec) {
-        ALOGW("Failed to create codec for gainmap stream");
-        return SkCodec::kInvalidInput;
-    }
     ImageDecoder decoder{std::move(gainmapCodec)};
     // Gainmap inherits the origin of the containing image
     decoder.mOverrideOrigin.emplace(getOrigin());
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 785aef3..49a7f73 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -183,14 +183,8 @@
            needsFineScale(fullSize.height(), decodedSize.height(), sampleSize);
 }
 
-static bool decodeGainmap(std::unique_ptr<SkStream> gainmapStream, const SkGainmapInfo& gainmapInfo,
+static bool decodeGainmap(std::unique_ptr<SkAndroidCodec> codec, const SkGainmapInfo& gainmapInfo,
                           sp<uirenderer::Gainmap>* outGainmap, const int sampleSize, float scale) {
-    std::unique_ptr<SkAndroidCodec> codec;
-    codec = SkAndroidCodec::MakeFromStream(std::move(gainmapStream), nullptr);
-    if (!codec) {
-        ALOGE("Can not create a codec for Gainmap.");
-        return false;
-    }
     SkColorType decodeColorType = kN32_SkColorType;
     if (codec->getInfo().colorType() == kGray_8_SkColorType) {
         decodeColorType = kGray_8_SkColorType;
@@ -613,15 +607,15 @@
 
     bool hasGainmap = false;
     SkGainmapInfo gainmapInfo;
-    std::unique_ptr<SkStream> gainmapStream = nullptr;
+    std::unique_ptr<SkAndroidCodec> gainmapCodec;
     sp<uirenderer::Gainmap> gainmap = nullptr;
     if (result == SkCodec::kSuccess) {
-        hasGainmap = codec->getAndroidGainmap(&gainmapInfo, &gainmapStream);
+        hasGainmap = codec->getGainmapAndroidCodec(&gainmapInfo, &gainmapCodec);
     }
 
     if (hasGainmap) {
         hasGainmap =
-                decodeGainmap(std::move(gainmapStream), gainmapInfo, &gainmap, sampleSize, scale);
+                decodeGainmap(std::move(gainmapCodec), gainmapInfo, &gainmap, sampleSize, scale);
     }
 
     if (!isMutable && javaBitmap == NULL) {
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 6a65b82..f7e8e07 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -48,25 +48,14 @@
         }
 
         SkGainmapInfo gainmapInfo;
-        std::unique_ptr<SkStream> gainmapStream;
+        std::unique_ptr<SkAndroidCodec> gainmapCodec;
         std::unique_ptr<skia::BitmapRegionDecoder> gainmapBRD = nullptr;
-        if (mainImageBRD->getAndroidGainmap(&gainmapInfo, &gainmapStream)) {
-            sk_sp<SkData> data = nullptr;
-            if (gainmapStream->getMemoryBase()) {
-                // It is safe to make without copy because we'll hold onto the stream.
-                data = SkData::MakeWithoutCopy(gainmapStream->getMemoryBase(),
-                                               gainmapStream->getLength());
-            } else {
-                data = SkCopyStreamToData(gainmapStream.get());
-                // We don't need to hold the stream anymore
-                gainmapStream = nullptr;
-            }
-            gainmapBRD = skia::BitmapRegionDecoder::Make(std::move(data));
+        if (!mainImageBRD->getGainmapBitmapRegionDecoder(&gainmapInfo, &gainmapBRD)) {
+            gainmapBRD = nullptr;
         }
 
-        return std::unique_ptr<BitmapRegionDecoderWrapper>(
-                new BitmapRegionDecoderWrapper(std::move(mainImageBRD), std::move(gainmapBRD),
-                                               gainmapInfo, std::move(gainmapStream)));
+        return std::unique_ptr<BitmapRegionDecoderWrapper>(new BitmapRegionDecoderWrapper(
+                std::move(mainImageBRD), std::move(gainmapBRD), gainmapInfo));
     }
 
     SkEncodedImageFormat getEncodedFormat() { return mMainImageBRD->getEncodedFormat(); }
@@ -191,16 +180,14 @@
 private:
     BitmapRegionDecoderWrapper(std::unique_ptr<skia::BitmapRegionDecoder> mainImageBRD,
                                std::unique_ptr<skia::BitmapRegionDecoder> gainmapBRD,
-                               SkGainmapInfo info, std::unique_ptr<SkStream> stream)
+                               SkGainmapInfo info)
             : mMainImageBRD(std::move(mainImageBRD))
             , mGainmapBRD(std::move(gainmapBRD))
-            , mGainmapInfo(info)
-            , mGainmapStream(std::move(stream)) {}
+            , mGainmapInfo(info) {}
 
     std::unique_ptr<skia::BitmapRegionDecoder> mMainImageBRD;
     std::unique_ptr<skia::BitmapRegionDecoder> mGainmapBRD;
     SkGainmapInfo mGainmapInfo;
-    std::unique_ptr<SkStream> mGainmapStream;
 };
 }  // namespace android
 
diff --git a/libs/hwui/jni/graphics_jni_helpers.h b/libs/hwui/jni/graphics_jni_helpers.h
index 78db54a..91db134 100644
--- a/libs/hwui/jni/graphics_jni_helpers.h
+++ b/libs/hwui/jni/graphics_jni_helpers.h
@@ -80,9 +80,52 @@
     return static_cast<T>(res);
 }
 
+//  Inline variable that specifies the method binding format.
+//  The expected format is 'XX${method}XX', where ${method} represents the original method name.
+//  This variable is shared across all translation units. This is treated as a global variable as
+//  per C++ 17.
+inline std::string jniMethodFormat;
+
+inline static void setJniMethodFormat(std::string value) {
+    jniMethodFormat = value;
+}
+
+// Register the native methods, potenially applying the jniMethodFormat if it has been set.
+static inline int jniRegisterMaybeRenamedNativeMethods(JNIEnv* env, const char* className,
+                                                       const JNINativeMethod* gMethods,
+                                                       int numMethods) {
+    if (jniMethodFormat.empty()) {
+        return jniRegisterNativeMethods(env, className, gMethods, numMethods);
+    }
+
+    // Make a copy of gMethods with reformatted method names.
+    JNINativeMethod* modifiedMethods = new JNINativeMethod[numMethods];
+    LOG_ALWAYS_FATAL_IF(!modifiedMethods, "Failed to allocate a copy of the JNI methods");
+
+    size_t methodNamePos = jniMethodFormat.find("${method}");
+    LOG_ALWAYS_FATAL_IF(methodNamePos == std::string::npos,
+                        "Invalid jniMethodFormat: could not find '${method}' in pattern");
+
+    for (int i = 0; i < numMethods; i++) {
+        modifiedMethods[i] = gMethods[i];
+        std::string modifiedName = jniMethodFormat;
+        modifiedName.replace(methodNamePos, 9, gMethods[i].name);
+        char* modifiedNameChars = new char[modifiedName.length() + 1];
+        LOG_ALWAYS_FATAL_IF(!modifiedNameChars, "Failed to allocate the new method name");
+        std::strcpy(modifiedNameChars, modifiedName.c_str());
+        modifiedMethods[i].name = modifiedNameChars;
+    }
+    int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods);
+    for (int i = 0; i < numMethods; i++) {
+        delete[] modifiedMethods[i].name;
+    }
+    delete[] modifiedMethods;
+    return res;
+}
+
 static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
                                        const JNINativeMethod* gMethods, int numMethods) {
-    int res = jniRegisterNativeMethods(env, className, gMethods, numMethods);
+    int res = jniRegisterMaybeRenamedNativeMethods(env, className, gMethods, numMethods);
     LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
     return res;
 }
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index cc9a97c..6a7e693 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -157,3 +157,11 @@
     description: "Enable EUICC card emulation"
     bug: "321314635"
 }
+
+flag {
+    name: "nfc_state_change_security_log_event_enabled"
+    is_exported: true
+    namespace: "nfc"
+    description: "Enabling security log for nfc state change"
+    bug: "319934052"
+}
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index bc5b691..b266912 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -50,13 +50,13 @@
     <!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
 
     <!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
-    <string name="title_app_streaming">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to stream apps and system features between your <xliff:g id="device_type" example="phone">%2$s</xliff:g> and &lt;strong&gt;<xliff:g id="device_name" example="Chromebook">%3$s</xliff:g>&lt;/strong&gt;?</string>
+    <string name="title_app_streaming">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to stream your <xliff:g id="device_type" example="phone">%2$s</xliff:g>\u2019s apps and system features to &lt;strong&gt;<xliff:g id="device_name" example="Chromebook">%3$s</xliff:g>&lt;/strong&gt;?</string>
 
     <!-- Summary for associating an application with a companion device of APP_STREAMING profile [CHAR LIMIT=NONE] -->
-    <string name="summary_app_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on your <xliff:g id="device_type" example="phone">%2$s</xliff:g>, including audio, photos, payment info, passwords and messages.&lt;br/>&lt;br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
+    <string name="summary_app_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on your <xliff:g id="device_type" example="phone">%2$s</xliff:g>, including audio, photos, payment info, passwords, and messages.&lt;br/>&lt;br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
 
     <!-- Description of the helper dialog for APP_STREAMING profile. [CHAR LIMIT=NONE] -->
-    <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> is requesting permission on behalf of <xliff:g id="device_name" example="Chromebook">%2$s</xliff:g> to stream apps and system features between your devices</string>
+    <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> is requesting permission on behalf of <xliff:g id="device_name" example="Chromebook">%2$s</xliff:g> to stream apps and system features from your <xliff:g id="device_type" example="phone">%3$s</xliff:g></string>
 
     <!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= -->
 
@@ -83,7 +83,7 @@
     <string name="title_nearby_device_streaming">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to stream your <xliff:g id="device_type" example="phone">%2$s</xliff:g>\u2019s apps to &lt;strong&gt;<xliff:g id="device_name" example="Chromebook">%3$s</xliff:g>&lt;/strong&gt;?</string>
 
     <!-- Summary for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile [CHAR LIMIT=NONE] -->
-    <string name="summary_nearby_device_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g>, including audio, photos, payment info, passwords and messages.&lt;br/>&lt;br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
+    <string name="summary_nearby_device_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g>, including audio, photos, payment info, passwords, and messages.&lt;br/>&lt;br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
 
     <!-- Description of the helper dialog for NEARBY_DEVICE_STREAMING profile. [CHAR LIMIT=NONE] -->
     <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> is requesting permission on behalf of <xliff:g id="device_name" example="Chromebook">%2$s</xliff:g> to stream apps from your <xliff:g id="device_type" example="phone">%3$s</xliff:g></string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 8b9ec38..739c7d6 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1651,6 +1651,9 @@
     <!-- Name of the usb audio device mic. [CHAR LIMIT=50] -->
     <string name="media_transfer_usb_device_mic_name">USB microphone</string>
 
+    <!-- Name of the bluetooth audio device mic. [CHAR LIMIT=50] -->
+    <string name="media_transfer_bt_device_mic_name">BT microphone</string>
+
     <!-- Label for Wifi hotspot switch on. Toggles hotspot on [CHAR LIMIT=30] -->
     <string name="wifi_hotspot_switch_on_text">On</string>
     <!-- Label for Wifi hotspot switch off. Toggles hotspot off [CHAR LIMIT=30] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 9d56c77..744e97e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -30,9 +30,10 @@
 import android.icu.text.NumberFormat;
 import android.location.LocationManager;
 import android.media.AudioManager;
+import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
 import android.net.TetheringManager;
-import android.net.vcn.VcnTransportInfo;
+import android.net.vcn.VcnUtils;
 import android.net.wifi.WifiInfo;
 import android.os.BatteryManager;
 import android.os.Build;
@@ -737,14 +738,9 @@
      * @param networkCapabilities NetworkCapabilities of the network.
      */
     @Nullable
-    public static WifiInfo tryGetWifiInfoForVcn(NetworkCapabilities networkCapabilities) {
-        if (networkCapabilities.getTransportInfo() == null
-                || !(networkCapabilities.getTransportInfo() instanceof VcnTransportInfo)) {
-            return null;
-        }
-        VcnTransportInfo vcnTransportInfo =
-                (VcnTransportInfo) networkCapabilities.getTransportInfo();
-        return vcnTransportInfo.getWifiInfo();
+    public static WifiInfo tryGetWifiInfoForVcn(
+            ConnectivityManager connectivityMgr, NetworkCapabilities networkCapabilities) {
+        return VcnUtils.getWifiInfoFromVcnCaps(connectivityMgr, networkCapabilities);
     }
 
     /** Whether there is any incompatible chargers in the current UsbPort? */
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
index e44a134..1d17b00 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
@@ -15,6 +15,7 @@
  */
 package com.android.settingslib.media;
 
+import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
 import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
 import static android.media.AudioDeviceInfo.TYPE_USB_ACCESSORY;
 import static android.media.AudioDeviceInfo.TYPE_USB_DEVICE;
@@ -90,7 +91,8 @@
                             TYPE_WIRED_HEADSET,
                             TYPE_USB_DEVICE,
                             TYPE_USB_HEADSET,
-                            TYPE_USB_ACCESSORY ->
+                            TYPE_USB_ACCESSORY,
+                            TYPE_BLUETOOTH_SCO ->
                     true;
             default -> false;
         };
@@ -103,6 +105,8 @@
                     R.string.media_transfer_wired_device_mic_name);
             case TYPE_USB_DEVICE, TYPE_USB_HEADSET, TYPE_USB_ACCESSORY -> mContext.getString(
                     R.string.media_transfer_usb_device_mic_name);
+            case TYPE_BLUETOOTH_SCO -> mContext.getString(
+                    R.string.media_transfer_bt_device_mic_name);
             default -> mContext.getString(R.string.media_transfer_this_device_name_desktop);
         };
         return name.toString();
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 015356e..cea3d17 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -30,6 +30,7 @@
 import android.net.ScoredNetwork;
 import android.net.TransportInfo;
 import android.net.vcn.VcnTransportInfo;
+import android.net.vcn.VcnUtils;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkScoreCache;
@@ -394,10 +395,7 @@
 
         TransportInfo transportInfo = networkCapabilities.getTransportInfo();
         if (transportInfo instanceof VcnTransportInfo) {
-            // This VcnTransportInfo logic is copied from
-            // [com.android.settingslib.Utils.tryGetWifiInfoForVcn]. It's copied instead of
-            // re-used because it makes the logic here clearer.
-            return ((VcnTransportInfo) transportInfo).getWifiInfo();
+            return VcnUtils.getWifiInfoFromVcnCaps(mConnectivityManager, networkCapabilities);
         } else if (transportInfo instanceof WifiInfo) {
             return (WifiInfo) transportInfo;
         } else {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
index 2f0aa1c..30e4637 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
@@ -37,6 +37,7 @@
     private final int BUILTIN_MIC_ID = 1;
     private final int WIRED_HEADSET_ID = 2;
     private final int USB_HEADSET_ID = 3;
+    private final int BT_HEADSET_ID = 4;
     private final int MAX_VOLUME = 1;
     private final int CURRENT_VOLUME = 0;
     private final boolean IS_VOLUME_FIXED = true;
@@ -108,4 +109,19 @@
         assertThat(usbMediaDevice.getName())
                 .isEqualTo(mContext.getString(R.string.media_transfer_usb_device_mic_name));
     }
+
+    @Test
+    public void getName_returnCorrectName_btHeadset() {
+        InputMediaDevice btMediaDevice =
+                InputMediaDevice.create(
+                        mContext,
+                        String.valueOf(BT_HEADSET_ID),
+                        AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
+                        MAX_VOLUME,
+                        CURRENT_VOLUME,
+                        IS_VOLUME_FIXED);
+        assertThat(btMediaDevice).isNotNull();
+        assertThat(btMediaDevice.getName())
+                .isEqualTo(mContext.getString(R.string.media_transfer_bt_device_mic_name));
+    }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 5e31da4..4dc8424 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -168,10 +168,6 @@
         Settings.Secure.SHOW_NOTIFICATION_SNOOZE,
         Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
         Settings.Secure.ZEN_DURATION,
-        Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION,
-        Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION,
-        Settings.Secure.ZEN_SETTINGS_UPDATED,
-        Settings.Secure.ZEN_SETTINGS_SUGGESTION_VIEWED,
         Settings.Secure.CHARGING_SOUNDS_ENABLED,
         Settings.Secure.CHARGING_VIBRATION_ENABLED,
         Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 0773bd7..8f58e8c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -434,7 +434,7 @@
         VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_STATUS,
                 new InclusiveIntegerRangeValidator(
                         Global.Wearable.PHONE_SWITCHING_STATUS_NOT_STARTED,
-                        Global.Wearable.PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION_SUCCESS));
+                        Global.Wearable.PHONE_SWITCHING_STATUS_ACCOUNTS_MATCHED));
         VALIDATORS.put(Global.Wearable.REDUCE_MOTION, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.RTL_SWIPE_TO_DISMISS_ENABLED_DEV, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index b3f7374..688676d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -247,10 +247,6 @@
         VALIDATORS.put(Secure.SHOW_NOTIFICATION_SNOOZE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.NOTIFICATION_HISTORY_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ZEN_DURATION, ANY_INTEGER_VALIDATOR);
-        VALIDATORS.put(Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, BOOLEAN_VALIDATOR);
-        VALIDATORS.put(Secure.SHOW_ZEN_SETTINGS_SUGGESTION, BOOLEAN_VALIDATOR);
-        VALIDATORS.put(Secure.ZEN_SETTINGS_UPDATED, BOOLEAN_VALIDATOR);
-        VALIDATORS.put(Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.CHARGING_VIBRATION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 3c24f5c..2034f36 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2734,18 +2734,6 @@
                 Settings.Secure.ZEN_DURATION,
                 SecureSettingsProto.Zen.DURATION);
         dumpSetting(s, p,
-                Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION,
-                SecureSettingsProto.Zen.SHOW_ZEN_UPGRADE_NOTIFICATION);
-        dumpSetting(s, p,
-                Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION,
-                SecureSettingsProto.Zen.SHOW_ZEN_SETTINGS_SUGGESTION);
-        dumpSetting(s, p,
-                Settings.Secure.ZEN_SETTINGS_UPDATED,
-                SecureSettingsProto.Zen.SETTINGS_UPDATED);
-        dumpSetting(s, p,
-                Settings.Secure.ZEN_SETTINGS_SUGGESTION_VIEWED,
-                SecureSettingsProto.Zen.SETTINGS_SUGGESTION_VIEWED);
-        dumpSetting(s, p,
                 Settings.Secure.CHARGE_OPTIMIZATION_MODE,
                 SecureSettingsProto.CHARGE_OPTIMIZATION_MODE);
         p.end(zenToken);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 749ad0a..a8af43f5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -4771,9 +4771,9 @@
                 }
 
                 if (currentVersion == 169) {
-                    // Version 169: Set the default value for Secure Settings ZEN_DURATION,
-                    // SHOW_ZEN_SETTINGS_SUGGESTION, ZEN_SETTINGS_UPDATE and
-                    // ZEN_SETTINGS_SUGGESTION_VIEWED
+                    // Version 169: Set the default value for Secure Settings ZEN_DURATION.
+                    // Also used to update SHOW_ZEN_SETTINGS_SUGGESTION, ZEN_SETTINGS_UPDATE and
+                    // ZEN_SETTINGS_SUGGESTION_VIEWED, but those properties are gone now.
 
                     final SettingsState globalSettings = getGlobalSettingsLocked();
                     final Setting globalZenDuration = globalSettings.getSettingLocked(
@@ -4801,33 +4801,6 @@
                                 SettingsState.SYSTEM_PACKAGE_NAME);
                     }
 
-                    // SHOW_ZEN_SETTINGS_SUGGESTION
-                    final Setting currentShowZenSettingSuggestion = secureSettings.getSettingLocked(
-                            Secure.SHOW_ZEN_SETTINGS_SUGGESTION);
-                    if (currentShowZenSettingSuggestion.isNull()) {
-                        secureSettings.insertSettingOverrideableByRestoreLocked(
-                                Secure.SHOW_ZEN_SETTINGS_SUGGESTION, "1",
-                                null, true, SettingsState.SYSTEM_PACKAGE_NAME);
-                    }
-
-                    // ZEN_SETTINGS_UPDATED
-                    final Setting currentUpdatedSetting = secureSettings.getSettingLocked(
-                            Secure.ZEN_SETTINGS_UPDATED);
-                    if (currentUpdatedSetting.isNull()) {
-                        secureSettings.insertSettingOverrideableByRestoreLocked(
-                                Secure.ZEN_SETTINGS_UPDATED, "0",
-                                null, true, SettingsState.SYSTEM_PACKAGE_NAME);
-                    }
-
-                    // ZEN_SETTINGS_SUGGESTION_VIEWED
-                    final Setting currentSettingSuggestionViewed = secureSettings.getSettingLocked(
-                            Secure.ZEN_SETTINGS_SUGGESTION_VIEWED);
-                    if (currentSettingSuggestionViewed.isNull()) {
-                        secureSettings.insertSettingOverrideableByRestoreLocked(
-                                Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, "0",
-                                null, true, SettingsState.SYSTEM_PACKAGE_NAME);
-                    }
-
                     currentVersion = 170;
                 }
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 3c634f0..011ffbc 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -610,7 +610,7 @@
                                 flag.getPackageName(),
                                 flag.getFlagName(),
                                 flag.getServerFlagValue(),
-                                false);
+                                StorageRequestMessage.SERVER_ON_REBOOT);
                     }
 
                     if (flag.getHasLocalOverride()) {
@@ -619,7 +619,7 @@
                                 flag.getPackageName(),
                                 flag.getFlagName(),
                                 flag.getLocalFlagValue(),
-                                true);
+                                StorageRequestMessage.LOCAL_ON_REBOOT);
                     }
                 }
 
diff --git a/packages/SettingsProvider/test/AndroidTest.xml b/packages/SettingsProvider/test/AndroidTest.xml
index dccc2d3..541a294 100644
--- a/packages/SettingsProvider/test/AndroidTest.xml
+++ b/packages/SettingsProvider/test/AndroidTest.xml
@@ -32,7 +32,7 @@
         <option name="package" value="com.android.providers.setting.test" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false"/>
-        <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
-        <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
+        <option name="exclude-annotation" value="com.android.bedstead.enterprise.annotations.RequireRunOnWorkProfile" />
+        <option name="exclude-annotation" value="com.android.bedstead.multiuser.annotations.RequireRunOnSecondaryUser" />
     </test>
 </configuration>
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index b491b5a..d39b564 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -223,7 +223,6 @@
                     Settings.Global.ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE,
                     Settings.Global.ENABLE_DISKSTATS_LOGGING,
                     Settings.Global.ENABLE_EPHEMERAL_FEATURE,
-                    Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED,
                     Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED,
                     Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
                     Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
index e4898da..e86e727 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
@@ -31,10 +31,10 @@
 import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile;
 import com.android.bedstead.harrier.BedsteadJUnit4;
 import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
 import com.android.bedstead.harrier.annotations.RequireFeature;
 import com.android.bedstead.harrier.annotations.RequireRunOnInitialUser;
-import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.multiuser.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.multiuser.annotations.RequireRunOnPrimaryUser;
 import com.android.bedstead.nene.TestApis;
 import com.android.bedstead.nene.users.UserReference;
 import com.android.bedstead.nene.users.UserType;
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index ec7012e..c6238e8 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1081,6 +1081,16 @@
 }
 
 flag {
+    name: "dream_overlay_updated_font"
+    namespace: "systemui"
+    description: "Flag to enable updated font settings for dream overlay"
+    bug: "349656117"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
   name: "app_clips_backlinks"
   namespace: "systemui"
   description: "Enables Backlinks improvement feature in App Clips"
@@ -1391,10 +1401,13 @@
 }
 
 flag {
-   name: "compose_haptic_sliders"
+   name: "haptics_for_compose_sliders"
    namespace: "systemui"
    description: "Adding haptic component infrastructure to sliders in Compose."
    bug: "341968766"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
 }
 
 flag {
@@ -1460,3 +1473,13 @@
        purpose: PURPOSE_BUGFIX
    }
 }
+
+flag {
+   name: "check_lockscreen_gone_transition"
+   namespace: "systemui"
+   description: "Run notification pipeline when the lockscreen is not in gone transition for avoiding janky frames during unlocking animation"
+   bug: "358301118"
+   metadata {
+       purpose: PURPOSE_BUGFIX
+   }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
index f5d01d7..907c39d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
@@ -944,9 +944,26 @@
                 }
 
                 override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
-                    startController.onTransitionAnimationEnd(isExpandingFullyAbove)
-                    endController.onTransitionAnimationEnd(isExpandingFullyAbove)
-                    onLaunchAnimationEnd()
+                    // onLaunchAnimationEnd is called by an Animator at the end of the animation,
+                    // on a Choreographer animation tick. The following calls will move the animated
+                    // content from the dialog overlay back to its original position, and this
+                    // change must be reflected in the next frame given that we then sync the next
+                    // frame of both the content and dialog ViewRoots. However, in case that content
+                    // is rendered by Compose, whose compositions are also scheduled on a
+                    // Choreographer frame, any state change made *right now* won't be reflected in
+                    // the next frame given that a Choreographer frame can't schedule another and
+                    // have it happen in the same frame. So we post the forwarded calls to
+                    // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring
+                    // that the move of the content back to its original window will be reflected in
+                    // the next frame right after [onLaunchAnimationEnd] is called.
+                    //
+                    // TODO(b/330672236): Move this to TransitionAnimator.
+                    dialog.context.mainExecutor.execute {
+                        startController.onTransitionAnimationEnd(isExpandingFullyAbove)
+                        endController.onTransitionAnimationEnd(isExpandingFullyAbove)
+
+                        onLaunchAnimationEnd()
+                    }
                 }
 
                 override fun onTransitionAnimationProgress(
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index 859fc4e0..fc4cf1d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -379,26 +379,13 @@
                         Log.d(TAG, "Animation ended")
                     }
 
-                    // onAnimationEnd is called at the end of the animation, on a Choreographer
-                    // animation tick. During dialog launches, the following calls will move the
-                    // animated content from the dialog overlay back to its original position, and
-                    // this change must be reflected in the next frame given that we then sync the
-                    // next frame of both the content and dialog ViewRoots. During SysUI activity
-                    // launches, we will instantly collapse the shade at the end of the transition.
-                    // However, if those are rendered by Compose, whose compositions are also
-                    // scheduled on a Choreographer frame, any state change made *right now* won't
-                    // be reflected in the next frame given that a Choreographer frame can't
-                    // schedule another and have it happen in the same frame. So we post the
-                    // forwarded calls to [Controller.onLaunchAnimationEnd] in the main executor,
-                    // leaving this Choreographer frame, ensuring that any state change applied by
-                    // onTransitionAnimationEnd() will be reflected in the same frame.
-                    mainExecutor.execute {
-                        controller.onTransitionAnimationEnd(isExpandingFullyAbove)
-                        transitionContainerOverlay.remove(windowBackgroundLayer)
+                    // TODO(b/330672236): Post this to the main thread instead so that it does not
+                    // flicker with Flexiglass enabled.
+                    controller.onTransitionAnimationEnd(isExpandingFullyAbove)
+                    transitionContainerOverlay.remove(windowBackgroundLayer)
 
-                        if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
-                            openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
-                        }
+                    if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
+                        openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
                     }
                 }
             }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt
new file mode 100644
index 0000000..3f2f84b
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import kotlin.math.roundToInt
+
+/** A component that can bounce in one dimension, for instance when it is tapped. */
+interface Bounceable {
+    val bounce: Dp
+}
+
+/**
+ * Bounce a composable in the given [orientation] when this [bounceable], the [previousBounceable]
+ * or [nextBounceable] is bouncing.
+ *
+ * Important: This modifier should be used on composables that have a fixed size in [orientation],
+ * i.e. they should be placed *after* modifiers like Modifier.fillMaxWidth() or Modifier.height().
+ *
+ * @param bounceable the [Bounceable] associated to the current composable that will make this
+ *   composable size grow when bouncing.
+ * @param previousBounceable the [Bounceable] associated to the previous composable in [orientation]
+ *   that will make this composable shrink when bouncing.
+ * @param nextBounceable the [Bounceable] associated to the next composable in [orientation] that
+ *   will make this composable shrink when bouncing.
+ * @param orientation the orientation in which this bounceable should grow/shrink.
+ * @param bounceEnd whether this bounceable should bounce on the end (right in LTR layouts, left in
+ *   RTL layouts) side. This can be used for grids for which the last item does not align perfectly
+ *   with the end of the grid.
+ */
+fun Modifier.bounceable(
+    bounceable: Bounceable,
+    previousBounceable: Bounceable?,
+    nextBounceable: Bounceable?,
+    orientation: Orientation,
+    bounceEnd: Boolean = nextBounceable != null,
+): Modifier {
+    return layout { measurable, constraints ->
+        // The constraints in the orientation should be fixed, otherwise there is no way to know
+        // what the size of our child node will be without this animation code.
+        checkFixedSize(constraints, orientation)
+
+        var sizePrevious = 0f
+        var sizeNext = 0f
+
+        if (previousBounceable != null) {
+            sizePrevious += bounceable.bounce.toPx() - previousBounceable.bounce.toPx()
+        }
+
+        if (nextBounceable != null) {
+            sizeNext += bounceable.bounce.toPx() - nextBounceable.bounce.toPx()
+        } else if (bounceEnd) {
+            sizeNext += bounceable.bounce.toPx()
+        }
+
+        when (orientation) {
+            Orientation.Horizontal -> {
+                val idleWidth = constraints.maxWidth
+                val animatedWidth = (idleWidth + sizePrevious + sizeNext).roundToInt()
+                val animatedConstraints =
+                    constraints.copy(minWidth = animatedWidth, maxWidth = animatedWidth)
+
+                val placeable = measurable.measure(animatedConstraints)
+
+                // Important: we still place the element using the idle size coming from the
+                // constraints, otherwise the parent will automatically center this node given the
+                // size that it expects us to be. This allows us to then place the element where we
+                // want it to be.
+                layout(idleWidth, placeable.height) {
+                    placeable.placeRelative(-sizePrevious.roundToInt(), 0)
+                }
+            }
+            Orientation.Vertical -> {
+                val idleHeight = constraints.maxHeight
+                val animatedHeight = (idleHeight + sizePrevious + sizeNext).roundToInt()
+                val animatedConstraints =
+                    constraints.copy(minHeight = animatedHeight, maxHeight = animatedHeight)
+
+                val placeable = measurable.measure(animatedConstraints)
+                layout(placeable.width, idleHeight) {
+                    placeable.placeRelative(0, -sizePrevious.roundToInt())
+                }
+            }
+        }
+    }
+}
+
+private fun checkFixedSize(constraints: Constraints, orientation: Orientation) {
+    when (orientation) {
+        Orientation.Horizontal -> {
+            check(constraints.hasFixedWidth) {
+                "Modifier.bounceable() should receive a fixed width from its parent. Make sure " +
+                    "that it is used *after* a fixed-width Modifier in the horizontal axis (like" +
+                    " Modifier.fillMaxWidth() or Modifier.width())."
+            }
+        }
+        Orientation.Vertical -> {
+            check(constraints.hasFixedHeight) {
+                "Modifier.bounceable() should receive a fixed height from its parent. Make sure " +
+                    "that it is used *after* a fixed-height Modifier in the vertical axis (like" +
+                    " Modifier.fillMaxHeight() or Modifier.height())."
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/BounceableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/BounceableTest.kt
new file mode 100644
index 0000000..335e9f8
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/BounceableTest.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.times
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class BounceableTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun bounceable_horizontal() {
+        var bounceables by mutableStateOf(List(4) { bounceable(0.dp) })
+
+        rule.setContent {
+            Row(Modifier.size(100.dp, 50.dp)) {
+                repeat(bounceables.size) { i ->
+                    Box(
+                        Modifier.weight(1f)
+                            .fillMaxHeight()
+                            .bounceable(bounceables, i, orientation = Orientation.Horizontal)
+                    )
+                }
+            }
+        }
+
+        // All bounceables have a width of (100dp / bounceables.size) = 25dp and height of 50dp.
+        repeat(bounceables.size) { i ->
+            rule
+                .onNodeWithTag(bounceableTag(i))
+                .assertWidthIsEqualTo(25.dp)
+                .assertHeightIsEqualTo(50.dp)
+                .assertPositionInRootIsEqualTo(i * 25.dp, 0.dp)
+        }
+
+        // If all bounceables have the same bounce, it's the same as if they didn't have any.
+        bounceables = List(4) { bounceable(10.dp) }
+        repeat(bounceables.size) { i ->
+            rule
+                .onNodeWithTag(bounceableTag(i))
+                .assertWidthIsEqualTo(25.dp)
+                .assertHeightIsEqualTo(50.dp)
+                .assertPositionInRootIsEqualTo(i * 25.dp, 0.dp)
+        }
+
+        // Bounce the first and third one.
+        bounceables =
+            listOf(
+                bounceable(bounce = 5.dp),
+                bounceable(bounce = 0.dp),
+                bounceable(bounce = 10.dp),
+                bounceable(bounce = 0.dp),
+            )
+
+        // First one has a width of 25dp + 5dp, located in (0, 0).
+        rule
+            .onNodeWithTag(bounceableTag(0))
+            .assertWidthIsEqualTo(30.dp)
+            .assertHeightIsEqualTo(50.dp)
+            .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+
+        // Second one has a width of 25dp - 5dp - 10dp, located in (30, 0).
+        rule
+            .onNodeWithTag(bounceableTag(1))
+            .assertWidthIsEqualTo(10.dp)
+            .assertHeightIsEqualTo(50.dp)
+            .assertPositionInRootIsEqualTo(30.dp, 0.dp)
+
+        // Third one has a width of 25 + 2 * 10dp, located in (40, 0).
+        rule
+            .onNodeWithTag(bounceableTag(2))
+            .assertWidthIsEqualTo(45.dp)
+            .assertHeightIsEqualTo(50.dp)
+            .assertPositionInRootIsEqualTo(40.dp, 0.dp)
+
+        // First one has a width of 25dp - 10dp, located in (85, 0).
+        rule
+            .onNodeWithTag(bounceableTag(3))
+            .assertWidthIsEqualTo(15.dp)
+            .assertHeightIsEqualTo(50.dp)
+            .assertPositionInRootIsEqualTo(85.dp, 0.dp)
+    }
+
+    @Test
+    fun bounceable_vertical() {
+        var bounceables by mutableStateOf(List(4) { bounceable(0.dp) })
+
+        rule.setContent {
+            Column(Modifier.size(50.dp, 100.dp)) {
+                repeat(bounceables.size) { i ->
+                    Box(
+                        Modifier.weight(1f)
+                            .fillMaxWidth()
+                            .bounceable(bounceables, i, Orientation.Vertical)
+                    )
+                }
+            }
+        }
+
+        // All bounceables have a height of (100dp / bounceables.size) = 25dp and width of 50dp.
+        repeat(bounceables.size) { i ->
+            rule
+                .onNodeWithTag(bounceableTag(i))
+                .assertWidthIsEqualTo(50.dp)
+                .assertHeightIsEqualTo(25.dp)
+                .assertPositionInRootIsEqualTo(0.dp, i * 25.dp)
+        }
+
+        // If all bounceables have the same bounce, it's the same as if they didn't have any.
+        bounceables = List(4) { bounceable(10.dp) }
+        repeat(bounceables.size) { i ->
+            rule
+                .onNodeWithTag(bounceableTag(i))
+                .assertWidthIsEqualTo(50.dp)
+                .assertHeightIsEqualTo(25.dp)
+                .assertPositionInRootIsEqualTo(0.dp, i * 25.dp)
+        }
+
+        // Bounce the first and third one.
+        bounceables =
+            listOf(
+                bounceable(bounce = 5.dp),
+                bounceable(bounce = 0.dp),
+                bounceable(bounce = 10.dp),
+                bounceable(bounce = 0.dp),
+            )
+
+        // First one has a height of 25dp + 5dp, located in (0, 0).
+        rule
+            .onNodeWithTag(bounceableTag(0))
+            .assertWidthIsEqualTo(50.dp)
+            .assertHeightIsEqualTo(30.dp)
+            .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+
+        // Second one has a height of 25dp - 5dp - 10dp, located in (0, 30).
+        rule
+            .onNodeWithTag(bounceableTag(1))
+            .assertWidthIsEqualTo(50.dp)
+            .assertHeightIsEqualTo(10.dp)
+            .assertPositionInRootIsEqualTo(0.dp, 30.dp)
+
+        // Third one has a height of 25 + 2 * 10dp, located in (0, 40).
+        rule
+            .onNodeWithTag(bounceableTag(2))
+            .assertWidthIsEqualTo(50.dp)
+            .assertHeightIsEqualTo(45.dp)
+            .assertPositionInRootIsEqualTo(0.dp, 40.dp)
+
+        // First one has a height of 25dp - 10dp, located in (0, 85).
+        rule
+            .onNodeWithTag(bounceableTag(3))
+            .assertWidthIsEqualTo(50.dp)
+            .assertHeightIsEqualTo(15.dp)
+            .assertPositionInRootIsEqualTo(0.dp, 85.dp)
+    }
+
+    private fun bounceable(bounce: Dp): Bounceable {
+        return object : Bounceable {
+            override val bounce: Dp = bounce
+        }
+    }
+
+    private fun Modifier.bounceable(
+        bounceables: List<Bounceable>,
+        i: Int,
+        orientation: Orientation,
+    ): Modifier {
+        val previous = if (i > 0) bounceables[i - 1] else null
+        val next = if (i < bounceables.lastIndex) bounceables[i + 1] else null
+        return this.bounceable(bounceables[i], previous, next, orientation)
+            .testTag(bounceableTag(i))
+    }
+
+    private fun bounceableTag(i: Int) = "bounceable$i"
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/DreamSceneModule.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
copy to packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/DreamSceneModule.kt
index 15ed1b3..2f82369 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/DreamSceneModule.kt
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.data.repository
+package com.android.systemui.scene
 
-import android.view.windowManagerService
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.dream.ui.composable.DreamScene
+import com.android.systemui.scene.ui.composable.Scene
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
 
-val Kosmos.systemGestureExclusionRepository by Fixture {
-    SystemGestureExclusionRepository(windowManager = windowManagerService)
+@Module
+interface DreamSceneModule {
+    @Binds @IntoSet fun dreamScene(scene: DreamScene): Scene
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt
new file mode 100644
index 0000000..fda46b8
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt
@@ -0,0 +1,242 @@
+/*
+ * 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.communal.ui.compose
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.gestures.AnchoredDraggableState
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.anchoredDraggable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastIsFinite
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.communal.ui.viewmodel.DragHandle
+import com.android.systemui.communal.ui.viewmodel.ResizeInfo
+import com.android.systemui.communal.ui.viewmodel.ResizeableItemFrameViewModel
+import com.android.systemui.lifecycle.rememberViewModel
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+
+@Composable
+private fun UpdateGridLayoutInfo(
+    viewModel: ResizeableItemFrameViewModel,
+    index: Int,
+    gridState: LazyGridState,
+    minItemSpan: Int,
+    gridContentPadding: PaddingValues,
+    verticalArrangement: Arrangement.Vertical,
+) {
+    val density = LocalDensity.current
+    LaunchedEffect(
+        density,
+        viewModel,
+        index,
+        gridState,
+        minItemSpan,
+        gridContentPadding,
+        verticalArrangement,
+    ) {
+        val verticalItemSpacingPx = with(density) { verticalArrangement.spacing.toPx() }
+        val verticalContentPaddingPx =
+            with(density) {
+                (gridContentPadding.calculateTopPadding() +
+                        gridContentPadding.calculateBottomPadding())
+                    .toPx()
+            }
+
+        combine(
+                snapshotFlow { gridState.layoutInfo.maxSpan },
+                snapshotFlow { gridState.layoutInfo.viewportSize.height },
+                snapshotFlow {
+                        gridState.layoutInfo.visibleItemsInfo.firstOrNull { it.index == index }
+                    }
+                    .filterNotNull(),
+                ::Triple,
+            )
+            .collectLatest { (maxItemSpan, viewportHeightPx, itemInfo) ->
+                viewModel.setGridLayoutInfo(
+                    verticalItemSpacingPx,
+                    verticalContentPaddingPx,
+                    viewportHeightPx,
+                    maxItemSpan,
+                    minItemSpan,
+                    itemInfo.row,
+                    itemInfo.span,
+                )
+            }
+    }
+}
+
+@Composable
+private fun BoxScope.DragHandle(
+    handle: DragHandle,
+    dragState: AnchoredDraggableState<Int>,
+    outlinePadding: Dp,
+    brush: Brush,
+    alpha: () -> Float,
+    modifier: Modifier = Modifier,
+) {
+    val directionalModifier = if (handle == DragHandle.TOP) -1 else 1
+    val alignment = if (handle == DragHandle.TOP) Alignment.TopCenter else Alignment.BottomCenter
+    Box(
+        modifier
+            .align(alignment)
+            .graphicsLayer {
+                translationY =
+                    directionalModifier * (size.height / 2 + outlinePadding.toPx()) +
+                        (dragState.offset.takeIf { it.fastIsFinite() } ?: 0f)
+            }
+            .anchoredDraggable(dragState, Orientation.Vertical)
+    ) {
+        Canvas(modifier = Modifier.fillMaxSize()) {
+            if (dragState.anchors.size > 1) {
+                drawCircle(
+                    brush = brush,
+                    radius = outlinePadding.toPx(),
+                    center = Offset(size.width / 2, size.height / 2),
+                    alpha = alpha(),
+                )
+            }
+        }
+    }
+}
+
+/**
+ * Draws a frame around the content with drag handles on the top and bottom of the content.
+ *
+ * @param index The index of this item in the [LazyGridState].
+ * @param gridState The [LazyGridState] for the grid containing this item.
+ * @param minItemSpan The minimum span that an item may occupy. Items are resized in multiples of
+ *   this span.
+ * @param gridContentPadding The content padding used for the grid, needed for determining offsets.
+ * @param verticalArrangement The vertical arrangement of the grid items.
+ * @param modifier Optional modifier to apply to the frame.
+ * @param enabled Whether resizing is enabled.
+ * @param outlinePadding The padding to apply around the entire frame, in [Dp]
+ * @param outlineColor Optional color to make the outline around the content.
+ * @param cornerRadius Optional radius to give to the outline around the content.
+ * @param strokeWidth Optional stroke width to draw the outline with.
+ * @param alpha Optional function to provide an alpha value for the outline. Can be used to fade the
+ *   outline in and out. This is wrapped in a function for performance, as the value is only
+ *   accessed during the draw phase.
+ * @param onResize Optional callback which gets executed when the item is resized to a new span.
+ * @param content The content to draw inside the frame.
+ */
+@Composable
+fun ResizableItemFrame(
+    index: Int,
+    gridState: LazyGridState,
+    minItemSpan: Int,
+    gridContentPadding: PaddingValues,
+    verticalArrangement: Arrangement.Vertical,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    outlinePadding: Dp = 8.dp,
+    outlineColor: Color = LocalAndroidColorScheme.current.primary,
+    cornerRadius: Dp = 37.dp,
+    strokeWidth: Dp = 3.dp,
+    alpha: () -> Float = { 1f },
+    onResize: (info: ResizeInfo) -> Unit = {},
+    content: @Composable () -> Unit,
+) {
+    val brush = SolidColor(outlineColor)
+    val viewModel =
+        rememberViewModel(traceName = "ResizeableItemFrame.viewModel") {
+            ResizeableItemFrameViewModel()
+        }
+
+    val dragHandleHeight = verticalArrangement.spacing - outlinePadding * 2
+
+    // Draw content surrounded by drag handles at top and bottom. Allow drag handles
+    // to overlap content.
+    Box(modifier) {
+        content()
+
+        if (enabled) {
+            DragHandle(
+                handle = DragHandle.TOP,
+                dragState = viewModel.topDragState,
+                outlinePadding = outlinePadding,
+                brush = brush,
+                alpha = alpha,
+                modifier = Modifier.fillMaxWidth().height(dragHandleHeight),
+            )
+
+            DragHandle(
+                handle = DragHandle.BOTTOM,
+                dragState = viewModel.bottomDragState,
+                outlinePadding = outlinePadding,
+                brush = brush,
+                alpha = alpha,
+                modifier = Modifier.fillMaxWidth().height(dragHandleHeight),
+            )
+
+            // Draw outline around the element.
+            Canvas(modifier = Modifier.matchParentSize()) {
+                val paddingPx = outlinePadding.toPx()
+                val topOffset = viewModel.topDragState.offset.takeIf { it.fastIsFinite() } ?: 0f
+                val bottomOffset =
+                    viewModel.bottomDragState.offset.takeIf { it.fastIsFinite() } ?: 0f
+                drawRoundRect(
+                    brush,
+                    alpha = alpha(),
+                    topLeft = Offset(-paddingPx, topOffset + -paddingPx),
+                    size =
+                        Size(
+                            width = size.width + paddingPx * 2,
+                            height = -topOffset + bottomOffset + size.height + paddingPx * 2,
+                        ),
+                    cornerRadius = CornerRadius(cornerRadius.toPx()),
+                    style = Stroke(width = strokeWidth.toPx()),
+                )
+            }
+
+            UpdateGridLayoutInfo(
+                viewModel,
+                index,
+                gridState,
+                minItemSpan,
+                gridContentPadding,
+                verticalArrangement,
+            )
+            LaunchedEffect(viewModel) { viewModel.resizeInfo.collectLatest(onResize) }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt
new file mode 100644
index 0000000..f4374c6
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.dream.ui.composable
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dreams.ui.viewmodel.DreamUserActionsViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.Scene
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** The dream scene shows when a dream activity is showing. */
+@SysUISingleton
+class DreamScene
+@Inject
+constructor(private val actionsViewModelFactory: DreamUserActionsViewModel.Factory) :
+    ExclusiveActivatable(), Scene {
+    override val key = Scenes.Dream
+
+    private val actionsViewModel: DreamUserActionsViewModel by lazy {
+        actionsViewModelFactory.create()
+    }
+
+    override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
+
+    override suspend fun onActivated(): Nothing {
+        actionsViewModel.activate()
+    }
+
+    @Composable
+    override fun SceneScope.Content(modifier: Modifier) {
+        Box(modifier = modifier.fillMaxSize()) {
+            // Render a sleep emoji to make the scene appear visible.
+            Text(
+                modifier = Modifier.padding(16.dp).align(Alignment.BottomStart),
+                text = "\uD83D\uDCA4",
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 2745f6e..4c6834c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -47,7 +47,6 @@
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
@@ -60,7 +59,6 @@
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.graphics.BlendMode
 import androidx.compose.ui.graphics.Color
@@ -70,7 +68,6 @@
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.boundsInWindow
 import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInWindow
 import androidx.compose.ui.platform.LocalConfiguration
@@ -82,6 +79,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.lerp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.LowestZIndexContentPicker
 import com.android.compose.animation.scene.NestedScrollBehavior
@@ -93,8 +91,9 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.session.ui.composable.SaveableSession
 import com.android.systemui.scene.session.ui.composable.rememberSession
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.shade.ui.composable.ShadeHeader
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
@@ -112,18 +111,16 @@
         val NotificationStackPlaceholder = ElementKey("NotificationStackPlaceholder")
         val HeadsUpNotificationPlaceholder =
             ElementKey("HeadsUpNotificationPlaceholder", contentPicker = LowestZIndexContentPicker)
-        val ShelfSpace = ElementKey("ShelfSpace")
         val NotificationStackCutoffGuideline = ElementKey("NotificationStackCutoffGuideline")
     }
-
-    // Expansion fraction thresholds (between 0-1f) at which the corresponding value should be
-    // at its maximum, given they are at their minimum value at expansion = 0f.
-    object TransitionThresholds {
-        const val EXPANSION_FOR_MAX_CORNER_RADIUS = 0.1f
-        const val EXPANSION_FOR_MAX_SCRIM_ALPHA = 0.3f
-    }
 }
 
+private val notificationsShadeContentKey: ContentKey
+    get() = if (DualShade.isEnabled) Overlays.NotificationsShade else Scenes.Shade
+
+private val quickSettingsShadeContentKey: ContentKey
+    get() = if (DualShade.isEnabled) Overlays.QuickSettingsShade else Scenes.QuickSettings
+
 /**
  * Adds the space where heads up notifications can appear in the scene. This should generally be the
  * entire size of the scene.
@@ -146,7 +143,7 @@
                     // This element is sometimes opted out of the shared element system, so there
                     // can be multiple instances of it during a transition. Thus we need to
                     // determine which instance should feed its bounds to NSSL to avoid providing
-                    // conflicting values
+                    // conflicting values.
                     val useBounds = useHunBounds()
                     if (useBounds) {
                         val positionInWindow = coordinates.positionInWindow()
@@ -157,8 +154,8 @@
                                 " bounds=$boundsInWindow"
                         }
                         // Note: boundsInWindow doesn't scroll off the screen, so use
-                        // positionInWindow
-                        // for top bound, which can scroll off screen while snoozing
+                        // positionInWindow for top bound, which can scroll off screen while
+                        // snoozing.
                         stackScrollView.setHeadsUpTop(positionInWindow.y)
                         stackScrollView.setHeadsUpBottom(boundsInWindow.bottom)
                     }
@@ -285,7 +282,8 @@
     shouldFillMaxSize: Boolean = true,
     shouldReserveSpaceForNavBar: Boolean = true,
     shouldIncludeHeadsUpSpace: Boolean = true,
-    shadeMode: ShadeMode,
+    shouldShowScrim: Boolean = true,
+    supportNestedScrolling: Boolean,
     onEmptySpaceClick: (() -> Unit)? = null,
     modifier: Modifier = Modifier,
 ) {
@@ -293,6 +291,7 @@
     val density = LocalDensity.current
     val screenCornerRadius = LocalScreenCornerRadius.current
     val scrimCornerRadius = dimensionResource(R.dimen.notification_scrim_corner_radius)
+    val scrimBackgroundColor = MaterialTheme.colorScheme.surface
     val scrollState =
         shadeSession.rememberSaveableSession(saver = ScrollState.Saver, key = null) {
             ScrollState(initial = 0)
@@ -427,8 +426,14 @@
                     // completes.
                     if (
                         scrimOffset.value < 0 &&
-                            layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Gone) ||
-                            layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)
+                            (layoutState.isTransitioning(
+                                from = notificationsShadeContentKey,
+                                to = Scenes.Gone,
+                            ) ||
+                                layoutState.isTransitioning(
+                                    from = notificationsShadeContentKey,
+                                    to = Scenes.Lockscreen,
+                                ))
                     ) {
                         IntOffset(x = 0, y = (scrimOffset.value * expansionFraction).roundToInt())
                     } else if (
@@ -498,7 +503,7 @@
                                 (expansionFraction / EXPANSION_FOR_MAX_SCRIM_ALPHA).coerceAtMost(1f)
                             } else 1f
                     }
-                    .background(MaterialTheme.colorScheme.surface)
+                    .thenIf(shouldShowScrim) { Modifier.background(scrimBackgroundColor) }
                     .thenIf(shouldFillMaxSize) { Modifier.fillMaxSize() }
                     .debugBackground(viewModel, DEBUG_BOX_COLOR)
         ) {
@@ -508,7 +513,7 @@
                             topBehavior = NestedScrollBehavior.EdgeWithPreview,
                             isExternalOverscrollGesture = { isCurrentGestureOverscroll.value },
                         )
-                        .thenIf(shadeMode == ShadeMode.Single) {
+                        .thenIf(supportNestedScrolling) {
                             Modifier.nestedScroll(scrimNestedScrollConnection)
                         }
                         .stackVerticalOverscroll(coroutineScope) { scrollState.canScrollForward }
@@ -550,38 +555,6 @@
 }
 
 /**
- * This may be added to the lockscreen to provide a space to the start of the lock icon where the
- * short shelf has room to flow vertically below the lock icon, but to its start, allowing more
- * notifications to fit in the stack itself. (see: b/213934746)
- *
- * NOTE: this is totally unused for now; it is here to clarify the future plan
- */
-@Composable
-fun SceneScope.NotificationShelfSpace(
-    viewModel: NotificationsPlaceholderViewModel,
-    modifier: Modifier = Modifier,
-) {
-    Text(
-        text = "Shelf Space",
-        modifier
-            .element(key = Notifications.Elements.ShelfSpace)
-            .fillMaxWidth()
-            .onPlaced { coordinates: LayoutCoordinates ->
-                debugLog(viewModel) {
-                    ("SHELF onPlaced:" +
-                        " size=${coordinates.size}" +
-                        " bounds=${coordinates.boundsInWindow()}")
-                }
-            }
-            .clip(RoundedCornerShape(24.dp))
-            .background(MaterialTheme.colorScheme.primaryContainer)
-            .padding(16.dp),
-        style = MaterialTheme.typography.titleLarge,
-        color = MaterialTheme.colorScheme.onPrimaryContainer,
-    )
-}
-
-/**
  * A 0 height horizontal spacer to be placed at the bottom-most position in the current scene, where
  * the notification contents (stack, footer, shelf) should be drawn.
  */
@@ -673,15 +646,19 @@
     }
 }
 
+private fun TransitionState.isOnLockscreen(): Boolean {
+    return currentScene == Scenes.Lockscreen && currentOverlays.isEmpty()
+}
+
 private fun shouldUseLockscreenStackBounds(state: TransitionState): Boolean {
-    return state is TransitionState.Idle && state.currentScene == Scenes.Lockscreen
+    return state is TransitionState.Idle && state.isOnLockscreen()
 }
 
 private fun shouldUseLockscreenHunBounds(state: TransitionState): Boolean {
     return when (state) {
-        is TransitionState.Idle -> state.currentScene == Scenes.Lockscreen
+        is TransitionState.Idle -> state.isOnLockscreen()
         is TransitionState.Transition ->
-            state.isTransitioning(from = Scenes.QuickSettings, to = Scenes.Lockscreen)
+            state.isTransitioning(from = quickSettingsShadeContentKey, to = Scenes.Lockscreen)
     }
 }
 
@@ -690,7 +667,7 @@
     shouldPunchHoleBehindScrim: Boolean,
 ): Boolean {
     return shouldPunchHoleBehindScrim ||
-        state.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)
+        state.isTransitioning(from = notificationsShadeContentKey, to = Scenes.Lockscreen)
 }
 
 private fun calculateCornerRadius(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
index a22becc..5b99670 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.scene.session.ui.composable.SaveableSession
 import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.ui.composable.Overlay
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
 import com.android.systemui.shade.ui.composable.OverlayShade
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -69,9 +68,7 @@
     }
 
     @Composable
-    override fun ContentScope.Content(
-        modifier: Modifier,
-    ) {
+    override fun ContentScope.Content(modifier: Modifier) {
         val viewModel =
             rememberViewModel("NotificationsShadeOverlay-viewModel") {
                 contentViewModelFactory.create()
@@ -81,10 +78,7 @@
                 viewModel.notificationsPlaceholderViewModelFactory.create()
             }
 
-        OverlayShade(
-            modifier = modifier,
-            onScrimClicked = viewModel::onScrimClicked,
-        ) {
+        OverlayShade(modifier = modifier, onScrimClicked = viewModel::onScrimClicked) {
             Column {
                 ExpandedShadeHeader(
                     viewModelFactory = viewModel.shadeHeaderViewModelFactory,
@@ -102,7 +96,8 @@
                     shouldPunchHoleBehindScrim = false,
                     shouldFillMaxSize = false,
                     shouldReserveSpaceForNavBar = false,
-                    shadeMode = ShadeMode.Dual,
+                    shouldShowScrim = false,
+                    supportNestedScrolling = false,
                     modifier = Modifier.fillMaxWidth(),
                 )
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 6304979..d75a776 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -100,7 +100,6 @@
 import com.android.systemui.scene.session.ui.composable.SaveableSession
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.Scene
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
 import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
 import com.android.systemui.shade.ui.composable.Shade
@@ -114,11 +113,9 @@
 import javax.inject.Inject
 import javax.inject.Named
 import kotlin.math.roundToInt
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 
 /** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
-@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class QuickSettingsScene
 @Inject
@@ -420,17 +417,26 @@
                     .navigationBarsPadding()
                     .padding(horizontal = shadeHorizontalPadding),
         )
+
+        // The minimum possible value for the top of the notification stack. In other words: how
+        // high is the notification stack allowed to get when the scene is at rest. It may still be
+        // translated farther upwards by a transition animation but, at rest, the top edge of its
+        // bounds must be limited to be at or below this value.
+        //
+        // A 1 pixel is added to compensate for any kind of rounding errors to make sure 100% that
+        // the notification stack is entirely "below" the entire screen.
+        val minNotificationStackTop = screenHeight.roundToInt() + 1
         NotificationScrollingStack(
             shadeSession = shadeSession,
             stackScrollView = notificationStackScrollView,
             viewModel = notificationsPlaceholderViewModel,
-            maxScrimTop = { screenHeight },
+            maxScrimTop = { minNotificationStackTop.toFloat() },
             shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
             shouldIncludeHeadsUpSpace = false,
-            shadeMode = ShadeMode.Single,
+            supportNestedScrolling = true,
             modifier =
                 Modifier.fillMaxWidth()
-                    .offset { IntOffset(x = 0, y = screenHeight.roundToInt()) }
+                    .offset { IntOffset(x = 0, y = minNotificationStackTop) }
                     .padding(horizontal = shadeHorizontalPadding),
         )
         NotificationStackCutoffGuideline(
@@ -439,7 +445,7 @@
             modifier =
                 Modifier.align(Alignment.BottomCenter)
                     .navigationBarsPadding()
-                    .offset { IntOffset(x = 0, y = screenHeight.roundToInt()) }
+                    .offset { IntOffset(x = 0, y = minNotificationStackTop) }
                     .padding(horizontal = shadeHorizontalPadding),
         )
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index ac58ab5..8728521 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.scene.ui.composable
 
+import androidx.compose.animation.core.spring
 import androidx.compose.foundation.gestures.Orientation
 import com.android.compose.animation.scene.ProgressConverter
 import com.android.compose.animation.scene.TransitionKey
@@ -12,11 +13,15 @@
 import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
 import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition
 import com.android.systemui.scene.ui.composable.transitions.bouncerToLockscreenPreview
+import com.android.systemui.scene.ui.composable.transitions.communalToBouncerTransition
+import com.android.systemui.scene.ui.composable.transitions.communalToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.dreamToGoneTransition
 import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition
 import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition
 import com.android.systemui.scene.ui.composable.transitions.goneToSplitShadeTransition
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToBouncerTransition
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToCommunalTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToDreamTransition
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTransition
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
@@ -44,10 +49,12 @@
 
     // Overscroll progress starts linearly with some resistance (3f) and slowly approaches 0.2f
     defaultOverscrollProgressConverter = ProgressConverter.tanh(maxProgress = 0.2f, tilt = 3f)
+    defaultSwipeSpec = spring(stiffness = 300f, dampingRatio = 0.8f, visibilityThreshold = 0.5f)
 
     // Scene transitions
 
     from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() }
+    from(Scenes.Dream, to = Scenes.Gone) { dreamToGoneTransition() }
     from(Scenes.Gone, to = Scenes.Shade) { goneToShadeTransition() }
     from(Scenes.Gone, to = Scenes.Shade, key = ToSplitShade) { goneToSplitShadeTransition() }
     from(Scenes.Gone, to = Scenes.Shade, key = SlightlyFasterShadeCollapse) {
@@ -68,6 +75,7 @@
         lockscreenToBouncerTransition()
     }
     from(Scenes.Lockscreen, to = Scenes.Communal) { lockscreenToCommunalTransition() }
+    from(Scenes.Lockscreen, to = Scenes.Dream) { lockscreenToDreamTransition() }
     from(Scenes.Lockscreen, to = Scenes.Shade) { lockscreenToShadeTransition() }
     from(Scenes.Lockscreen, to = Scenes.Shade, key = ToSplitShade) {
         lockscreenToSplitShadeTransition()
@@ -87,6 +95,8 @@
         sharedElement(Notifications.Elements.NotificationStackPlaceholder, enabled = false)
         sharedElement(Notifications.Elements.HeadsUpNotificationPlaceholder, enabled = false)
     }
+    from(Scenes.Communal, to = Scenes.Shade) { communalToShadeTransition() }
+    from(Scenes.Communal, to = Scenes.Bouncer) { communalToBouncerTransition() }
 
     // Overlay transitions
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToBouncerTransition.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
copy to packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToBouncerTransition.kt
index 15ed1b3..d7173fd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToBouncerTransition.kt
@@ -14,12 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.data.repository
+package com.android.systemui.scene.ui.composable.transitions
 
-import android.view.windowManagerService
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.compose.animation.scene.TransitionBuilder
 
-val Kosmos.systemGestureExclusionRepository by Fixture {
-    SystemGestureExclusionRepository(windowManager = windowManagerService)
+fun TransitionBuilder.communalToBouncerTransition() {
+    toBouncerTransition()
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToShadeTransition.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
copy to packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToShadeTransition.kt
index 15ed1b3..ba920ac 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToShadeTransition.kt
@@ -14,12 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.data.repository
+package com.android.systemui.scene.ui.composable.transitions
 
-import android.view.windowManagerService
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.compose.animation.scene.TransitionBuilder
 
-val Kosmos.systemGestureExclusionRepository by Fixture {
-    SystemGestureExclusionRepository(windowManager = windowManagerService)
+fun TransitionBuilder.communalToShadeTransition() {
+    toShadeTransition()
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToGoneTransition.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
copy to packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToGoneTransition.kt
index 15ed1b3..60dc4c05 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToGoneTransition.kt
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.data.repository
+package com.android.systemui.scene.ui.composable.transitions
 
-import android.view.windowManagerService
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.shared.model.Scenes
 
-val Kosmos.systemGestureExclusionRepository by Fixture {
-    SystemGestureExclusionRepository(windowManager = windowManagerService)
+fun TransitionBuilder.dreamToGoneTransition() {
+    spec = tween(durationMillis = 1000)
+
+    fade(Scenes.Dream.rootElementKey)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
index 4c0efd2..dd37b53 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
@@ -1,29 +1,11 @@
 package com.android.systemui.scene.ui.composable.transitions
 
 import androidx.compose.animation.core.CubicBezierEasing
-import androidx.compose.animation.core.tween
-import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.TransitionBuilder
-import com.android.compose.animation.scene.UserActionDistance
 import com.android.systemui.bouncer.ui.composable.Bouncer
 
-const val FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION = 0.5f
-const val FROM_LOCK_SCREEN_TO_BOUNCER_SWIPE_DISTANCE_FRACTION = 0.5f
-
 fun TransitionBuilder.lockscreenToBouncerTransition() {
-    spec = tween(durationMillis = 500)
-
-    distance = UserActionDistance { fromSceneSize, _ ->
-        fromSceneSize.height * FROM_LOCK_SCREEN_TO_BOUNCER_SWIPE_DISTANCE_FRACTION
-    }
-
-    translate(Bouncer.Elements.Content, y = 300.dp)
-    fractionRange(end = FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION) {
-        fade(Bouncer.Elements.Background)
-    }
-    fractionRange(start = FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION) {
-        fade(Bouncer.Elements.Content)
-    }
+    toBouncerTransition()
 }
 
 fun TransitionBuilder.bouncerToLockscreenPreview() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToDreamTransition.kt
similarity index 62%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt
rename to packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToDreamTransition.kt
index 3e46c3f..7092e3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToDreamTransition.kt
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.domain.interactor
+package com.android.systemui.scene.ui.composable.transitions
 
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.scene.data.repository.systemGestureExclusionRepository
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.shared.model.Scenes
 
-val Kosmos.systemGestureExclusionInteractor by Fixture {
-    SystemGestureExclusionInteractor(repository = systemGestureExclusionRepository)
+fun TransitionBuilder.lockscreenToDreamTransition() {
+    spec = tween(durationMillis = 1000)
+
+    fade(Scenes.Lockscreen.rootElementKey)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt
new file mode 100644
index 0000000..de76f70
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.systemui.bouncer.ui.composable.Bouncer
+
+const val TO_BOUNCER_FADE_FRACTION = 0.5f
+private const val TO_BOUNCER_SWIPE_DISTANCE_FRACTION = 0.5f
+
+fun TransitionBuilder.toBouncerTransition() {
+    spec = tween(durationMillis = 500)
+
+    distance = UserActionDistance { fromSceneSize, _ ->
+        fromSceneSize.height * TO_BOUNCER_SWIPE_DISTANCE_FRACTION
+    }
+
+    translate(Bouncer.Elements.Content, y = 300.dp)
+    fractionRange(end = TO_BOUNCER_FADE_FRACTION) { fade(Bouncer.Elements.Background) }
+    fractionRange(start = TO_BOUNCER_FADE_FRACTION) { fade(Bouncer.Elements.Content) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index 337f53a5..23c4f12 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -21,26 +21,20 @@
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
-import com.android.compose.animation.scene.UserActionDistance
 import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.shade.ui.composable.OverlayShade
 import com.android.systemui.shade.ui.composable.Shade
 import com.android.systemui.shade.ui.composable.ShadeHeader
 import kotlin.time.Duration.Companion.milliseconds
 
-fun TransitionBuilder.toNotificationsShadeTransition(
-    durationScale: Double = 1.0,
-) {
+fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
     swipeSpec =
         spring(
             stiffness = Spring.StiffnessMediumLow,
             visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
         )
-    distance = UserActionDistance { fromSceneSize, orientation ->
-        fromSceneSize.height.toFloat() * 2 / 3f
-    }
-
+    scaleSize(OverlayShade.Elements.Panel, height = 0f)
     translate(OverlayShade.Elements.Panel, Edge.Top)
 
     fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index c6c42fc..3ec057b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -40,7 +40,6 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.systemBars
 import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
@@ -89,7 +88,6 @@
 import com.android.systemui.media.controls.ui.controller.MediaCarouselController
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.view.MediaHost
-import com.android.systemui.media.controls.ui.view.MediaHostState
 import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.COLLAPSED
 import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.EXPANDED
 import com.android.systemui.media.dagger.MediaModule.QS_PANEL
@@ -117,7 +115,6 @@
 import javax.inject.Inject
 import javax.inject.Named
 import kotlin.math.roundToInt
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 
 object Shade {
@@ -128,23 +125,13 @@
     }
 
     object Dimensions {
-        val ScrimCornerSize = 32.dp
         val HorizontalPadding = 16.dp
         val ScrimOverscrollLimit = 32.dp
         const val ScrimVisibilityThreshold = 5f
     }
-
-    object Shapes {
-        val Scrim =
-            RoundedCornerShape(
-                topStart = Dimensions.ScrimCornerSize,
-                topEnd = Dimensions.ScrimCornerSize,
-            )
-    }
 }
 
 /** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */
-@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class ShadeScene
 @Inject
@@ -197,11 +184,11 @@
         )
 
     init {
-        qqsMediaHost.expansion = MediaHostState.EXPANDED
+        qqsMediaHost.expansion = EXPANDED
         qqsMediaHost.showsOnlyActiveMedia = true
         qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS)
 
-        qsMediaHost.expansion = MediaHostState.EXPANDED
+        qsMediaHost.expansion = EXPANDED
         qsMediaHost.showsOnlyActiveMedia = false
         qsMediaHost.init(MediaHierarchyManager.LOCATION_QS)
     }
@@ -329,8 +316,7 @@
         modifier =
             modifier.thenIf(shouldPunchHoleBehindScrim) {
                 // Render the scene to an offscreen buffer so that BlendMode.DstOut only clears this
-                // scene
-                // (and not the one under it) during a scene transition.
+                // scene (and not the one under it) during a scene transition.
                 Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
             }
     ) {
@@ -382,8 +368,8 @@
                     stackScrollView = notificationStackScrollView,
                     viewModel = notificationsPlaceholderViewModel,
                     maxScrimTop = { maxNotifScrimTop.toFloat() },
-                    shadeMode = ShadeMode.Single,
                     shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
+                    supportNestedScrolling = true,
                     onEmptySpaceClick =
                         viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
                     modifier =
@@ -601,7 +587,7 @@
                     maxScrimTop = { 0f },
                     shouldPunchHoleBehindScrim = false,
                     shouldReserveSpaceForNavBar = false,
-                    shadeMode = ShadeMode.Split,
+                    supportNestedScrolling = false,
                     onEmptySpaceClick =
                         viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
                     modifier =
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 5fa5db8..085157a 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
@@ -226,7 +226,6 @@
         val fromSource = resolveSwipeSource(startedPosition)
         val upOrLeft = resolveSwipe(pointersDown, fromSource, isUpOrLeft = true)
         val downOrRight = resolveSwipe(pointersDown, fromSource, isUpOrLeft = false)
-
         return if (fromSource == null) {
             Swipes(
                 upOrLeft = null,
@@ -366,10 +365,18 @@
             return 0f
         }
 
+        val currentTransitionIrreversible =
+            if (swipeAnimation.isUpOrLeft) {
+                swipes.upOrLeftResult?.isIrreversible ?: false
+            } else {
+                swipes.downOrRightResult?.isIrreversible ?: false
+            }
+
         val needNewTransition =
-            hasReachedToContent ||
-                result.toContent(layoutState.currentScene) != swipeAnimation.toContent ||
-                result.transitionKey != swipeAnimation.contentTransition.key
+            !currentTransitionIrreversible &&
+                (hasReachedToContent ||
+                    result.toContent(layoutState.currentScene) != swipeAnimation.toContent ||
+                    result.transitionKey != swipeAnimation.contentTransition.key)
 
         if (needNewTransition) {
             // Make sure the current transition will finish to the right current scene.
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 5ddc284..dc3135d 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
@@ -42,8 +42,10 @@
 import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
 import androidx.compose.ui.node.DelegatingNode
 import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.ObserverModifierNode
 import androidx.compose.ui.node.PointerInputModifierNode
 import androidx.compose.ui.node.currentValueOf
+import androidx.compose.ui.node.observeReads
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.Velocity
@@ -77,6 +79,7 @@
 @Stable
 internal fun Modifier.multiPointerDraggable(
     orientation: Orientation,
+    enabled: () -> Boolean,
     startDragImmediately: (startedPosition: Offset) -> Boolean,
     onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
     onFirstPointerDown: () -> Unit = {},
@@ -86,6 +89,7 @@
     this.then(
         MultiPointerDraggableElement(
             orientation,
+            enabled,
             startDragImmediately,
             onDragStarted,
             onFirstPointerDown,
@@ -96,6 +100,7 @@
 
 private data class MultiPointerDraggableElement(
     private val orientation: Orientation,
+    private val enabled: () -> Boolean,
     private val startDragImmediately: (startedPosition: Offset) -> Boolean,
     private val onDragStarted:
         (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
@@ -106,6 +111,7 @@
     override fun create(): MultiPointerDraggableNode =
         MultiPointerDraggableNode(
             orientation = orientation,
+            enabled = enabled,
             startDragImmediately = startDragImmediately,
             onDragStarted = onDragStarted,
             onFirstPointerDown = onFirstPointerDown,
@@ -115,6 +121,7 @@
 
     override fun update(node: MultiPointerDraggableNode) {
         node.orientation = orientation
+        node.enabled = enabled
         node.startDragImmediately = startDragImmediately
         node.onDragStarted = onDragStarted
         node.onFirstPointerDown = onFirstPointerDown
@@ -124,6 +131,7 @@
 
 internal class MultiPointerDraggableNode(
     orientation: Orientation,
+    enabled: () -> Boolean,
     var startDragImmediately: (startedPosition: Offset) -> Boolean,
     var onDragStarted:
         (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
@@ -134,10 +142,21 @@
     DelegatingNode(),
     PointerInputModifierNode,
     CompositionLocalConsumerModifierNode,
+    ObserverModifierNode,
     SpaceVectorConverter {
     private val pointerTracker = delegate(SuspendingPointerInputModifierNode { pointerTracker() })
     private val pointerInput = delegate(SuspendingPointerInputModifierNode { pointerInput() })
     private val velocityTracker = VelocityTracker()
+    private var previousEnabled: Boolean = false
+
+    var enabled: () -> Boolean = enabled
+        set(value) {
+            // Reset the pointer input whenever enabled changed.
+            if (value != field) {
+                field = value
+                pointerInput.resetPointerInputHandler()
+            }
+        }
 
     private var converter = SpaceVectorConverter(orientation)
 
@@ -159,6 +178,21 @@
             }
         }
 
+    override fun onAttach() {
+        previousEnabled = enabled()
+        onObservedReadsChanged()
+    }
+
+    override fun onObservedReadsChanged() {
+        observeReads {
+            val newEnabled = enabled()
+            if (newEnabled != previousEnabled) {
+                pointerInput.resetPointerInputHandler()
+            }
+            previousEnabled = newEnabled
+        }
+    }
+
     override fun onCancelPointerInput() {
         pointerTracker.onCancelPointerInput()
         pointerInput.onCancelPointerInput()
@@ -220,7 +254,9 @@
                         velocityTracker.resetTracking()
                         velocityTracker.addPointerInputChange(firstPointerDown)
                         startedPosition = firstPointerDown.position
-                        onFirstPointerDown()
+                        if (enabled()) {
+                            onFirstPointerDown()
+                        }
                     }
 
                     // Changes with at least one pointer
@@ -259,6 +295,10 @@
     }
 
     private suspend fun PointerInputScope.pointerInput() {
+        if (!enabled()) {
+            return
+        }
+
         val currentContext = currentCoroutineContext()
         awaitPointerEventScope {
             while (currentContext.isActive) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index cec8883..c9a4d58 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -498,6 +498,12 @@
      * bigger than 100% when the user released their finger. `
      */
     open val requiresFullDistanceSwipe: Boolean,
+
+    /**
+     * Whether swiping back in the opposite direction past the origin point of the swipe can replace
+     * the action with the action for the opposite direction.
+     */
+    open val isIrreversible: Boolean = false,
 ) {
     internal abstract fun toContent(currentScene: SceneKey): ContentKey
 
@@ -507,6 +513,7 @@
         val toScene: SceneKey,
         override val transitionKey: TransitionKey? = null,
         override val requiresFullDistanceSwipe: Boolean = false,
+        override val isIrreversible: Boolean = false,
     ) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
         override fun toContent(currentScene: SceneKey): ContentKey = toScene
     }
@@ -516,6 +523,7 @@
         val overlay: OverlayKey,
         override val transitionKey: TransitionKey? = null,
         override val requiresFullDistanceSwipe: Boolean = false,
+        override val isIrreversible: Boolean = false,
     ) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
         override fun toContent(currentScene: SceneKey): ContentKey = overlay
     }
@@ -558,7 +566,14 @@
              * the user released their finger.
              */
             requiresFullDistanceSwipe: Boolean = false,
-        ): UserActionResult = ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe)
+
+            /**
+             * Whether swiping back in the opposite direction past the origin point of the swipe can
+             * replace the action with the action for the opposite direction.
+             */
+            isIrreversible: Boolean = false,
+        ): UserActionResult =
+            ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe, isIrreversible)
 
         /** A [UserActionResult] that shows [toOverlay]. */
         operator fun invoke(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index b358faf..879dc54 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -152,6 +152,7 @@
         internal val DefaultSwipeSpec =
             spring(
                 stiffness = Spring.StiffnessMediumLow,
+                dampingRatio = Spring.DampingRatioLowBouncy,
                 visibilityThreshold = OffsetVisibilityThreshold,
             )
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index d201be9..98d4aaa 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -41,28 +41,7 @@
     draggableHandler: DraggableHandlerImpl,
     swipeDetector: SwipeDetector,
 ): Modifier {
-    return if (draggableHandler.enabled()) {
-        this.then(SwipeToSceneElement(draggableHandler, swipeDetector))
-    } else {
-        this
-    }
-}
-
-private fun DraggableHandlerImpl.enabled(): Boolean {
-    return isDrivingTransition || contentForSwipes().shouldEnableSwipes(orientation)
-}
-
-private fun DraggableHandlerImpl.contentForSwipes(): Content {
-    return layoutImpl.contentForUserActions()
-}
-
-/** Whether swipe should be enabled in the given [orientation]. */
-private fun Content.shouldEnableSwipes(orientation: Orientation): Boolean {
-    if (userActions.isEmpty()) {
-        return false
-    }
-
-    return userActions.keys.any { it is Swipe.Resolved && it.direction.orientation == orientation }
+    return this.then(SwipeToSceneElement(draggableHandler, swipeDetector))
 }
 
 private data class SwipeToSceneElement(
@@ -85,6 +64,7 @@
         delegate(
             MultiPointerDraggableNode(
                 orientation = draggableHandler.orientation,
+                enabled = ::enabled,
                 startDragImmediately = ::startDragImmediately,
                 onDragStarted = draggableHandler::onDragStarted,
                 onFirstPointerDown = ::onFirstPointerDown,
@@ -144,6 +124,22 @@
 
     override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput()
 
+    private fun enabled(): Boolean {
+        return draggableHandler.isDrivingTransition ||
+            contentForSwipes().shouldEnableSwipes(multiPointerDraggableNode.orientation)
+    }
+
+    private fun contentForSwipes(): Content {
+        return draggableHandler.layoutImpl.contentForUserActions()
+    }
+
+    /** Whether swipe should be enabled in the given [orientation]. */
+    private fun Content.shouldEnableSwipes(orientation: Orientation): Boolean {
+        return userActions.keys.any {
+            it is Swipe.Resolved && it.direction.orientation == orientation
+        }
+    }
+
     private fun startDragImmediately(startedPosition: Offset): Boolean {
         // Immediately start the drag if the user can't swipe in the other direction and the gesture
         // handler can intercept it.
@@ -156,7 +152,7 @@
                 Orientation.Vertical -> Orientation.Horizontal
                 Orientation.Horizontal -> Orientation.Vertical
             }
-        return draggableHandler.contentForSwipes().shouldEnableSwipes(oppositeOrientation)
+        return contentForSwipes().shouldEnableSwipes(oppositeOrientation)
     }
 }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index a6ebb0e..a3641e6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -34,71 +34,76 @@
  * Note: Call [reset] before destroying this object to make sure you always get a call to [onStop]
  * after [onStart].
  *
- * @sample com.android.compose.animation.scene.rememberSwipeToSceneNestedScrollConnection
+ * @sample LargeTopAppBarNestedScrollConnection
+ * @sample com.android.compose.animation.scene.NestedScrollHandlerImpl.nestedScrollConnection
  */
 class PriorityNestedScrollConnection(
-    private val canStartPreScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
-    private val canStartPostScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
-    private val canStartPostFling: (velocityAvailable: Velocity) -> Boolean,
+    orientation: Orientation,
+    private val canStartPreScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
+    private val canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
+    private val canStartPostFling: (velocityAvailable: Float) -> Boolean,
     private val canContinueScroll: (source: NestedScrollSource) -> Boolean,
     private val canScrollOnFling: Boolean,
-    private val onStart: (offsetAvailable: Offset) -> Unit,
-    private val onScroll: (offsetAvailable: Offset) -> Offset,
-    private val onStop: (velocityAvailable: Velocity) -> SuspendedValue<Velocity>,
-) : NestedScrollConnection {
+    private val onStart: (offsetAvailable: Float) -> Unit,
+    private val onScroll: (offsetAvailable: Float) -> Float,
+    private val onStop: (velocityAvailable: Float) -> SuspendedValue<Float>,
+) : NestedScrollConnection, SpaceVectorConverter by SpaceVectorConverter(orientation) {
 
     /** In priority mode [onPreScroll] events are first consumed by the parent, via [onScroll]. */
     private var isPriorityMode = false
 
-    private var offsetScrolledBeforePriorityMode = Offset.Zero
+    private var offsetScrolledBeforePriorityMode = 0f
 
     override fun onPostScroll(
         consumed: Offset,
         available: Offset,
         source: NestedScrollSource,
     ): Offset {
+        val availableFloat = available.toFloat()
         // The offset before the start takes into account the up and down movements, starting from
         // the beginning or from the last fling gesture.
-        val offsetBeforeStart = offsetScrolledBeforePriorityMode - available
+        val offsetBeforeStart = offsetScrolledBeforePriorityMode - availableFloat
 
         if (
             isPriorityMode ||
                 (source == NestedScrollSource.SideEffect && !canScrollOnFling) ||
-                !canStartPostScroll(available, offsetBeforeStart)
+                !canStartPostScroll(availableFloat, offsetBeforeStart)
         ) {
             // The priority mode cannot start so we won't consume the available offset.
             return Offset.Zero
         }
 
-        return onPriorityStart(available)
+        return onPriorityStart(availableFloat).toOffset()
     }
 
     override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
         if (!isPriorityMode) {
             if (source == NestedScrollSource.UserInput || canScrollOnFling) {
-                if (canStartPreScroll(available, offsetScrolledBeforePriorityMode)) {
-                    return onPriorityStart(available)
+                val availableFloat = available.toFloat()
+                if (canStartPreScroll(availableFloat, offsetScrolledBeforePriorityMode)) {
+                    return onPriorityStart(availableFloat).toOffset()
                 }
                 // We want to track the amount of offset consumed before entering priority mode
-                offsetScrolledBeforePriorityMode += available
+                offsetScrolledBeforePriorityMode += availableFloat
             }
 
             return Offset.Zero
         }
 
+        val availableFloat = available.toFloat()
         if (!canContinueScroll(source)) {
             // Step 3a: We have lost priority and we no longer need to intercept scroll events.
-            onPriorityStop(velocity = Velocity.Zero)
+            onPriorityStop(velocity = 0f)
 
-            // We've just reset offsetScrolledBeforePriorityMode to Offset.Zero
+            // We've just reset offsetScrolledBeforePriorityMode to 0f
             // We want to track the amount of offset consumed before entering priority mode
-            offsetScrolledBeforePriorityMode += available
+            offsetScrolledBeforePriorityMode += availableFloat
 
             return Offset.Zero
         }
 
         // Step 2: We have the priority and can consume the scroll events.
-        return onScroll(available)
+        return onScroll(availableFloat).toOffset()
     }
 
     override suspend fun onPreFling(available: Velocity): Velocity {
@@ -108,15 +113,16 @@
         }
         // Step 3b: The finger is lifted, we can stop intercepting scroll events and use the speed
         // of the fling gesture.
-        return onPriorityStop(velocity = available).invoke()
+        return onPriorityStop(velocity = available.toFloat()).invoke().toVelocity()
     }
 
     override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+        val availableFloat = available.toFloat()
         if (isPriorityMode) {
-            return onPriorityStop(velocity = available).invoke()
+            return onPriorityStop(velocity = availableFloat).invoke().toVelocity()
         }
 
-        if (!canStartPostFling(available)) {
+        if (!canStartPostFling(availableFloat)) {
             return Velocity.Zero
         }
 
@@ -124,11 +130,11 @@
         // given the available velocity.
         // TODO(b/291053278): Remove canStartPostFling() and instead make it possible to define the
         // overscroll behavior on the Scene level.
-        val smallOffset = Offset(available.x.sign, available.y.sign)
-        onPriorityStart(available = smallOffset)
+        val smallOffset = availableFloat.sign
+        onPriorityStart(availableOffset = smallOffset)
 
         // This is the last event of a scroll gesture.
-        return onPriorityStop(available).invoke()
+        return onPriorityStop(availableFloat).invoke().toVelocity()
     }
 
     /**
@@ -138,10 +144,10 @@
      */
     fun reset() {
         // Step 3c: To ensure that an onStop is always called for every onStart.
-        onPriorityStop(velocity = Velocity.Zero)
+        onPriorityStop(velocity = 0f)
     }
 
-    private fun onPriorityStart(available: Offset): Offset {
+    private fun onPriorityStart(availableOffset: Float): Float {
         if (isPriorityMode) {
             error("This should never happen, onPriorityStart() was called when isPriorityMode")
         }
@@ -152,17 +158,17 @@
 
         // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
         // lifted (step 3b), or this object has been destroyed (step 3c).
-        onStart(available)
+        onStart(availableOffset)
 
-        return onScroll(available)
+        return onScroll(availableOffset)
     }
 
-    private fun onPriorityStop(velocity: Velocity): SuspendedValue<Velocity> {
+    private fun onPriorityStop(velocity: Float): SuspendedValue<Float> {
         // We can restart tracking the consumed offsets from scratch.
-        offsetScrolledBeforePriorityMode = Offset.Zero
+        offsetScrolledBeforePriorityMode = 0f
 
         if (!isPriorityMode) {
-            return { Velocity.Zero }
+            return { 0f }
         }
 
         isPriorityMode = false
@@ -170,38 +176,3 @@
         return onStop(velocity)
     }
 }
-
-fun PriorityNestedScrollConnection(
-    orientation: Orientation,
-    canStartPreScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
-    canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
-    canStartPostFling: (velocityAvailable: Float) -> Boolean,
-    canContinueScroll: (source: NestedScrollSource) -> Boolean,
-    canScrollOnFling: Boolean,
-    onStart: (offsetAvailable: Float) -> Unit,
-    onScroll: (offsetAvailable: Float) -> Float,
-    onStop: (velocityAvailable: Float) -> SuspendedValue<Float>,
-) =
-    with(SpaceVectorConverter(orientation)) {
-        PriorityNestedScrollConnection(
-            canStartPreScroll = { offsetAvailable: Offset, offsetBeforeStart: Offset ->
-                canStartPreScroll(offsetAvailable.toFloat(), offsetBeforeStart.toFloat())
-            },
-            canStartPostScroll = { offsetAvailable: Offset, offsetBeforeStart: Offset ->
-                canStartPostScroll(offsetAvailable.toFloat(), offsetBeforeStart.toFloat())
-            },
-            canStartPostFling = { velocityAvailable: Velocity ->
-                canStartPostFling(velocityAvailable.toFloat())
-            },
-            canContinueScroll = canContinueScroll,
-            canScrollOnFling = canScrollOnFling,
-            onStart = { offsetAvailable -> onStart(offsetAvailable.toFloat()) },
-            onScroll = { offsetAvailable: Offset ->
-                onScroll(offsetAvailable.toFloat()).toOffset()
-            },
-            onStop = { velocityAvailable: Velocity ->
-                val consumedVelocity = onStop(velocityAvailable.toFloat())
-                suspend { consumedVelocity.invoke().toVelocity() }
-            },
-        )
-    }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index dd4f99f..ecef6be 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -507,6 +507,54 @@
     }
 
     @Test
+    fun onDragWithActionsInBothDirections_dragToOppositeDirectionReplacesAction() = runGestureTest {
+        // We are on SceneA. UP -> B, DOWN-> C.
+        val dragController = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
+        assertTransition(
+            currentScene = SceneA,
+            fromScene = SceneA,
+            toScene = SceneB,
+            progress = 0.2f,
+        )
+
+        // Reverse drag direction, it will replace the previous transition
+        dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f))
+        assertTransition(
+            currentScene = SceneA,
+            fromScene = SceneA,
+            toScene = SceneC,
+            progress = 0.3f,
+        )
+    }
+
+    @Test
+    fun onDragWithActionsInBothDirections_dragToOppositeDirectionNotReplaceable() = runGestureTest {
+        // We are on SceneA. UP -> B, DOWN-> C. The up swipe is not replaceable though.
+        mutableUserActionsA =
+            mapOf(Swipe.Up to UserActionResult(SceneB, isIrreversible = true), Swipe.Down to SceneC)
+        val dragController =
+            onDragStarted(
+                startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f),
+                overSlop = up(fractionOfScreen = 0.2f),
+            )
+        assertTransition(
+            currentScene = SceneA,
+            fromScene = SceneA,
+            toScene = SceneB,
+            progress = 0.2f,
+        )
+
+        // Reverse drag direction, it cannot replace the previous transition
+        dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f))
+        assertTransition(
+            currentScene = SceneA,
+            fromScene = SceneA,
+            toScene = SceneB,
+            progress = -0.3f,
+        )
+    }
+
+    @Test
     fun onDragFromEdge_startTransitionToEdgeAction() = runGestureTest {
         navigateToSceneC()
 
@@ -1241,7 +1289,8 @@
     fun overscroll_releaseBetween0And100Percent_up() = runGestureTest {
         // Make scene B overscrollable.
         layoutState.transitions = transitions {
-            from(SceneA, to = SceneB) { spec = spring(dampingRatio = Spring.DampingRatioNoBouncy) }
+            defaultSwipeSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
+            from(SceneA, to = SceneB) {}
             overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
         }
 
@@ -1272,7 +1321,8 @@
     fun overscroll_releaseBetween0And100Percent_down() = runGestureTest {
         // Make scene C overscrollable.
         layoutState.transitions = transitions {
-            from(SceneA, to = SceneC) { spec = spring(dampingRatio = Spring.DampingRatioNoBouncy) }
+            defaultSwipeSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
+            from(SceneA, to = SceneC) {}
             overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
         }
 
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index c8f6e6d..493f3a1 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -45,7 +45,6 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Velocity
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.compose.modifiers.thenIf
 import com.android.compose.nestedscroll.SuspendedValue
 import com.google.common.truth.Truth.assertThat
 import kotlin.properties.Delegates
@@ -95,20 +94,19 @@
             Box(
                 Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
                     .nestedScrollDispatcher()
-                    .thenIf(enabled) {
-                        Modifier.multiPointerDraggable(
-                            orientation = Orientation.Vertical,
-                            startDragImmediately = { false },
-                            onDragStarted = { _, _, _ ->
-                                started = true
-                                SimpleDragController(
-                                    onDrag = { dragged = true },
-                                    onStop = { stopped = true },
-                                )
-                            },
-                            dispatcher = defaultDispatcher,
-                        )
-                    }
+                    .multiPointerDraggable(
+                        orientation = Orientation.Vertical,
+                        enabled = { enabled },
+                        startDragImmediately = { false },
+                        onDragStarted = { _, _, _ ->
+                            started = true
+                            SimpleDragController(
+                                onDrag = { dragged = true },
+                                onStop = { stopped = true },
+                            )
+                        },
+                        dispatcher = defaultDispatcher,
+                    )
             )
         }
 
@@ -166,6 +164,7 @@
                     .nestedScrollDispatcher()
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
+                        enabled = { true },
                         // We want to start a drag gesture immediately
                         startDragImmediately = { true },
                         onDragStarted = { _, _, _ ->
@@ -239,6 +238,7 @@
                     .nestedScrollDispatcher()
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
+                        enabled = { true },
                         startDragImmediately = { false },
                         onDragStarted = { _, _, _ ->
                             started = true
@@ -358,6 +358,7 @@
                     .nestedScrollDispatcher()
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
+                        enabled = { true },
                         startDragImmediately = { false },
                         onDragStarted = { _, _, _ ->
                             started = true
@@ -463,6 +464,7 @@
                     .nestedScrollDispatcher()
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
+                        enabled = { true },
                         startDragImmediately = { false },
                         onDragStarted = { _, _, _ ->
                             verticalStarted = true
@@ -475,6 +477,7 @@
                     )
                     .multiPointerDraggable(
                         orientation = Orientation.Horizontal,
+                        enabled = { true },
                         startDragImmediately = { false },
                         onDragStarted = { _, _, _ ->
                             horizontalStarted = true
@@ -567,6 +570,7 @@
                     .nestedScrollDispatcher()
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
+                        enabled = { true },
                         startDragImmediately = { false },
                         swipeDetector =
                             object : SwipeDetector {
@@ -668,6 +672,7 @@
                     .nestedScrollDispatcher()
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
+                        enabled = { true },
                         startDragImmediately = { false },
                         onDragStarted = { _, _, _ ->
                             SimpleDragController(
@@ -739,6 +744,7 @@
                     .nestedScrollDispatcher()
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
+                        enabled = { true },
                         startDragImmediately = { false },
                         onDragStarted = { _, _, _ ->
                             SimpleDragController(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index ce64628..25e8713 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -22,15 +22,11 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.size
-import androidx.compose.material3.Button
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
@@ -40,11 +36,8 @@
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.assertPositionInRootIsEqualTo
-import androidx.compose.ui.test.assertTextEquals
 import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onRoot
-import androidx.compose.ui.test.performClick
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeWithVelocity
 import androidx.compose.ui.unit.Density
@@ -851,29 +844,4 @@
         assertThat(transition.progress).isEqualTo(1f)
         assertThat(availableOnPostScroll).isEqualTo(ovescrollPx)
     }
-
-    @Test
-    fun sceneWithoutSwipesDoesNotConsumeGestures() {
-        val buttonTag = "button"
-
-        rule.setContent {
-            Box {
-                var count by remember { mutableStateOf(0) }
-                Button(onClick = { count++ }, Modifier.testTag(buttonTag).align(Alignment.Center)) {
-                    Text("Count: $count")
-                }
-
-                SceneTransitionLayout(remember { MutableSceneTransitionLayoutState(SceneA) }) {
-                    scene(SceneA) { Box(Modifier.fillMaxSize()) }
-                }
-            }
-        }
-
-        rule.onNodeWithTag(buttonTag).assertTextEquals("Count: 0")
-
-        // Click on the root at its center, where the button is located. Clicks should go through
-        // the STL and reach the button given that there is no swipes for the current scene.
-        repeat(3) { rule.onRoot().performClick() }
-        rule.onNodeWithTag(buttonTag).assertTextEquals("Count: 3")
-    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index bde7699..badc43b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -18,8 +18,9 @@
 
 package com.android.compose.nestedscroll
 
+import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
 import androidx.compose.ui.unit.Velocity
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
@@ -35,13 +36,14 @@
     private var canStartPostFling = false
     private var canContinueScroll = false
     private var isStarted = false
-    private var lastScroll: Offset? = null
-    private var returnOnScroll = Offset.Zero
-    private var lastStop: Velocity? = null
-    private var returnOnStop = Velocity.Zero
+    private var lastScroll: Float? = null
+    private var returnOnScroll = 0f
+    private var lastStop: Float? = null
+    private var returnOnStop = 0f
 
     private val scrollConnection =
         PriorityNestedScrollConnection(
+            orientation = Orientation.Vertical,
             canStartPreScroll = { _, _ -> canStartPreScroll },
             canStartPostScroll = { _, _ -> canStartPostScroll },
             canStartPostFling = { canStartPostFling },
@@ -58,11 +60,6 @@
             },
         )
 
-    private val offset1 = Offset(1f, 1f)
-    private val offset2 = Offset(2f, 2f)
-    private val velocity1 = Velocity(1f, 1f)
-    private val velocity2 = Velocity(2f, 2f)
-
     @Test
     fun step1_priorityModeShouldStartOnlyOnPreScroll() = runTest {
         canStartPreScroll = true
@@ -70,7 +67,7 @@
         scrollConnection.onPostScroll(
             consumed = Offset.Zero,
             available = Offset.Zero,
-            source = NestedScrollSource.Drag,
+            source = UserInput,
         )
         assertThat(isStarted).isEqualTo(false)
 
@@ -80,7 +77,7 @@
         scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
         assertThat(isStarted).isEqualTo(false)
 
-        scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+        scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
         assertThat(isStarted).isEqualTo(true)
     }
 
@@ -89,7 +86,7 @@
         scrollConnection.onPostScroll(
             consumed = Offset.Zero,
             available = Offset.Zero,
-            source = NestedScrollSource.Drag,
+            source = UserInput,
         )
     }
 
@@ -97,7 +94,7 @@
     fun step1_priorityModeShouldStartOnlyOnPostScroll() = runTest {
         canStartPostScroll = true
 
-        scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+        scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
         assertThat(isStarted).isEqualTo(false)
 
         scrollConnection.onPreFling(available = Velocity.Zero)
@@ -115,7 +112,7 @@
         scrollConnection.onPostScroll(
             consumed = Offset.Zero,
             available = Offset.Zero,
-            source = NestedScrollSource.Drag,
+            source = UserInput,
         )
         assertThat(isStarted).isEqualTo(false)
 
@@ -128,12 +125,12 @@
         canStartPostScroll = true
 
         scrollConnection.onPostScroll(
-            consumed = offset1,
-            available = offset2,
-            source = NestedScrollSource.Drag,
+            consumed = Offset(1f, 1f),
+            available = Offset(2f, 2f),
+            source = UserInput,
         )
 
-        assertThat(lastScroll).isEqualTo(offset2)
+        assertThat(lastScroll).isEqualTo(2f)
     }
 
     @Test
@@ -141,13 +138,13 @@
         startPriorityModePostScroll()
         canContinueScroll = true
 
-        scrollConnection.onPreScroll(available = offset1, source = NestedScrollSource.Drag)
-        assertThat(lastScroll).isEqualTo(offset1)
+        scrollConnection.onPreScroll(available = Offset(1f, 1f), source = UserInput)
+        assertThat(lastScroll).isEqualTo(1f)
 
         canContinueScroll = false
-        scrollConnection.onPreScroll(available = offset2, source = NestedScrollSource.Drag)
-        assertThat(lastScroll).isNotEqualTo(offset2)
-        assertThat(lastScroll).isEqualTo(offset1)
+        scrollConnection.onPreScroll(available = Offset(2f, 2f), source = UserInput)
+        assertThat(lastScroll).isNotEqualTo(2f)
+        assertThat(lastScroll).isEqualTo(1f)
     }
 
     @Test
@@ -155,7 +152,7 @@
         startPriorityModePostScroll()
         canContinueScroll = false
 
-        scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+        scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
 
         assertThat(lastStop).isNotNull()
     }
@@ -184,22 +181,22 @@
     fun receive_onPostFling() = runTest {
         canStartPostFling = true
 
-        scrollConnection.onPostFling(consumed = velocity1, available = velocity2)
+        scrollConnection.onPostFling(consumed = Velocity(1f, 1f), available = Velocity(2f, 2f))
 
-        assertThat(lastStop).isEqualTo(velocity2)
+        assertThat(lastStop).isEqualTo(2f)
     }
 
     @Test
     fun step1_priorityModeShouldStartOnlyOnPostFling() = runTest {
         canStartPostFling = true
 
-        scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+        scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
         assertThat(isStarted).isEqualTo(false)
 
         scrollConnection.onPostScroll(
             consumed = Offset.Zero,
             available = Offset.Zero,
-            source = NestedScrollSource.Drag,
+            source = UserInput,
         )
         assertThat(isStarted).isEqualTo(false)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 94d3b2c..176824f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -61,6 +61,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.UserSwitcherController.UserSwitchCallback;
 import com.android.systemui.user.data.source.UserRecord;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.GlobalSettings;
@@ -70,6 +71,8 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -96,6 +99,9 @@
     private UserSwitcherController mUserSwitcherController;
     @Mock
     private FalsingA11yDelegate mFalsingA11yDelegate;
+    @Captor
+    private ArgumentCaptor<UserSwitchCallback> mUserSwitchCallbackCaptor =
+            ArgumentCaptor.forClass(UserSwitchCallback.class);
 
     private KeyguardSecurityContainer mKeyguardSecurityContainer;
     private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@@ -360,6 +366,18 @@
     }
 
     @Test
+    public void goingOutOfUserSwitcherRemovesCallback() {
+        // WHEN UserSwitcherViewMode is initialized
+        setupUserSwitcher();
+        verify(mUserSwitcherController).addUserSwitchCallback(mUserSwitchCallbackCaptor.capture());
+
+        // Back to default mode, as SIM PIN would be
+        initMode(MODE_DEFAULT);
+        verify(mUserSwitcherController).removeUserSwitchCallback(
+                mUserSwitchCallbackCaptor.getValue());
+    }
+
+    @Test
     public void testOnDensityOrFontScaleChanged() {
         setupUserSwitcher();
         View oldUserSwitcher = mKeyguardSecurityContainer.findViewById(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable
index 0d369a3..97f2e56 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable
@@ -67,7 +67,6 @@
 import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
 import com.android.systemui.scene.ui.composable.Scene
 import com.android.systemui.scene.ui.composable.SceneContainer
-import com.android.systemui.settings.displayTracker
 import com.android.systemui.testKosmos
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.awaitCancellation
@@ -127,7 +126,7 @@
 
     private val sceneContainerViewModel by lazy {
         kosmos.sceneContainerViewModelFactory
-            .create(view, kosmos.displayTracker.defaultDisplayId, {})
+            .create(view) {}
             .apply { setTransitionState(transitionState) }
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
index 58b59ff..755c4eb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
@@ -85,7 +85,8 @@
             assertThat(actions).isNotEmpty()
             assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
-            assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
+            assertThat(actions?.get(Swipe.Down))
+                .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
 
             setUpState(
                 isShadeTouchable = false,
@@ -102,7 +103,8 @@
             assertThat(actions).isNotEmpty()
             assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
-            assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
+            assertThat(actions?.get(Swipe.Down))
+                .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
         }
 
     @Test
@@ -120,7 +122,7 @@
             assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
             assertThat(actions?.get(Swipe.Down))
-                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
 
             setUpState(
                 isShadeTouchable = false,
@@ -138,7 +140,7 @@
             assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
             assertThat(actions?.get(Swipe.Down))
-                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
         }
 
     @Test
@@ -156,7 +158,9 @@
             assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
             assertThat(actions?.get(Swipe.Down))
-                .isEqualTo(UserActionResult(Overlays.NotificationsShade))
+                .isEqualTo(
+                    UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+                )
 
             setUpState(
                 isShadeTouchable = false,
@@ -170,7 +174,9 @@
             assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
             assertThat(actions?.get(Swipe.Down))
-                .isEqualTo(UserActionResult(Overlays.NotificationsShade))
+                .isEqualTo(
+                    UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+                )
         }
 
     private fun TestScope.setUpState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt
new file mode 100644
index 0000000..e1946fc
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt
@@ -0,0 +1,323 @@
+/*
+ * 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.communal.ui.viewmodel
+
+import androidx.compose.foundation.gestures.DraggableAnchors
+import androidx.compose.runtime.snapshots.Snapshot
+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.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ResizeableItemFrameViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.resizeableItemFrameViewModel
+
+    /** Total viewport height of the entire grid */
+    private val viewportHeightPx = 100
+    /** Total amount of vertical padding around the viewport */
+    private val verticalContentPaddingPx = 20f
+
+    private val singleSpanGrid =
+        GridLayout(
+            verticalItemSpacingPx = 10f,
+            verticalContentPaddingPx = verticalContentPaddingPx,
+            viewportHeightPx = viewportHeightPx,
+            maxItemSpan = 1,
+            minItemSpan = 1,
+            currentSpan = 1,
+            currentRow = 0,
+        )
+
+    @Before
+    fun setUp() {
+        underTest.activateIn(testScope)
+    }
+
+    @Test
+    fun testDefaultState() {
+        val topState = underTest.topDragState
+        assertThat(topState.currentValue).isEqualTo(0)
+        assertThat(topState.offset).isEqualTo(0f)
+        assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+
+        val bottomState = underTest.bottomDragState
+        assertThat(bottomState.currentValue).isEqualTo(0)
+        assertThat(bottomState.offset).isEqualTo(0f)
+        assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
+    }
+
+    @Test
+    fun testSingleSpanGrid() =
+        testScope.runTest(timeout = Duration.INFINITE) {
+            updateGridLayout(singleSpanGrid)
+
+            val topState = underTest.topDragState
+            assertThat(topState.currentValue).isEqualTo(0)
+            assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+
+            val bottomState = underTest.bottomDragState
+            assertThat(bottomState.currentValue).isEqualTo(0)
+            assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
+        }
+
+    /**
+     * Verifies element in first row which is already at the minimum size can only be expanded
+     * downwards.
+     */
+    @Test
+    fun testTwoSpanGrid_elementInFirstRow_sizeSingleSpan() =
+        testScope.runTest {
+            updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2))
+
+            val topState = underTest.topDragState
+            assertThat(topState.currentValue).isEqualTo(0)
+            assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+
+            val bottomState = underTest.bottomDragState
+            assertThat(bottomState.currentValue).isEqualTo(0)
+            assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 45f)
+        }
+
+    /**
+     * Verifies element in second row which is already at the minimum size can only be expanded
+     * upwards.
+     */
+    @Test
+    fun testTwoSpanGrid_elementInSecondRow_sizeSingleSpan() =
+        testScope.runTest {
+            updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, currentRow = 1))
+
+            val topState = underTest.topDragState
+            assertThat(topState.currentValue).isEqualTo(0)
+            assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -1 to -45f)
+
+            val bottomState = underTest.bottomDragState
+            assertThat(bottomState.currentValue).isEqualTo(0)
+            assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
+        }
+
+    /**
+     * Verifies element in first row which is already at full size (2 span) can only be shrunk from
+     * the bottom.
+     */
+    @Test
+    fun testTwoSpanGrid_elementInFirstRow_sizeTwoSpan() =
+        testScope.runTest {
+            updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, currentSpan = 2))
+
+            val topState = underTest.topDragState
+            assertThat(topState.currentValue).isEqualTo(0)
+            assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+
+            val bottomState = underTest.bottomDragState
+            assertThat(bottomState.currentValue).isEqualTo(0)
+            assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, -1 to -45f)
+        }
+
+    /**
+     * Verifies element in a middle row at minimum size can be expanded from either top or bottom.
+     */
+    @Test
+    fun testThreeSpanGrid_elementInMiddleRow_sizeOneSpan() =
+        testScope.runTest {
+            updateGridLayout(singleSpanGrid.copy(maxItemSpan = 3, currentRow = 1))
+
+            val topState = underTest.topDragState
+            assertThat(topState.currentValue).isEqualTo(0)
+            assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -1 to -30f)
+
+            val bottomState = underTest.bottomDragState
+            assertThat(bottomState.currentValue).isEqualTo(0)
+            assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 30f)
+        }
+
+    @Test
+    fun testThreeSpanGrid_elementInTopRow_sizeOneSpan() =
+        testScope.runTest {
+            updateGridLayout(singleSpanGrid.copy(maxItemSpan = 3))
+
+            val topState = underTest.topDragState
+            assertThat(topState.currentValue).isEqualTo(0)
+            assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+
+            val bottomState = underTest.bottomDragState
+            assertThat(bottomState.currentValue).isEqualTo(0)
+            assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 30f, 2 to 60f)
+        }
+
+    @Test
+    fun testSixSpanGrid_minSpanThree_itemInThirdRow_sizeThreeSpans() =
+        testScope.runTest {
+            updateGridLayout(
+                singleSpanGrid.copy(
+                    maxItemSpan = 6,
+                    currentRow = 3,
+                    currentSpan = 3,
+                    minItemSpan = 3,
+                )
+            )
+
+            val topState = underTest.topDragState
+            assertThat(topState.currentValue).isEqualTo(0)
+            assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -3 to -45f)
+
+            val bottomState = underTest.bottomDragState
+            assertThat(bottomState.currentValue).isEqualTo(0)
+            assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
+        }
+
+    @Test
+    fun testTwoSpanGrid_elementMovesFromFirstRowToSecondRow() =
+        testScope.runTest {
+            updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2))
+
+            val topState = underTest.topDragState
+            val bottomState = underTest.bottomDragState
+
+            assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+            assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 45f)
+
+            updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, currentRow = 1))
+
+            assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -1 to -45f)
+            assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
+        }
+
+    @Test
+    fun testTwoSpanGrid_expandElementFromBottom() = runTestWithSnapshots {
+        val resizeInfo by collectLastValue(underTest.resizeInfo)
+        updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2))
+
+        assertThat(resizeInfo).isNull()
+        underTest.bottomDragState.anchoredDrag { dragTo(45f) }
+        assertThat(resizeInfo).isEqualTo(ResizeInfo(1, DragHandle.BOTTOM))
+    }
+
+    @Test
+    fun testThreeSpanGrid_expandMiddleElementUpwards() = runTestWithSnapshots {
+        val resizeInfo by collectLastValue(underTest.resizeInfo)
+        updateGridLayout(singleSpanGrid.copy(maxItemSpan = 3, currentRow = 1))
+
+        assertThat(resizeInfo).isNull()
+        underTest.topDragState.anchoredDrag { dragTo(-30f) }
+        assertThat(resizeInfo).isEqualTo(ResizeInfo(1, DragHandle.TOP))
+    }
+
+    @Test
+    fun testThreeSpanGrid_expandTopElementDownBy2Spans() = runTestWithSnapshots {
+        val resizeInfo by collectLastValue(underTest.resizeInfo)
+        updateGridLayout(singleSpanGrid.copy(maxItemSpan = 3))
+
+        assertThat(resizeInfo).isNull()
+        underTest.bottomDragState.anchoredDrag { dragTo(60f) }
+        assertThat(resizeInfo).isEqualTo(ResizeInfo(2, DragHandle.BOTTOM))
+    }
+
+    @Test
+    fun testTwoSpanGrid_shrinkElementFromBottom() = runTestWithSnapshots {
+        val resizeInfo by collectLastValue(underTest.resizeInfo)
+        updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, currentSpan = 2))
+
+        assertThat(resizeInfo).isNull()
+        underTest.bottomDragState.anchoredDrag { dragTo(-45f) }
+        assertThat(resizeInfo).isEqualTo(ResizeInfo(-1, DragHandle.BOTTOM))
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun testIllegalState_maxSpanSmallerThanMinSpan() =
+        testScope.runTest {
+            updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, minItemSpan = 3))
+        }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun testIllegalState_minSpanOfZero() =
+        testScope.runTest {
+            updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, minItemSpan = 0))
+        }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun testIllegalState_maxSpanOfZero() =
+        testScope.runTest {
+            updateGridLayout(singleSpanGrid.copy(maxItemSpan = 0, minItemSpan = 0))
+        }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun testIllegalState_currentRowNotMultipleOfMinSpan() =
+        testScope.runTest {
+            updateGridLayout(singleSpanGrid.copy(maxItemSpan = 6, minItemSpan = 3, currentSpan = 2))
+        }
+
+    private fun TestScope.updateGridLayout(gridLayout: GridLayout) {
+        underTest.setGridLayoutInfo(
+            gridLayout.verticalItemSpacingPx,
+            gridLayout.verticalContentPaddingPx,
+            gridLayout.viewportHeightPx,
+            gridLayout.maxItemSpan,
+            gridLayout.minItemSpan,
+            gridLayout.currentRow,
+            gridLayout.currentSpan,
+        )
+        runCurrent()
+    }
+
+    private fun DraggableAnchors<Int>.toList() = buildList {
+        for (index in 0 until this@toList.size) {
+            add(anchorAt(index) to positionAt(index))
+        }
+    }
+
+    private fun runTestWithSnapshots(testBody: suspend TestScope.() -> Unit) {
+        val globalWriteObserverHandle =
+            Snapshot.registerGlobalWriteObserver {
+                // This is normally done by the compose runtime.
+                Snapshot.sendApplyNotifications()
+            }
+
+        try {
+            testScope.runTest(testBody = testBody)
+        } finally {
+            globalWriteObserverHandle.dispose()
+        }
+    }
+
+    private data class GridLayout(
+        val verticalItemSpacingPx: Float,
+        val verticalContentPaddingPx: Float,
+        val viewportHeightPx: Int,
+        val maxItemSpan: Int,
+        val minItemSpan: Int,
+        val currentRow: Int,
+        val currentSpan: Int,
+    )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
index d7fe263..dd83702 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
@@ -71,7 +71,7 @@
     @Mock private lateinit var accessibilityManagerWrapper: AccessibilityManagerWrapper
     @get:Rule val mockitoRule = MockitoJUnit.rule()
     private var toastContent = ""
-    private val timeoutMillis = 3500L
+    private val timeoutMillis = 5000L
 
     @Before
     fun setUp() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
index fb1bf28..6397979 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
@@ -303,7 +303,8 @@
                 // Top edge is not applicable in dual shade, as well as two-finger swipe.
                 assertThat(downDestination).isNull()
             } else {
-                assertThat(downDestination).isEqualTo(ShowOverlay(Overlays.NotificationsShade))
+                assertThat(downDestination)
+                    .isEqualTo(ShowOverlay(Overlays.NotificationsShade, isIrreversible = true))
                 assertThat(downDestination?.transitionKey).isNull()
             }
 
@@ -320,7 +321,7 @@
                 downWithTwoPointers -> assertThat(downFromTopRightDestination).isNull()
                 else -> {
                     assertThat(downFromTopRightDestination)
-                        .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade))
+                        .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade, isIrreversible = true))
                     assertThat(downFromTopRightDestination?.transitionKey).isNull()
                 }
             }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
index 5c47f55..47fae9f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
@@ -106,7 +106,7 @@
             runCurrent()
 
             assertThat(userActions?.get(swipeDownFromTopWithTwoFingers()))
-                .isEqualTo(UserActionResult(Scenes.QuickSettings))
+                .isEqualTo(UserActionResult(Scenes.QuickSettings, isIrreversible = true))
         }
 
     @Test
@@ -118,7 +118,7 @@
             runCurrent()
 
             assertThat(userActions?.get(swipeDownFromTopWithTwoFingers()))
-                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilterTest.kt
deleted file mode 100644
index efde1ec..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilterTest.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.scene.ui.viewmodel
-
-import android.graphics.Region
-import android.view.setSystemGestureExclusionRegion
-import androidx.compose.ui.geometry.Offset
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.lifecycle.activateIn
-import com.android.systemui.scene.sceneContainerGestureFilterFactory
-import com.android.systemui.settings.displayTracker
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class SceneContainerGestureFilterTest : SysuiTestCase() {
-
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-    private val displayId = kosmos.displayTracker.defaultDisplayId
-
-    private val underTest = kosmos.sceneContainerGestureFilterFactory.create(displayId)
-    private val activationJob = Job()
-
-    @Test
-    fun shouldFilterGesture_whenNoRegion_returnsFalse() =
-        testScope.runTest {
-            activate()
-            setSystemGestureExclusionRegion(displayId, null)
-            runCurrent()
-
-            assertThat(underTest.shouldFilterGesture(Offset(100f, 100f))).isFalse()
-        }
-
-    @Test
-    fun shouldFilterGesture_whenOutsideRegion_returnsFalse() =
-        testScope.runTest {
-            activate()
-            setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
-            runCurrent()
-
-            assertThat(underTest.shouldFilterGesture(Offset(300f, 100f))).isFalse()
-        }
-
-    @Test
-    fun shouldFilterGesture_whenInsideRegion_returnsTrue() =
-        testScope.runTest {
-            activate()
-            setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
-            runCurrent()
-
-            assertThat(underTest.shouldFilterGesture(Offset(100f, 100f))).isTrue()
-        }
-
-    @Test(expected = IllegalStateException::class)
-    fun shouldFilterGesture_beforeActivation_throws() =
-        testScope.runTest {
-            setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
-            runCurrent()
-
-            underTest.shouldFilterGesture(Offset(100f, 100f))
-        }
-
-    @Test(expected = IllegalStateException::class)
-    fun shouldFilterGesture_afterCancellation_throws() =
-        testScope.runTest {
-            activate()
-            setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
-            runCurrent()
-
-            cancel()
-
-            underTest.shouldFilterGesture(Offset(100f, 100f))
-        }
-
-    private fun TestScope.activate() {
-        underTest.activateIn(testScope, activationJob)
-        runCurrent()
-    }
-
-    private fun TestScope.cancel() {
-        activationJob.cancel()
-        runCurrent()
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index a37f511..b632a8a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -39,7 +39,6 @@
 import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
-import com.android.systemui.settings.displayTracker
 import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.shared.flag.DualShade
@@ -82,7 +81,6 @@
         underTest =
             kosmos.sceneContainerViewModelFactory.create(
                 view,
-                kosmos.displayTracker.defaultDisplayId,
                 { motionEventHandler ->
                     this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler
                 },
@@ -178,8 +176,8 @@
             sceneContainerConfig.sceneKeys
                 .filter { it != currentScene }
                 .filter {
-                    // Moving to the Communal scene is not currently falsing protected.
-                    it != Scenes.Communal
+                    // Moving to the Communal and Dream scene is not currently falsing protected.
+                    it != Scenes.Communal && it != Scenes.Dream
                 }
                 .forEach { toScene ->
                     assertWithMessage("Protected scene $toScene not properly protected")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
index 15d6881..fcb366b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.EnableSceneContainer
@@ -50,6 +51,7 @@
 import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.TestScope
@@ -248,6 +250,27 @@
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Communal))
         }
 
+    @Test
+    fun upTransitionSceneKey_neverGoesBackToShadeScene() =
+        testScope.runTest {
+            val actions by collectValues(underTest.actions)
+            val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+
+            kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "")
+            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+
+            actions.forEachIndexed { index, map ->
+                assertWithMessage(
+                        "Actions on index $index is incorrectly mapping back to the Shade scene!"
+                    )
+                    .that((map[Swipe.Up] as? UserActionResult.ChangeScene)?.toScene)
+                    .isNotEqualTo(Scenes.Shade)
+            }
+        }
+
     private fun TestScope.setDeviceEntered(isEntered: Boolean) {
         if (isEntered) {
             // Unlock the device marking the device has entered.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index beba162..ea5c29e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -20,8 +20,6 @@
 
 import static junit.framework.Assert.assertFalse;
 
-import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -31,12 +29,16 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+
+import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.compose.animation.scene.ObservableTransitionState;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.communal.shared.model.CommunalScenes;
 import com.android.systemui.dump.DumpManager;
@@ -67,9 +69,6 @@
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.FakeSystemClock;
 
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.test.TestScope;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -79,6 +78,9 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.verification.VerificationMode;
 
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.test.TestScope;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
@@ -517,6 +519,32 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION)
+    public void testNotLockscreenInGoneTransition_invalidationCalled() {
+        // GIVEN visual stability is being maintained b/c animation is playing
+        mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
+                mTestScope, new TransitionStep(
+                        KeyguardState.LOCKSCREEN,
+                        KeyguardState.GONE,
+                        1f,
+                        TransitionState.RUNNING),  /* validateStep = */ false);
+        mTestScope.getTestScheduler().runCurrent();
+        assertFalse(mNotifStabilityManager.isPipelineRunAllowed());
+
+        // WHEN the animation has stopped playing
+        mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
+                mTestScope, new TransitionStep(
+                        KeyguardState.LOCKSCREEN,
+                        KeyguardState.GONE,
+                        1f,
+                        TransitionState.FINISHED),  /* validateStep = */ false);
+        mTestScope.getTestScheduler().runCurrent();
+
+        // invalidate is called, b/c we were previously suppressing the pipeline from running
+        verifyStabilityManagerWasInvalidated(times(1));
+    }
+
+    @Test
     public void testNeverSuppressPipelineRunFromPanelCollapse_noInvalidationCalled() {
         // GIVEN animation is playing
         setPanelCollapsing(true);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt
deleted file mode 100644
index a310ef4..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.statusbar.notification.row.ui.viewmodel
-
-import android.platform.test.annotations.EnableFlags
-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.kosmos.testScope
-import com.android.systemui.statusbar.notification.row.data.repository.fakeNotificationRowRepository
-import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
-import com.android.systemui.statusbar.notification.row.shared.IconModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-@EnableFlags(RichOngoingNotificationFlag.FLAG_NAME)
-class EnRouteViewModelTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-    private val repository = kosmos.fakeNotificationRowRepository
-
-    private var contentModel: EnRouteContentModel?
-        get() = repository.richOngoingContentModel.value as? EnRouteContentModel
-        set(value) {
-            repository.richOngoingContentModel.value = value
-        }
-
-    private lateinit var underTest: EnRouteViewModel
-
-    @Before
-    fun setup() {
-        underTest = kosmos.getEnRouteViewModel(repository)
-    }
-
-    @Test
-    fun viewModelShowsContent() =
-        testScope.runTest {
-            val title by collectLastValue(underTest.title)
-            val text by collectLastValue(underTest.text)
-            contentModel =
-                exampleEnRouteContent(
-                    title = "Example EnRoute Title",
-                    text = "Example EnRoute Text",
-                )
-            assertThat(title).isEqualTo("Example EnRoute Title")
-            assertThat(text).isEqualTo("Example EnRoute Text")
-        }
-
-    private fun exampleEnRouteContent(
-        icon: IconModel = mock(),
-        title: CharSequence = "example text",
-        text: CharSequence = "example title",
-    ) =
-        EnRouteContentModel(
-            smallIcon = icon,
-            title = title,
-            text = text,
-        )
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt
deleted file mode 100644
index 61873ad..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.statusbar.notification.row.ui.viewmodel
-
-import android.app.Notification
-import android.app.PendingIntent
-import android.platform.test.annotations.EnableFlags
-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.kosmos.testScope
-import com.android.systemui.statusbar.notification.row.data.repository.fakeNotificationRowRepository
-import com.android.systemui.statusbar.notification.row.shared.IconModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel.TimerState.Paused
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import java.time.Duration
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-@EnableFlags(RichOngoingNotificationFlag.FLAG_NAME)
-class TimerViewModelTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-    private val repository = kosmos.fakeNotificationRowRepository
-
-    private var contentModel: TimerContentModel?
-        get() = repository.richOngoingContentModel.value as? TimerContentModel
-        set(value) {
-            repository.richOngoingContentModel.value = value
-        }
-
-    private lateinit var underTest: TimerViewModel
-
-    @Before
-    fun setup() {
-        underTest = kosmos.getTimerViewModel(repository)
-    }
-
-    @Test
-    fun labelShowsTheTimerName() =
-        testScope.runTest {
-            val label by collectLastValue(underTest.label)
-            contentModel = pausedTimer(name = "Example Timer Name")
-            assertThat(label).isEqualTo("Example Timer Name")
-        }
-
-    @Test
-    fun pausedTimeRemainingFormatsWell() =
-        testScope.runTest {
-            val label by collectLastValue(underTest.pausedTime)
-            contentModel = pausedTimer(timeRemaining = Duration.ofMinutes(3))
-            assertThat(label).isEqualTo("3:00")
-            contentModel = pausedTimer(timeRemaining = Duration.ofSeconds(119))
-            assertThat(label).isEqualTo("1:59")
-            contentModel = pausedTimer(timeRemaining = Duration.ofSeconds(121))
-            assertThat(label).isEqualTo("2:01")
-            contentModel = pausedTimer(timeRemaining = Duration.ofHours(1))
-            assertThat(label).isEqualTo("1:00:00")
-            contentModel = pausedTimer(timeRemaining = Duration.ofHours(24))
-            assertThat(label).isEqualTo("24:00:00")
-        }
-
-    private fun pausedTimer(
-        icon: IconModel = mock(),
-        name: String = "example",
-        timeRemaining: Duration = Duration.ofMinutes(3),
-        resumeIntent: PendingIntent? = null,
-        addMinuteAction: Notification.Action? = null,
-        resetAction: Notification.Action? = null
-    ) =
-        TimerContentModel(
-            icon = icon,
-            name = name,
-            state =
-                Paused(
-                    timeRemaining = timeRemaining,
-                    resumeIntent = resumeIntent,
-                    addMinuteAction = addMinuteAction,
-                    resetAction = resetAction,
-                )
-        )
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index f40bfbd..8d678ef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -506,7 +506,7 @@
     @EnableSceneContainer
     fun pinnedHeadsUpRows_filtersForPinnedItems() =
         testScope.runTest {
-            val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+            val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
 
             // WHEN there are no pinned rows
             val rows =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index a0f6431..9d93a9c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -61,7 +61,7 @@
             interactor,
             kosmos.testDispatcher,
             mockDialogDelegate,
-            mockDialogEventLogger
+            mockDialogEventLogger,
         )
 
     @Test
@@ -97,7 +97,7 @@
             assertThat(tiles?.size).isEqualTo(3)
             with(tiles?.elementAt(0)!!) {
                 assertThat(this.text).isEqualTo("Disabled by other")
-                assertThat(this.subtext).isEqualTo("Set up")
+                assertThat(this.subtext).isEqualTo("Not set")
                 assertThat(this.enabled).isEqualTo(false)
             }
             with(tiles?.elementAt(1)!!) {
@@ -323,10 +323,10 @@
             assertThat(tiles!!).hasSize(6)
             assertThat(tiles!![0].subtext).isEqualTo("When the going gets tough")
             assertThat(tiles!![1].subtext).isEqualTo("On • When in Rome")
-            assertThat(tiles!![2].subtext).isEqualTo("Set up")
+            assertThat(tiles!![2].subtext).isEqualTo("Not set")
             assertThat(tiles!![3].subtext).isEqualTo("Off")
             assertThat(tiles!![4].subtext).isEqualTo("On")
-            assertThat(tiles!![5].subtext).isEqualTo("Set up")
+            assertThat(tiles!![5].subtext).isEqualTo("Not set")
         }
 
     @Test
@@ -387,7 +387,7 @@
             }
             with(tiles?.elementAt(2)!!) {
                 assertThat(this.stateDescription).isEqualTo("Off")
-                assertThat(this.subtextDescription).isEqualTo("Set up")
+                assertThat(this.subtextDescription).isEqualTo("Not set")
             }
             with(tiles?.elementAt(3)!!) {
                 assertThat(this.stateDescription).isEqualTo("Off")
@@ -399,7 +399,7 @@
             }
             with(tiles?.elementAt(5)!!) {
                 assertThat(this.stateDescription).isEqualTo("Off")
-                assertThat(this.subtextDescription).isEqualTo("Set up")
+                assertThat(this.subtextDescription).isEqualTo("Not set")
             }
 
             // All tiles have the same long click info
@@ -451,7 +451,7 @@
                         .setName("Active without manual")
                         .setActive(true)
                         .setManualInvocationAllowed(false)
-                        .build(),
+                        .build()
                 )
             )
             runCurrent()
@@ -492,7 +492,7 @@
                         .setId("ID")
                         .setName("Disabled by other")
                         .setEnabled(false, /* byUser= */ false)
-                        .build(),
+                        .build()
                 )
             )
             runCurrent()
@@ -500,7 +500,7 @@
             assertThat(tiles?.size).isEqualTo(1)
             with(tiles?.elementAt(0)!!) {
                 assertThat(this.text).isEqualTo("Disabled by other")
-                assertThat(this.subtext).isEqualTo("Set up")
+                assertThat(this.subtext).isEqualTo("Not set")
                 assertThat(this.enabled).isEqualTo(false)
 
                 // Click the tile
@@ -519,7 +519,7 @@
             // Check that nothing happened to the tile
             with(tiles?.elementAt(0)!!) {
                 assertThat(this.text).isEqualTo("Disabled by other")
-                assertThat(this.subtext).isEqualTo("Set up")
+                assertThat(this.subtext).isEqualTo("Not set")
                 assertThat(this.enabled).isEqualTo(false)
             }
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt
new file mode 100644
index 0000000..7ce421a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.interactor
+
+import android.app.ActivityManager
+import android.testing.TestableLooper
+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.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.fakeVolumeDialogController
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.days
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private val dialogTimeoutDuration = 3.seconds
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper()
+class VolumeDialogVisibilityInteractorTest : SysuiTestCase() {
+
+    private val kosmos: Kosmos = testKosmos()
+
+    private lateinit var underTest: VolumeDialogVisibilityInteractor
+
+    @Before
+    fun setUp() {
+        underTest = kosmos.volumeDialogVisibilityInteractor
+    }
+
+    @Test
+    fun testShowRequest_visible() =
+        with(kosmos) {
+            testScope.runTest {
+                runCurrent()
+                val visibilityModel by collectLastValue(underTest.dialogVisibility)
+                fakeVolumeDialogController.onShowRequested(
+                    Events.SHOW_REASON_VOLUME_CHANGED,
+                    false,
+                    ActivityManager.LOCK_TASK_MODE_LOCKED,
+                )
+                runCurrent()
+
+                assertThat(visibilityModel!!)
+                    .isEqualTo(
+                        VolumeDialogVisibilityModel.Visible(
+                            Events.SHOW_REASON_VOLUME_CHANGED,
+                            false,
+                            ActivityManager.LOCK_TASK_MODE_LOCKED,
+                        )
+                    )
+            }
+        }
+
+    @Test
+    fun testDismissRequest_dismissed() =
+        with(kosmos) {
+            testScope.runTest {
+                runCurrent()
+                val visibilityModel by collectLastValue(underTest.dialogVisibility)
+                fakeVolumeDialogController.onShowRequested(
+                    Events.SHOW_REASON_VOLUME_CHANGED,
+                    false,
+                    ActivityManager.LOCK_TASK_MODE_LOCKED,
+                )
+                runCurrent()
+
+                fakeVolumeDialogController.onDismissRequested(Events.DISMISS_REASON_SCREEN_OFF)
+
+                assertThat(visibilityModel!!)
+                    .isEqualTo(
+                        VolumeDialogVisibilityModel.Dismissed(Events.DISMISS_REASON_SCREEN_OFF)
+                    )
+            }
+        }
+
+    @Test
+    fun testTimeout_dismissed() =
+        with(kosmos) {
+            testScope.runTest {
+                runCurrent()
+                underTest.resetDismissTimeout()
+                val visibilityModel by collectLastValue(underTest.dialogVisibility)
+                fakeVolumeDialogController.onShowRequested(
+                    Events.SHOW_REASON_VOLUME_CHANGED,
+                    false,
+                    ActivityManager.LOCK_TASK_MODE_LOCKED,
+                )
+                runCurrent()
+
+                advanceTimeBy(1.days)
+
+                assertThat(visibilityModel!!)
+                    .isEqualTo(VolumeDialogVisibilityModel.Dismissed(Events.DISMISS_REASON_TIMEOUT))
+            }
+        }
+
+    @Test
+    fun testResetTimeoutInterruptsEvents() =
+        with(kosmos) {
+            testScope.runTest {
+                runCurrent()
+                underTest.resetDismissTimeout()
+                val visibilityModel by collectLastValue(underTest.dialogVisibility)
+                fakeVolumeDialogController.onShowRequested(
+                    Events.SHOW_REASON_VOLUME_CHANGED,
+                    false,
+                    ActivityManager.LOCK_TASK_MODE_LOCKED,
+                )
+                runCurrent()
+
+                advanceTimeBy(dialogTimeoutDuration / 2)
+                underTest.resetDismissTimeout()
+                advanceTimeBy(dialogTimeoutDuration / 2)
+                underTest.resetDismissTimeout()
+                advanceTimeBy(dialogTimeoutDuration / 2)
+
+                assertThat(visibilityModel)
+                    .isInstanceOf(VolumeDialogVisibilityModel.Visible::class.java)
+            }
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
index e1be6b0..d2688a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
@@ -53,7 +53,7 @@
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.kotlin.JavaAdapter
 import com.android.wm.shell.desktopmode.DesktopMode
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
 import com.android.wm.shell.onehanded.OneHanded
 import com.android.wm.shell.onehanded.OneHandedEventCallback
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback
diff --git a/packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml
new file mode 100644
index 0000000..8a77d88
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2024, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This is the screen that shows the 9 circle unlock widget and instructs
+     the user how to unlock their device, or make an emergency call.  This
+     is the landscape layout.  -->
+<com.android.keyguard.KeyguardPatternView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/keyguard_pattern_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_horizontal|bottom"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:orientation="horizontal">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="2"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layoutDirection="ltr"
+        android:orientation="vertical">
+
+        <include layout="@layout/keyguard_bouncer_message_area"/>
+
+        <com.android.systemui.bouncer.ui.BouncerMessageView
+            android:id="@+id/bouncer_message_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            androidprv:layout_constraintBottom_toTopOf="@+id/lockPatternView"
+            androidprv:layout_constraintTop_toTopOf="parent"
+            androidprv:layout_constraintVertical_chainStyle="packed" />
+
+        <include
+            android:id="@+id/keyguard_selector_fade_container"
+            layout="@layout/keyguard_eca"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom|center_horizontal"
+            android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+            android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+            android:gravity="center_horizontal"
+            android:orientation="vertical"
+            androidprv:layout_constraintBottom_toBottomOf="parent" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/pattern_container"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="3"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layoutDirection="ltr"
+        android:orientation="vertical">
+
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/pattern_top_guideline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            androidprv:layout_constraintGuide_percent="0" />
+
+        <com.android.internal.widget.LockPatternView
+            android:id="@+id/lockPatternView"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:orientation="horizontal"
+            androidprv:layout_constraintDimensionRatio="1.0"
+            androidprv:layout_constraintVertical_bias="1.0"
+            androidprv:layout_constraintLeft_toLeftOf="parent"
+            androidprv:layout_constraintRight_toRightOf="parent"
+            androidprv:layout_constraintBottom_toBottomOf="parent"
+            androidprv:layout_constraintTop_toBottomOf="@id/pattern_top_guideline"/>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.keyguard.KeyguardPatternView>
diff --git a/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_pin_view.xml
new file mode 100644
index 0000000..4b8b63f
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_pin_view.xml
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2024, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<!-- This is the SIM PIN view that allows the user to enter a SIM PIN to unlock the device. -->
+<com.android.keyguard.KeyguardSimPinView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/keyguard_sim_pin_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_horizontal|bottom"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:orientation="horizontal">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="2"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layoutDirection="ltr"
+        android:orientation="vertical">
+
+        <include layout="@layout/keyguard_bouncer_message_area"/>
+
+        <ImageView
+            android:id="@+id/keyguard_sim"
+            android:layout_width="40dp"
+            android:layout_height="40dp"
+            android:layout_marginBottom="3dp"
+            android:src="@drawable/ic_lockscreen_sim"
+            androidprv:layout_constraintBottom_toTopOf="@+id/pin_entry_area"
+            androidprv:layout_constraintLeft_toLeftOf="parent"
+            androidprv:layout_constraintRight_toRightOf="parent"
+            app:tint="@color/background_protected"/>
+
+        <include
+            android:id="@+id/keyguard_esim_area"
+            layout="@layout/keyguard_esim_area"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            androidprv:layout_constraintLeft_toLeftOf="parent"
+            androidprv:layout_constraintRight_toRightOf="parent"
+            androidprv:layout_constraintBottom_toTopOf="@+id/pin_entry_area"/>
+
+        <com.android.keyguard.AlphaOptimizedRelativeLayout
+            android:id="@+id/pin_entry_area"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container">
+
+            <com.android.keyguard.PasswordTextView
+                android:id="@+id/simPinEntry"
+                style="@style/Widget.TextView.Password"
+                android:layout_width="@dimen/keyguard_security_width"
+                android:layout_height="@dimen/keyguard_password_height"
+                android:layout_centerHorizontal="true"
+                android:layout_marginRight="72dp"
+                android:contentDescription="@string/keyguard_accessibility_sim_pin_area"
+                androidprv:scaledTextSize="@integer/scaled_password_text_size" />
+        </com.android.keyguard.AlphaOptimizedRelativeLayout>
+
+        <include
+            android:id="@+id/keyguard_selector_fade_container"
+            layout="@layout/keyguard_eca"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom|center_horizontal"
+            android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+            android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+            android:gravity="center_horizontal"
+            android:orientation="vertical"
+            androidprv:layout_constraintBottom_toBottomOf="parent"/>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/sim_pin_container"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="3"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layoutDirection="ltr"
+        android:orientation="vertical">
+
+        <androidx.constraintlayout.helper.widget.Flow
+            android:id="@+id/flow1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:orientation="horizontal"
+            androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+            androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+            androidprv:flow_horizontalStyle="packed"
+            androidprv:flow_maxElementsWrap="3"
+            androidprv:flow_verticalBias="0.5"
+            androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:flow_verticalStyle="packed"
+            androidprv:flow_wrapMode="aligned"
+            androidprv:layout_constraintLeft_toLeftOf="parent"
+            androidprv:layout_constraintRight_toRightOf="parent"
+            androidprv:layout_constraintTop_toTopOf="parent"
+            androidprv:layout_constraintBottom_toBottomOf="parent"/>
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/delete_button"
+            style="@style/NumPadKey.Delete"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key0"
+            android:contentDescription="@string/keyboardview_keycode_delete" />
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/key_enter"
+            style="@style/NumPadKey.Enter"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:contentDescription="@string/keyboardview_keycode_enter" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key2"
+            androidprv:digit="1"
+            androidprv:textView="@+id/simPinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key2"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key3"
+            androidprv:digit="2"
+            androidprv:textView="@+id/simPinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key3"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key4"
+            androidprv:digit="3"
+            androidprv:textView="@+id/simPinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key4"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key5"
+            androidprv:digit="4"
+            androidprv:textView="@+id/simPinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key5"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key6"
+            androidprv:digit="5"
+            androidprv:textView="@+id/simPinEntry" />
+
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key6"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key7"
+            androidprv:digit="6"
+            androidprv:textView="@+id/simPinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key7"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key8"
+            androidprv:digit="7"
+            androidprv:textView="@+id/simPinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key8"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key9"
+            androidprv:digit="8"
+            androidprv:textView="@+id/simPinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key9"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/delete_button"
+            androidprv:digit="9"
+            androidprv:textView="@+id/simPinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key0"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key_enter"
+            androidprv:digit="0"
+            androidprv:textView="@+id/simPinEntry" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.keyguard.KeyguardSimPinView>
diff --git a/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_puk_view.xml
new file mode 100644
index 0000000..9012856
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-land/keyguard_sim_puk_view.xml
@@ -0,0 +1,223 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2024, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<!-- This is the SIM PUK view that allows the user to recover their device by entering the
+    carrier-provided PUK code and entering a new SIM PIN for it. -->
+<com.android.keyguard.KeyguardSimPukView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/keyguard_sim_puk_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_horizontal|bottom"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:orientation="horizontal">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="2"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layoutDirection="ltr"
+        android:orientation="vertical">
+
+        <include layout="@layout/keyguard_bouncer_message_area"/>
+
+        <ImageView
+            android:id="@+id/keyguard_sim"
+            android:layout_width="40dp"
+            android:layout_height="40dp"
+            android:layout_marginBottom="3dp"
+            android:src="@drawable/ic_lockscreen_sim"
+            androidprv:layout_constraintBottom_toTopOf="@+id/pin_entry_area"
+            androidprv:layout_constraintLeft_toLeftOf="parent"
+            androidprv:layout_constraintRight_toRightOf="parent"
+            app:tint="@color/background_protected"/>
+
+        <include
+            android:id="@+id/keyguard_esim_area"
+            layout="@layout/keyguard_esim_area"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            androidprv:layout_constraintLeft_toLeftOf="parent"
+            androidprv:layout_constraintRight_toRightOf="parent"
+            androidprv:layout_constraintBottom_toTopOf="@+id/pin_entry_area"/>
+
+        <com.android.keyguard.AlphaOptimizedRelativeLayout
+            android:id="@+id/pin_entry_area"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container">
+
+            <com.android.keyguard.PasswordTextView
+                android:id="@+id/pukEntry"
+                style="@style/Widget.TextView.Password"
+                android:layout_width="@dimen/keyguard_security_width"
+                android:layout_height="@dimen/keyguard_password_height"
+                android:layout_centerHorizontal="true"
+                android:layout_marginRight="72dp"
+                android:contentDescription="@string/keyguard_accessibility_sim_pin_area"
+                androidprv:scaledTextSize="@integer/scaled_password_text_size" />
+        </com.android.keyguard.AlphaOptimizedRelativeLayout>
+
+        <include
+            android:id="@+id/keyguard_selector_fade_container"
+            layout="@layout/keyguard_eca"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom|center_horizontal"
+            android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+            android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+            android:gravity="center_horizontal"
+            android:orientation="vertical"
+            androidprv:layout_constraintBottom_toBottomOf="parent"/>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/sim_puk_container"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="3"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layoutDirection="ltr"
+        android:orientation="vertical">
+
+        <androidx.constraintlayout.helper.widget.Flow
+            android:id="@+id/flow1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:orientation="horizontal"
+            androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+            androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+            androidprv:flow_horizontalStyle="packed"
+            androidprv:flow_maxElementsWrap="3"
+            androidprv:flow_verticalBias="0.5"
+            androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:flow_verticalStyle="packed"
+            androidprv:flow_wrapMode="aligned"
+            androidprv:layout_constraintLeft_toLeftOf="parent"
+            androidprv:layout_constraintRight_toRightOf="parent"
+            androidprv:layout_constraintTop_toTopOf="parent"
+            androidprv:layout_constraintBottom_toBottomOf="parent"/>
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/delete_button"
+            style="@style/NumPadKey.Delete"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key0"
+            android:contentDescription="@string/keyboardview_keycode_delete" />
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/key_enter"
+            style="@style/NumPadKey.Enter"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:contentDescription="@string/keyboardview_keycode_enter" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key2"
+            androidprv:digit="1"
+            androidprv:textView="@+id/pukEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key2"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key3"
+            androidprv:digit="2"
+            androidprv:textView="@+id/pukEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key3"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key4"
+            androidprv:digit="3"
+            androidprv:textView="@+id/pukEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key4"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key5"
+            androidprv:digit="4"
+            androidprv:textView="@+id/pukEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key5"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key6"
+            androidprv:digit="5"
+            androidprv:textView="@+id/pukEntry" />
+
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key6"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key7"
+            androidprv:digit="6"
+            androidprv:textView="@+id/pukEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key7"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key8"
+            androidprv:digit="7"
+            androidprv:textView="@+id/pukEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key8"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key9"
+            androidprv:digit="8"
+            androidprv:textView="@+id/pukEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key9"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/delete_button"
+            androidprv:digit="9"
+            androidprv:textView="@+id/pukEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key0"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key_enter"
+            androidprv:digit="0"
+            androidprv:textView="@+id/pukEntry" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.keyguard.KeyguardSimPukView>
diff --git a/packages/SystemUI/res/drawable/touchpad_tutorial_home_icon.xml b/packages/SystemUI/res/drawable/touchpad_tutorial_home_icon.xml
new file mode 100644
index 0000000..9f3d075
--- /dev/null
+++ b/packages/SystemUI/res/drawable/touchpad_tutorial_home_icon.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M240,760L360,760L360,520L600,520L600,760L720,760L720,400L480,220L240,400L240,760ZM160,840L160,360L480,120L800,360L800,840L520,840L520,600L440,600L440,840L160,840ZM480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/touchpad_tutorial_recents_icon.xml b/packages/SystemUI/res/drawable/touchpad_tutorial_recents_icon.xml
new file mode 100644
index 0000000..113908a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/touchpad_tutorial_recents_icon.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:autoMirrored="true"
+    android:tint="?attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M120,800Q87,800 63.5,776.5Q40,753 40,720L40,240Q40,207 63.5,183.5Q87,160 120,160L200,160Q233,160 256.5,183.5Q280,207 280,240L280,720Q280,753 256.5,776.5Q233,800 200,800L120,800ZM120,721L200,721Q200,721 200,721Q200,721 200,721L200,239Q200,239 200,239Q200,239 200,239L120,239Q120,239 120,239Q120,239 120,239L120,721Q120,721 120,721Q120,721 120,721ZM440,800Q407,800 383.5,776.5Q360,753 360,720L360,240Q360,207 383.5,183.5Q407,160 440,160L840,160Q873,160 896.5,183.5Q920,207 920,240L920,720Q920,753 896.5,776.5Q873,800 840,800L440,800ZM440,721L840,721Q840,721 840,721Q840,721 840,721L840,239Q840,239 840,239Q840,239 840,239L440,239Q440,239 440,239Q440,239 440,239L440,721Q440,721 440,721Q440,721 440,721ZM200,721Q200,721 200,721Q200,721 200,721L200,239Q200,239 200,239Q200,239 200,239L200,239Q200,239 200,239Q200,239 200,239L200,721Q200,721 200,721Q200,721 200,721ZM440,721Q440,721 440,721Q440,721 440,721L440,239Q440,239 440,239Q440,239 440,239L440,239Q440,239 440,239Q440,239 440,239L440,721Q440,721 440,721Q440,721 440,721Z" />
+</vector>
diff --git a/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml b/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml
deleted file mode 100644
index e7a40d1..0000000
--- a/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ Copyright (C) 2024 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@*android:id/status_bar_latest_event_content"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:layout_weight="1"
-    android:minHeight="@*android:dimen/notification_headerless_min_height"
-    android:tag="enroute"
-    >
-
-    <include layout="@*android:layout/notification_template_material_base" />
-
-</com.android.systemui.statusbar.notification.row.ui.view.EnRouteView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml b/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml
deleted file mode 100644
index ca6d66a..0000000
--- a/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml
+++ /dev/null
@@ -1,86 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ Copyright (C) 2014 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@*android:id/status_bar_latest_event_content"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical"
-    android:clipChildren="false"
-    android:tag="big"
-    >
-
-    <LinearLayout
-        android:id="@*android:id/notification_action_list_margin_target"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="@*android:dimen/notification_content_margin"
-        android:orientation="vertical"
-        >
-
-        <FrameLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:layout_gravity="top"
-            >
-
-            <include layout="@*android:layout/notification_template_header" />
-
-            <LinearLayout
-                android:id="@*android:id/notification_main_column"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginStart="@*android:dimen/notification_content_margin_start"
-                android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
-                android:layout_marginTop="@*android:dimen/notification_content_margin_top"
-                android:orientation="vertical"
-                >
-
-                <include layout="@*android:layout/notification_template_part_line1" />
-
-                <include layout="@*android:layout/notification_template_text_multiline" />
-
-                <include
-                    android:layout_width="match_parent"
-                    android:layout_height="@*android:dimen/notification_progress_bar_height"
-                    android:layout_marginTop="@*android:dimen/notification_progress_margin_top"
-                    layout="@*android:layout/notification_template_progress"
-                    />
-            </LinearLayout>
-
-            <include layout="@*android:layout/notification_template_right_icon" />
-        </FrameLayout>
-
-        <ViewStub
-            android:layout="@*android:layout/notification_material_reply_text"
-            android:id="@*android:id/notification_material_reply_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            />
-
-        <include
-            layout="@*android:layout/notification_template_smart_reply_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@*android:dimen/notification_content_margin_start"
-            android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
-            android:layout_marginTop="@*android:dimen/notification_content_margin"
-            />
-
-        <include layout="@*android:layout/notification_material_action_list" />
-    </LinearLayout>
-</com.android.systemui.statusbar.notification.row.ui.view.EnRouteView>
diff --git a/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml b/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml
deleted file mode 100644
index 3a679e3..0000000
--- a/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml
+++ /dev/null
@@ -1,122 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2024 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<com.android.systemui.statusbar.notification.row.ui.view.TimerView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/topBaseline"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        app:layout_constraintGuide_begin="22sp"
-        />
-
-    <ImageView
-        android:id="@+id/icon"
-        android:layout_width="24dp"
-        android:layout_height="24dp"
-        app:tint="@android:color/white"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toStartOf="@id/label"
-        android:baseline="18dp"
-        app:layout_constraintBaseline_toTopOf="@id/topBaseline"
-        />
-    <TextView
-        android:id="@+id/label"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        app:layout_constraintStart_toEndOf="@id/icon"
-        app:layout_constraintEnd_toStartOf="@id/chronoRemaining"
-        android:singleLine="true"
-        tools:text="15s Timer"
-        app:layout_constraintBaseline_toTopOf="@id/topBaseline"
-        android:paddingEnd="4dp"
-        />
-    <Chronometer
-        android:id="@+id/chronoRemaining"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:singleLine="true"
-        android:textSize="20sp"
-        android:gravity="end"
-        tools:text="0:12"
-        app:layout_constraintBaseline_toTopOf="@id/topBaseline"
-        app:layout_constraintEnd_toStartOf="@id/pausedTimeRemaining"
-        app:layout_constraintStart_toEndOf="@id/label"
-        android:countDown="true"
-        android:paddingEnd="4dp"
-        />
-    <TextView
-        android:id="@+id/pausedTimeRemaining"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:singleLine="true"
-        android:textSize="20sp"
-        android:gravity="end"
-        tools:text="0:12"
-        app:layout_constraintBaseline_toTopOf="@id/topBaseline"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toEndOf="@id/chronoRemaining"
-        android:paddingEnd="4dp"
-        />
-
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/bottomOfTop"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        app:barrierDirection="bottom"
-        app:constraint_referenced_ids="icon,label,chronoRemaining,pausedTimeRemaining"
-        />
-
-    <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
-        style="@*android:style/NotificationEmphasizedAction"
-        android:id="@+id/mainButton"
-        android:layout_width="124dp"
-        android:layout_height="wrap_content"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toStartOf="@id/altButton"
-        app:layout_constraintTop_toBottomOf="@id/bottomOfTop"
-        app:layout_constraintHorizontal_chainStyle="spread"
-        android:paddingEnd="4dp"
-        />
-
-    <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
-        style="@*android:style/NotificationEmphasizedAction"
-        android:id="@+id/altButton"
-        android:layout_width="124dp"
-        android:layout_height="wrap_content"
-        app:layout_constraintTop_toBottomOf="@id/bottomOfTop"
-        app:layout_constraintStart_toEndOf="@id/mainButton"
-        app:layout_constraintEnd_toEndOf="@id/resetButton"
-        android:paddingEnd="4dp"
-        />
-
-    <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
-        style="@*android:style/NotificationEmphasizedAction"
-        android:id="@+id/resetButton"
-        android:layout_width="124dp"
-        android:layout_height="wrap_content"
-        app:layout_constraintTop_toBottomOf="@id/bottomOfTop"
-        app:layout_constraintStart_toEndOf="@id/altButton"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:paddingEnd="4dp"
-        />
-</com.android.systemui.statusbar.notification.row.ui.view.TimerView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c76b35f..96a85d7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1120,7 +1120,7 @@
     <string name="zen_mode_off">Off</string>
 
     <!-- Modes: label for a mode that needs to be set up [CHAR LIMIT=35] -->
-    <string name="zen_mode_set_up">Set up</string>
+    <string name="zen_mode_set_up">Not set</string>
 
     <!-- Modes: label for a mode that cannot be manually turned on [CHAR LIMIT=35] -->
     <string name="zen_mode_no_manual_invocation">Manage in settings</string>
@@ -1827,6 +1827,12 @@
     <!-- Name of the alarm status bar icon. -->
     <string name="status_bar_alarm">Alarm</string>
 
+    <!-- Format string for the content description of the icon that indicates that a Mode is on.
+       For example, if the mode name is Bedtime, this will be "Bedtime is on". This content
+       description will be associated to the mode icon in status bar, smartspace, and everyone else
+       where it might be displayed without text. [CHAR LIMIT=NONE] -->
+    <string name="active_mode_content_description"><xliff:g id="modeName" example="Do Not Disturb">%1$s</xliff:g> is on</string>
+
     <!-- Wallet strings -->
     <!-- Wallet empty state, title [CHAR LIMIT=32] -->
     <string name="wallet_title">Wallet</string>
@@ -3746,9 +3752,9 @@
 
     <!-- TOUCHPAD TUTORIAL-->
     <!-- Label for button opening tutorial for back gesture on touchpad [CHAR LIMIT=NONE] -->
-    <string name="touchpad_tutorial_back_gesture_button">Back gesture</string>
+    <string name="touchpad_tutorial_back_gesture_button">Go back</string>
     <!-- Label for button opening tutorial for back gesture on touchpad [CHAR LIMIT=NONE] -->
-    <string name="touchpad_tutorial_home_gesture_button">Home gesture</string>
+    <string name="touchpad_tutorial_home_gesture_button">Go home</string>
     <!-- Label for button opening tutorial for "view recent apps" gesture on touchpad [CHAR LIMIT=NONE] -->
     <string name="touchpad_tutorial_recent_apps_gesture_button">View recent apps</string>
     <!-- Label for button finishing touchpad tutorial [CHAR LIMIT=NONE] -->
@@ -3757,26 +3763,25 @@
     <!-- Touchpad back gesture action name in tutorial [CHAR LIMIT=NONE] -->
     <string name="touchpad_back_gesture_action_title">Go back</string>
     <!-- Touchpad back gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
-    <string name="touchpad_back_gesture_guidance">To go back, swipe left or right using three fingers anywhere on the touchpad.\n\nYou can also use the keyboard shortcut
-Action + ESC for this.</string>
+    <string name="touchpad_back_gesture_guidance">Swipe left or right using three fingers on your touchpad</string>
     <!-- Screen title after back gesture was done successfully [CHAR LIMIT=NONE] -->
-    <string name="touchpad_back_gesture_success_title">Great job!</string>
+    <string name="touchpad_back_gesture_success_title">Nice!</string>
     <!-- Text shown to the user after they complete back gesture tutorial [CHAR LIMIT=NONE] -->
     <string name="touchpad_back_gesture_success_body">You completed the go back gesture.</string>
     <!-- HOME GESTURE -->
     <!-- Touchpad home gesture action name in tutorial [CHAR LIMIT=NONE] -->
     <string name="touchpad_home_gesture_action_title">Go home</string>
     <!-- Touchpad home gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
-    <string name="touchpad_home_gesture_guidance">To go to your home screen at any time, swipe up with three fingers from the bottom of your screen.</string>
+    <string name="touchpad_home_gesture_guidance">Swipe up with three fingers on your touchpad</string>
     <!-- Screen title after home gesture was done successfully [CHAR LIMIT=NONE] -->
-    <string name="touchpad_home_gesture_success_title">Nice!</string>
+    <string name="touchpad_home_gesture_success_title">Great job!</string>
     <!-- Text shown to the user after they complete home gesture tutorial [CHAR LIMIT=NONE] -->
-    <string name="touchpad_home_gesture_success_body">You completed the go home gesture.</string>
+    <string name="touchpad_home_gesture_success_body">You completed the go home gesture</string>
     <!-- RECENT APPS GESTURE -->
     <!-- Touchpad recent apps gesture action name in tutorial [CHAR LIMIT=NONE] -->
     <string name="touchpad_recent_apps_gesture_action_title">View recent apps</string>
     <!-- Touchpad recent apps gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
-    <string name="touchpad_recent_apps_gesture_guidance">Swipe up and hold using three fingers on your touchpad.</string>
+    <string name="touchpad_recent_apps_gesture_guidance">Swipe up and hold using three fingers on your touchpad</string>
     <!-- Screen title after recent apps gesture was done successfully [CHAR LIMIT=NONE] -->
     <string name="touchpad_recent_apps_gesture_success_title">Great job!</string>
     <!-- Text shown to the user after they complete recent apps gesture tutorial [CHAR LIMIT=NONE] -->
@@ -3784,13 +3789,13 @@
 
     <!-- KEYBOARD TUTORIAL-->
     <!-- Action key tutorial title [CHAR LIMIT=NONE] -->
-    <string name="tutorial_action_key_title">Action key</string>
+    <string name="tutorial_action_key_title">View all apps</string>
     <!-- Action key tutorial guidance[CHAR LIMIT=NONE] -->
-    <string name="tutorial_action_key_guidance">To access your apps, press the action key on your keyboard.</string>
+    <string name="tutorial_action_key_guidance">Press the action key on your keyboard</string>
     <!-- Screen title after action key pressed successfully [CHAR LIMIT=NONE] -->
-    <string name="tutorial_action_key_success_title">Congratulations!</string>
+    <string name="tutorial_action_key_success_title">Well done!</string>
     <!-- Text shown to the user after they complete action key tutorial [CHAR LIMIT=NONE] -->
-    <string name="tutorial_action_key_success_body">You completed the action key gesture.\n\nAction + / shows all the shortcuts you have available.</string>
+    <string name="tutorial_action_key_success_body">You completed the view all apps gesture</string>
 
     <!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] -->
     <string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 1c09f84..94b0b5f 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -540,6 +540,7 @@
     <!-- Overridden by values-television/styles.xml with tv-specific settings -->
     <style name="volume_dialog_theme" parent="Theme.SystemUI">
         <item name="android:windowIsFloating">true</item>
+        <item name="android:showWhenLocked">true</item>
     </style>
 
     <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index f05cbf4..2d27f1c0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -1067,6 +1067,8 @@
 
         @Override
         public void onDestroy() {
+            mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback);
+
             ConstraintSet constraintSet = new ConstraintSet();
             constraintSet.clone(mView);
             constraintSet.clear(mUserSwitcherViewGroup.getId());
@@ -1075,6 +1077,8 @@
 
             mView.removeView(mUserSwitcherViewGroup);
             mView.removeView(mUserSwitcher);
+            mUserSwitcher = null;
+            mUserSwitcherViewGroup = null;
         }
 
         private void findLargeUserIcon(int userId, Consumer<Drawable> consumer) {
@@ -1102,6 +1106,10 @@
                 return;
             }
 
+            if (mUserSwitcherViewGroup == null) {
+                return;
+            }
+
             mUserSwitcherViewGroup.setAlpha(0f);
             ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
             int yTrans = mView.getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry);
@@ -1110,14 +1118,18 @@
             animator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    mUserSwitcherViewGroup.setAlpha(1f);
-                    mUserSwitcherViewGroup.setTranslationY(0f);
+                    if (mUserSwitcherViewGroup != null) {
+                        mUserSwitcherViewGroup.setAlpha(1f);
+                        mUserSwitcherViewGroup.setTranslationY(0f);
+                    }
                 }
             });
             animator.addUpdateListener(animation -> {
-                float value = (float) animation.getAnimatedValue();
-                mUserSwitcherViewGroup.setAlpha(value);
-                mUserSwitcherViewGroup.setTranslationY(yTrans - yTrans * value);
+                if (mUserSwitcherViewGroup != null) {
+                    float value = (float) animation.getAnimatedValue();
+                    mUserSwitcherViewGroup.setAlpha(value);
+                    mUserSwitcherViewGroup.setTranslationY(yTrans - yTrans * value);
+                }
             });
             animator.start();
         }
@@ -1148,6 +1160,10 @@
                 Log.e(TAG, "Current user in user switcher is null.");
                 return;
             }
+            if (mUserSwitcher == null) {
+                Log.w(TAG, "User switcher is not inflated, cannot setupUserSwitcher");
+                return;
+            }
             final String currentUserName = mUserSwitcherController.getCurrentUserName();
             findLargeUserIcon(currentUser.info.id,
                     (Drawable userIcon) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 60edaae..158623f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -280,6 +280,14 @@
         if (mLocalBluetoothManager == null) {
             return;
         }
+
+        // Remove the default padding of the system ui dialog
+        View container = dialog.findViewById(android.R.id.custom);
+        if (container != null && container.getParent() != null) {
+            View containerParent = (View) container.getParent();
+            containerParent.setPadding(0, 0, 0, 0);
+        }
+
         mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW, mLaunchSourceId);
         mPairButton = dialog.requireViewById(R.id.pair_new_device_button);
         mDeviceList = dialog.requireViewById(R.id.device_list);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
index 664f3f8..9367cb5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
@@ -108,6 +108,7 @@
         private final ImageView mIconView;
         private final ImageView mGearIcon;
         private final View mGearView;
+        private final View mDividerView;
 
         DeviceItemViewHolder(@NonNull View itemView, Context context) {
             super(itemView);
@@ -118,6 +119,7 @@
             mIconView = itemView.requireViewById(R.id.bluetooth_device_icon);
             mGearIcon = itemView.requireViewById(R.id.gear_icon_image);
             mGearView = itemView.requireViewById(R.id.gear_icon);
+            mDividerView = itemView.requireViewById(R.id.divider);
         }
 
         public void bindView(DeviceItem item, HearingDeviceItemCallback callback) {
@@ -153,6 +155,7 @@
 
             mGearIcon.getDrawable().mutate().setTint(tintColor);
             mGearView.setOnClickListener(view -> callback.onDeviceItemGearClicked(item, view));
+            mDividerView.setBackgroundColor(tintColor);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
new file mode 100644
index 0000000..7aad33d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.communal.ui.viewmodel
+
+import androidx.compose.foundation.gestures.AnchoredDraggableState
+import androidx.compose.foundation.gestures.DraggableAnchors
+import androidx.compose.runtime.snapshotFlow
+import com.android.app.tracing.coroutines.coroutineScope
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.dropWhile
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+
+enum class DragHandle {
+    TOP,
+    BOTTOM,
+}
+
+data class ResizeInfo(
+    /**
+     * The number of spans to resize by. A positive number indicates expansion, whereas a negative
+     * number indicates shrinking.
+     */
+    val spans: Int,
+    /** The drag handle which was used to resize the element. */
+    val fromHandle: DragHandle,
+)
+
+class ResizeableItemFrameViewModel : ExclusiveActivatable() {
+    private data class GridLayoutInfo(
+        val minSpan: Int,
+        val maxSpan: Int,
+        val heightPerSpanPx: Float,
+        val verticalItemSpacingPx: Float,
+        val currentRow: Int,
+        val currentSpan: Int,
+    )
+
+    /**
+     * The layout information necessary in order to calculate the pixel offsets of the drag anchor
+     * points.
+     */
+    private val gridLayoutInfo = MutableStateFlow<GridLayoutInfo?>(null)
+
+    val topDragState = AnchoredDraggableState(0, DraggableAnchors { 0 at 0f })
+    val bottomDragState = AnchoredDraggableState(0, DraggableAnchors { 0 at 0f })
+
+    /** Emits a [ResizeInfo] when the element is resized using a drag gesture. */
+    val resizeInfo: Flow<ResizeInfo> =
+        merge(
+                snapshotFlow { topDragState.settledValue }.map { ResizeInfo(-it, DragHandle.TOP) },
+                snapshotFlow { bottomDragState.settledValue }
+                    .map { ResizeInfo(it, DragHandle.BOTTOM) },
+            )
+            .dropWhile { it.spans == 0 }
+            .distinctUntilChanged()
+
+    /**
+     * Sets the necessary grid layout information needed for calculating the pixel offsets of the
+     * drag anchors.
+     */
+    fun setGridLayoutInfo(
+        verticalItemSpacingPx: Float,
+        verticalContentPaddingPx: Float,
+        viewportHeightPx: Int,
+        maxItemSpan: Int,
+        minItemSpan: Int,
+        currentRow: Int,
+        currentSpan: Int,
+    ) {
+        require(maxItemSpan >= minItemSpan) {
+            "Maximum item span of $maxItemSpan cannot be less than the minimum span of $minItemSpan"
+        }
+        require(minItemSpan in 1..maxItemSpan) {
+            "Minimum span must be between 1 and $maxItemSpan, but was $minItemSpan"
+        }
+        require(currentSpan % minItemSpan == 0) {
+            "Current span of $currentSpan is not a multiple of the minimum span of $minItemSpan"
+        }
+        val availableHeight = viewportHeightPx - verticalContentPaddingPx
+        val totalSpacing = verticalItemSpacingPx * ((maxItemSpan / minItemSpan) - 1)
+        val heightPerSpanPx = (availableHeight - totalSpacing) / maxItemSpan
+        gridLayoutInfo.value =
+            GridLayoutInfo(
+                minSpan = minItemSpan,
+                maxSpan = maxItemSpan,
+                heightPerSpanPx = heightPerSpanPx,
+                verticalItemSpacingPx = verticalItemSpacingPx,
+                currentRow = currentRow,
+                currentSpan = currentSpan,
+            )
+    }
+
+    private fun calculateAnchorsForHandle(
+        handle: DragHandle,
+        layoutInfo: GridLayoutInfo,
+    ): DraggableAnchors<Int> {
+
+        if (!isDragAllowed(handle, layoutInfo)) {
+            return DraggableAnchors { 0 at 0f }
+        }
+
+        val (
+            minItemSpan,
+            maxItemSpan,
+            heightPerSpanPx,
+            verticalSpacingPx,
+            currentRow,
+            currentSpan,
+        ) = layoutInfo
+
+        // The maximum row this handle can be dragged to.
+        val maxRow =
+            if (handle == DragHandle.TOP) {
+                (currentRow + currentSpan - minItemSpan).coerceAtLeast(0)
+            } else {
+                maxItemSpan
+            }
+
+        // The minimum row this handle can be dragged to.
+        val minRow =
+            if (handle == DragHandle.TOP) {
+                0
+            } else {
+                (currentRow + minItemSpan).coerceAtMost(maxItemSpan)
+            }
+
+        // The current row position of this handle
+        val currentPosition = if (handle == DragHandle.TOP) currentRow else currentRow + currentSpan
+
+        return DraggableAnchors {
+            for (targetRow in minRow..maxRow step minItemSpan) {
+                val diff = targetRow - currentPosition
+                val spacing = diff / minItemSpan * verticalSpacingPx
+                diff at diff * heightPerSpanPx + spacing
+            }
+        }
+    }
+
+    private fun isDragAllowed(handle: DragHandle, layoutInfo: GridLayoutInfo): Boolean {
+        val minItemSpan = layoutInfo.minSpan
+        val maxItemSpan = layoutInfo.maxSpan
+        val currentRow = layoutInfo.currentRow
+        val currentSpan = layoutInfo.currentSpan
+        val atMinSize = currentSpan == minItemSpan
+
+        // If already at the minimum size and in the first row, item cannot be expanded from the top
+        if (handle == DragHandle.TOP && currentRow == 0 && atMinSize) {
+            return false
+        }
+
+        // If already at the minimum size and occupying the last row, item cannot be expanded from
+        // the
+        // bottom
+        if (handle == DragHandle.BOTTOM && (currentRow + currentSpan) == maxItemSpan && atMinSize) {
+            return false
+        }
+
+        // If at maximum size, item can only be shrunk from the bottom and not the top.
+        if (handle == DragHandle.TOP && currentSpan == maxItemSpan) {
+            return false
+        }
+
+        return true
+    }
+
+    override suspend fun onActivated(): Nothing {
+        coroutineScope("ResizeableItemFrameViewModel.onActivated") {
+            gridLayoutInfo
+                .filterNotNull()
+                .onEach { layoutInfo ->
+                    topDragState.updateAnchors(
+                        calculateAnchorsForHandle(DragHandle.TOP, layoutInfo)
+                    )
+                    bottomDragState.updateAnchors(
+                        calculateAnchorsForHandle(DragHandle.BOTTOM, layoutInfo)
+                    )
+                }
+                .launchIn(this)
+            awaitCancellation()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
index 099e3fc..4b9ac1d 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
@@ -21,9 +21,10 @@
 import android.view.View
 import android.widget.TextClock
 import com.android.internal.util.Preconditions
-import com.android.systemui.res.R
+import com.android.systemui.Flags
 import com.android.systemui.complication.DreamClockTimeComplication
 import com.android.systemui.complication.DreamClockTimeComplication.DreamClockTimeViewHolder
+import com.android.systemui.res.R
 import dagger.Module
 import dagger.Provides
 import dagger.Subcomponent
@@ -71,9 +72,13 @@
                             /* root = */ null,
                             /* attachToRoot = */ false,
                         ) as TextClock,
-                        "R.layout.dream_overlay_complication_clock_time did not properly inflate"
+                        "R.layout.dream_overlay_complication_clock_time did not properly inflate",
                     )
-                view.setFontVariationSettings(TAG_WEIGHT + WEIGHT)
+                if (Flags.dreamOverlayUpdatedFont()) {
+                    view.setFontVariationSettings("'wght' 600, 'opsz' 96")
+                } else {
+                    view.setFontVariationSettings(TAG_WEIGHT + WEIGHT)
+                }
                 return view
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
index 373279c..462e820 100644
--- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -20,6 +20,8 @@
 import com.android.systemui.display.data.repository.DeviceStateRepositoryImpl
 import com.android.systemui.display.data.repository.DisplayRepository
 import com.android.systemui.display.data.repository.DisplayRepositoryImpl
+import com.android.systemui.display.data.repository.FocusedDisplayRepository
+import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
 import dagger.Binds
@@ -39,4 +41,9 @@
     fun bindsDeviceStateRepository(
         deviceStateRepository: DeviceStateRepositoryImpl
     ): DeviceStateRepository
+
+    @Binds
+    fun bindsFocusedDisplayRepository(
+        focusedDisplayRepository: FocusedDisplayRepositoryImpl
+    ): FocusedDisplayRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/FocusedDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/FocusedDisplayRepository.kt
index dc07cca..6fc08f6 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/FocusedDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/FocusedDisplayRepository.kt
@@ -19,8 +19,7 @@
 import android.annotation.MainThread
 import android.view.Display.DEFAULT_DISPLAY
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.FocusedDisplayRepoLog
@@ -38,20 +37,30 @@
 import kotlinx.coroutines.flow.stateIn
 
 /** Repository tracking display focus. */
+interface FocusedDisplayRepository {
+    /** Provides the currently focused display. */
+    val focusedDisplayId: StateFlow<Int>
+}
+
 @SysUISingleton
 @MainThread
-class FocusedDisplayRepository
+class FocusedDisplayRepositoryImpl
 @Inject
 constructor(
-    @Application val scope: CoroutineScope,
-    @Main private val mainExecutor: Executor,
+    @Background val backgroundScope: CoroutineScope,
+    @Background private val backgroundExecutor: Executor,
     transitions: ShellTransitions,
     @FocusedDisplayRepoLog logBuffer: LogBuffer,
-) {
+) : FocusedDisplayRepository {
     val focusedTask: Flow<Int> =
-        conflatedCallbackFlow {
-                val listener = FocusTransitionListener { displayId -> trySend(displayId) }
-                transitions.setFocusTransitionListener(listener, mainExecutor)
+        conflatedCallbackFlow<Int> {
+                val listener =
+                    object : FocusTransitionListener {
+                        override fun onFocusedDisplayChanged(displayId: Int) {
+                            trySend(displayId)
+                        }
+                    }
+                transitions.setFocusTransitionListener(listener, backgroundExecutor)
                 awaitClose { transitions.unsetFocusTransitionListener(listener) }
             }
             .onEach {
@@ -63,7 +72,6 @@
                 )
             }
 
-    /** Provides the currently focused display. */
-    val focusedDisplayId: StateFlow<Int>
-        get() = focusedTask.stateIn(scope, SharingStarted.Eagerly, DEFAULT_DISPLAY)
+    override val focusedDisplayId: StateFlow<Int>
+        get() = focusedTask.stateIn(backgroundScope, SharingStarted.Eagerly, DEFAULT_DISPLAY)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
new file mode 100644
index 0000000..8b6cc8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.ui.viewmodel
+
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Handles user input for the dream scene. */
+class DreamUserActionsViewModel @AssistedInject constructor() : UserActionsViewModel() {
+
+    override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+        setActions(emptyMap())
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): DreamUserActionsViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
index 32e7f41..5563969 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
@@ -48,7 +48,7 @@
 ) {
 
     companion object {
-        const val DEFAULT_DIALOG_TIMEOUT_MILLIS = 3500
+        const val DEFAULT_DIALOG_TIMEOUT_MILLIS = 5000
     }
 
     private val timeoutMillis: Long
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 1e9541e..6d1d9cb 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
@@ -189,6 +189,7 @@
                     internalTransitionInteractor.currentTransitionInfoInternal,
                     keyguardInteractor.statusBarState,
                     keyguardInteractor.isKeyguardDismissible,
+                    keyguardInteractor.isKeyguardOccluded,
                 )
                 .collect {
                     (
@@ -196,7 +197,8 @@
                         startedStep,
                         currentTransitionInfo,
                         statusBarState,
-                        isKeyguardUnlocked) ->
+                        isKeyguardUnlocked,
+                        isKeyguardOccluded) ->
                     val id = transitionId
                     if (id != null) {
                         if (startedStep.to == KeyguardState.PRIMARY_BOUNCER) {
@@ -236,9 +238,13 @@
                             if (nextState == TransitionState.CANCELED) {
                                 transitionRepository.startTransition(
                                     TransitionInfo(
-                                        ownerName = name,
+                                        ownerName =
+                                            "$name " +
+                                                "(on behalf of FromPrimaryBouncerInteractor)",
                                         from = KeyguardState.PRIMARY_BOUNCER,
-                                        to = KeyguardState.LOCKSCREEN,
+                                        to =
+                                            if (isKeyguardOccluded) KeyguardState.OCCLUDED
+                                            else KeyguardState.LOCKSCREEN,
                                         modeOnCanceled = TransitionModeOnCanceled.REVERSE,
                                         animator =
                                             getDefaultAnimatorForTransitionsToState(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index c0b9efaa..914730e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.ui.composable.transitions.FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION
+import com.android.systemui.scene.ui.composable.transitions.TO_BOUNCER_FADE_FRACTION
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,14 +49,12 @@
                 duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
                 edge = Edge.create(from = LOCKSCREEN, to = Scenes.Bouncer),
             )
-            .setupWithoutSceneContainer(
-                edge = Edge.create(from = LOCKSCREEN, to = PRIMARY_BOUNCER),
-            )
+            .setupWithoutSceneContainer(edge = Edge.create(from = LOCKSCREEN, to = PRIMARY_BOUNCER))
 
     private val alphaForAnimationStep: (Float) -> Float =
         when {
             SceneContainerFlag.isEnabled -> { step ->
-                    1f - Math.min((step / FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION), 1f)
+                    1f - Math.min((step / TO_BOUNCER_FADE_FRACTION), 1f)
                 }
             else -> { step -> 1f - step }
         }
@@ -64,7 +62,7 @@
     val shortcutsAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
             duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
-            onStep = alphaForAnimationStep
+            onStep = alphaForAnimationStep,
         )
 
     val lockscreenAlpha: Flow<Float> = shortcutsAlpha
@@ -76,8 +74,8 @@
                     duration = 250.milliseconds,
                     onStep = { 1f - it },
                     onCancel = { 0f },
-                    onFinish = { 0f }
+                    onFinish = { 0f },
                 ),
-            flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f)
+            flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f),
         )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 51d2329..65c29b8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -52,8 +52,8 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.approachLayout
 import androidx.compose.ui.layout.onPlaced
-import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.res.dimensionResource
@@ -66,6 +66,9 @@
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneScope
@@ -288,7 +291,7 @@
                 transitions =
                     transitions {
                         from(QuickQuickSettings, QuickSettings) {
-                            quickQuickSettingsToQuickSettings()
+                            quickQuickSettingsToQuickSettings(viewModel::inFirstPage::get)
                         }
                     },
             )
@@ -533,6 +536,10 @@
 
             onDispose { qqsVisible.value = false }
         }
+        val squishiness by
+            viewModel.containerViewModel.quickQuickSettingsViewModel.squishinessViewModel
+                .squishiness
+                .collectAsStateWithLifecycle()
         Column(modifier = Modifier.sysuiResTag("quick_qs_panel")) {
             Box(
                 modifier =
@@ -546,7 +553,16 @@
                                 topFromRoot + coordinates.size.height,
                             )
                         }
-                        .onSizeChanged { size -> qqsHeight.value = size.height }
+                        // Use an approach layout to determien the height without squishiness, as
+                        // that's the value that NPVC and QuickSettingsController care about
+                        // (measured height).
+                        .approachLayout(isMeasurementApproachInProgress = { squishiness < 1f }) {
+                            measurable,
+                            constraints ->
+                            qqsHeight.value = lookaheadSize.height
+                            val placeable = measurable.measure(constraints)
+                            layout(placeable.width, placeable.height) { placeable.place(0, 0) }
+                        }
                         .padding(top = { qqsPadding }, bottom = { bottomPadding.roundToPx() })
             ) {
                 val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle()
@@ -704,6 +720,14 @@
             else -> QuickSettings
         }
     }
+
+    val QqsTileElementMatcher =
+        object : ElementMatcher {
+            override fun matches(key: ElementKey, content: ContentKey): Boolean {
+                return content == SceneKeys.QuickQuickSettings &&
+                    ElementKeys.TileElementMatcher.matches(key, content)
+            }
+        }
 }
 
 suspend fun synchronizeQsState(state: MutableSceneTransitionLayoutState, expansion: Flow<Float>) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt
index 1514986..9e3945e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt
@@ -17,13 +17,23 @@
 package com.android.systemui.qs.composefragment.ui
 
 import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.qs.composefragment.SceneKeys
 import com.android.systemui.qs.shared.ui.ElementKeys
 
-fun TransitionBuilder.quickQuickSettingsToQuickSettings() {
+fun TransitionBuilder.quickQuickSettingsToQuickSettings(inFirstPage: () -> Boolean = { true }) {
 
     fractionRange(start = 0.5f) { fade(ElementKeys.QuickSettingsContent) }
 
     fractionRange(start = 0.9f) { fade(ElementKeys.FooterActions) }
 
     anchoredTranslate(ElementKeys.QuickSettingsContent, ElementKeys.GridAnchor)
+
+    sharedElement(ElementKeys.TileElementMatcher, enabled = inFirstPage())
+
+    // This will animate between 0f (QQS) and 0.6, fading in the QQS tiles when coming back
+    // from non first page QS. The QS content ends fading out at 0.5f, so there's a brief
+    // overlap, but because they are really faint, it looks better than complete black without
+    // overlap.
+    fractionRange(end = 0.6f) { fade(SceneKeys.QqsTileElementMatcher) }
+    anchoredTranslate(SceneKeys.QqsTileElementMatcher, ElementKeys.GridAnchor)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 2d4e358..7a8b2c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel.QSExpansionState
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor
+import com.android.systemui.qs.panels.ui.viewmodel.PaginatedGridViewModel
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
 import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
@@ -71,6 +72,7 @@
     private val configurationInteractor: ConfigurationInteractor,
     private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
     private val squishinessInteractor: TileSquishinessInteractor,
+    private val paginatedGridViewModel: PaginatedGridViewModel,
     @Assisted private val lifecycleScope: LifecycleCoroutineScope,
 ) : Dumpable, ExclusiveActivatable() {
     val footerActionsViewModel =
@@ -292,6 +294,9 @@
      */
     var collapseExpandAccessibilityAction: Runnable? = null
 
+    val inFirstPage: Boolean
+        get() = paginatedGridViewModel.inFirstPage
+
     override suspend fun onActivated(): Nothing {
         hydrateSquishinessInteractor()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
index 083f529..e749475 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
@@ -31,8 +31,10 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -76,6 +78,11 @@
 
         val pagerState = rememberPagerState(0) { pages.size }
 
+        // Used to track if this is currently in the first page or not, for animations
+        LaunchedEffect(key1 = pagerState) {
+            snapshotFlow { pagerState.currentPage == 0 }.collect { viewModel.inFirstPage = it }
+        }
+
         Column {
             HorizontalPager(
                 state = pagerState,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt
index ada1ef4..91f7641 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.qs.panels.ui.compose.infinitegrid
 
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.layout
+import androidx.compose.ui.layout.approachLayout
 import kotlin.math.roundToInt
 
 /**
@@ -27,17 +27,22 @@
  * [squishiness] on the measure/layout pass.
  *
  * The squished composable will be center aligned.
+ *
+ * Use an [approachLayout] to indicate that this should be measured in the lookahead step without
+ * using squishiness. If a parent of this node needs to determine unsquished height, they should
+ * also use an approachLayout tracking the squishiness.
  */
 fun Modifier.verticalSquish(squishiness: () -> Float): Modifier {
-    return layout { measurable, constraints ->
-        val placeable = measurable.measure(constraints)
-        val actualHeight = placeable.height
-        val squishedHeight = actualHeight * squishiness()
-        // Center the content by moving it UP (squishedHeight < actualHeight)
-        val scroll = (squishedHeight - actualHeight) / 2
+    return approachLayout(isMeasurementApproachInProgress = { squishiness() < 1 }) { measurable, _
+        ->
+        val squishinessValue = squishiness()
+        val expectedHeight = lookaheadSize.height
 
-        layout(placeable.width, squishedHeight.roundToInt()) {
-            placeable.place(0, scroll.roundToInt())
-        }
+        val placeable = measurable.measure(lookaheadConstraints)
+        val squishedHeight = (expectedHeight * squishinessValue).roundToInt()
+        // Center the content by moving it UP (squishedHeight < actualHeight)
+        val scroll = (squishedHeight - expectedHeight) / 2
+
+        layout(placeable.width, squishedHeight) { placeable.place(0, scroll) }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
index 28bf474..d4f8298 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
@@ -43,4 +43,10 @@
             SharingStarted.WhileSubscribed(),
             paginatedGridInteractor.defaultRows,
         )
+
+    /*
+     * Tracks whether the current HorizontalPager (using this viewmodel) is in the first page.
+     * This requires it to be a `@SysUISingleton` to be shared between viewmodels.
+     */
+    var inFirstPage = true
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt b/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt
index 625459d..2425f13 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt
@@ -25,7 +25,10 @@
     val GridAnchor = ElementKey("QuickSettingsGridAnchor")
     val FooterActions = ElementKey("FooterActions")
 
-    class TileElementKey(spec: TileSpec, val position: Int) : ElementKey(spec.spec, spec.spec)
+    fun TileSpec.toElementKey(positionInGrid: Int) =
+        ElementKey(this.spec, TileIdentity(this, positionInGrid))
 
-    fun TileSpec.toElementKey(positionInGrid: Int) = TileElementKey(this, positionInGrid)
+    val TileElementMatcher = ElementKey.withIdentity { it is TileIdentity }
 }
+
+private data class TileIdentity(val spec: TileSpec, val position: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index a4fe4e3..ad76b4f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -169,50 +169,34 @@
 
     private void enableZenMode(@Nullable Expandable expandable) {
         int zenDuration = mSettingZenDuration.getValue();
-        boolean showOnboarding = Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0) != 0
-                && Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.ZEN_SETTINGS_UPDATED, 0) != 1;
-        if (showOnboarding) {
-            // don't show on-boarding again or notification ever
-            Settings.Secure.putInt(mContext.getContentResolver(),
-                    Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
-            // turn on DND
-            mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
-            // show on-boarding screen
-            Intent intent = new Intent(Settings.ZEN_MODE_ONBOARDING);
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-            mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
-        } else {
-            switch (zenDuration) {
-                case Settings.Secure.ZEN_DURATION_PROMPT:
-                    mUiHandler.post(() -> {
-                        Dialog dialog = makeZenModeDialog();
-                        if (expandable != null) {
-                            DialogTransitionAnimator.Controller controller =
-                                    expandable.dialogTransitionController(new DialogCuj(
-                                            InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
-                                            INTERACTION_JANK_TAG));
-                            if (controller != null) {
-                                mDialogTransitionAnimator.show(dialog,
-                                        controller, /* animateBackgroundBoundsChange= */ false);
-                            } else {
-                                dialog.show();
-                            }
+        switch (zenDuration) {
+            case Settings.Secure.ZEN_DURATION_PROMPT:
+                mUiHandler.post(() -> {
+                    Dialog dialog = makeZenModeDialog();
+                    if (expandable != null) {
+                        DialogTransitionAnimator.Controller controller =
+                                expandable.dialogTransitionController(new DialogCuj(
+                                        InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+                                        INTERACTION_JANK_TAG));
+                        if (controller != null) {
+                            mDialogTransitionAnimator.show(dialog,
+                                    controller, /* animateBackgroundBoundsChange= */ false);
                         } else {
                             dialog.show();
                         }
-                    });
-                    break;
-                case Settings.Secure.ZEN_DURATION_FOREVER:
-                    mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
-                    break;
-                default:
-                    Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
-                            mHost.getUserId(), true).id;
-                    mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
-                            conditionId, TAG);
-            }
+                    } else {
+                        dialog.show();
+                    }
+                });
+                break;
+            case Settings.Secure.ZEN_DURATION_FOREVER:
+                mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
+                break;
+            default:
+                Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
+                        mHost.getUserId(), true).id;
+                mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                        conditionId, TAG);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index a89f752..4beec10 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -44,6 +44,7 @@
         [
             BouncerSceneModule::class,
             CommunalSceneModule::class,
+            DreamSceneModule::class,
             EmptySceneModule::class,
             GoneSceneModule::class,
             LockscreenSceneModule::class,
@@ -98,6 +99,7 @@
                     listOfNotNull(
                         Scenes.Gone,
                         Scenes.Communal,
+                        Scenes.Dream,
                         Scenes.Lockscreen,
                         Scenes.Bouncer,
                         Scenes.QuickSettings.takeUnless { DualShade.isEnabled },
@@ -114,9 +116,10 @@
                             Scenes.Gone to 0,
                             Scenes.Lockscreen to 0,
                             Scenes.Communal to 1,
-                            Scenes.Shade to 2.takeUnless { DualShade.isEnabled },
-                            Scenes.QuickSettings to 3.takeUnless { DualShade.isEnabled },
-                            Scenes.Bouncer to 4,
+                            Scenes.Dream to 2,
+                            Scenes.Shade to 3.takeUnless { DualShade.isEnabled },
+                            Scenes.QuickSettings to 4.takeUnless { DualShade.isEnabled },
+                            Scenes.Bouncer to 5,
                         )
                         .filterValues { it != null }
                         .mapValues { checkNotNull(it.value) },
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepository.kt
deleted file mode 100644
index a8d0777..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepository.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.data.repository
-
-import android.graphics.Region
-import android.view.ISystemGestureExclusionListener
-import android.view.IWindowManager
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
-import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-
-@SysUISingleton
-class SystemGestureExclusionRepository
-@Inject
-constructor(private val windowManager: IWindowManager) {
-
-    /**
-     * Returns [Flow] of the [Region] in which system gestures should be excluded on the display
-     * identified with [displayId].
-     */
-    fun exclusionRegion(displayId: Int): Flow<Region?> {
-        return conflatedCallbackFlow {
-            val listener =
-                object : ISystemGestureExclusionListener.Stub() {
-                    override fun onSystemGestureExclusionChanged(
-                        displayId: Int,
-                        restrictedRegion: Region?,
-                        unrestrictedRegion: Region?,
-                    ) {
-                        trySend(restrictedRegion)
-                    }
-                }
-            windowManager.registerSystemGestureExclusionListener(listener, displayId)
-
-            awaitClose {
-                windowManager.unregisterSystemGestureExclusionListener(listener, displayId)
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractor.kt
deleted file mode 100644
index 4cee874..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractor.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.domain.interactor
-
-import android.graphics.Region
-import com.android.systemui.scene.data.repository.SystemGestureExclusionRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-class SystemGestureExclusionInteractor
-@Inject
-constructor(private val repository: SystemGestureExclusionRepository) {
-
-    /**
-     * Returns [Flow] of the [Region] in which system gestures should be excluded on the display
-     * identified with [displayId].
-     */
-    fun exclusionRegion(displayId: Int): Flow<Region?> {
-        return repository.exclusionRegion(displayId)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
index 82b4b1c..16492ef 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
@@ -33,6 +33,9 @@
     /** The communal scene shows the glanceable hub when device is locked and docked. */
     @JvmField val Communal = SceneKey("communal")
 
+    /** The dream scene shows up when a dream activity is showing. */
+    @JvmField val Dream = SceneKey("dream")
+
     /**
      * "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
      * content from the scene framework.
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index a8be580..38f4e73 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -107,13 +107,7 @@
             view.viewModel(
                 traceName = "SceneWindowRootViewBinder",
                 minWindowLifecycleState = WindowLifecycleState.ATTACHED,
-                factory = {
-                    viewModelFactory.create(
-                        view,
-                        view.context.displayId,
-                        motionEventHandlerReceiver,
-                    )
-                },
+                factory = { viewModelFactory.create(view, motionEventHandlerReceiver) },
             ) { viewModel ->
                 try {
                     view.setViewTreeOnBackPressedDispatcherOwner(
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
index ea19020..f0f476e 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -25,6 +25,7 @@
 import android.view.WindowInsets
 import android.widget.FrameLayout
 import androidx.core.view.updateMargins
+import com.android.systemui.Flags
 import com.android.systemui.compose.ComposeInitializer
 import com.android.systemui.res.R
 
@@ -103,6 +104,8 @@
 
     private fun applyMargins() {
         val count = childCount
+        val hasFlagsEnabled = Flags.checkLockscreenGoneTransition()
+        var hasChildMarginUpdated = false
         for (i in 0 until count) {
             val child = getChildAt(i)
             if (child.layoutParams is LayoutParams) {
@@ -113,10 +116,17 @@
                             layoutParams.leftMargin != leftInset)
                 ) {
                     layoutParams.updateMargins(left = leftInset, right = rightInset)
-                    child.requestLayout()
+                    hasChildMarginUpdated = true
+                    if (!hasFlagsEnabled) {
+                        child.requestLayout()
+                    }
                 }
             }
         }
+        if (hasFlagsEnabled && hasChildMarginUpdated) {
+            // Request layout at once after all children's margins has updated
+            requestLayout()
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilter.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilter.kt
deleted file mode 100644
index a1d915a..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilter.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.ui.viewmodel
-
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.geometry.Offset
-import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.Hydrator
-import com.android.systemui.scene.domain.interactor.SystemGestureExclusionInteractor
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-import kotlin.math.roundToInt
-
-/** Decides whether drag gestures should be filtered out in the scene container framework. */
-class SceneContainerGestureFilter
-@AssistedInject
-constructor(interactor: SystemGestureExclusionInteractor, @Assisted displayId: Int) :
-    ExclusiveActivatable() {
-
-    private val hydrator = Hydrator("SceneContainerGestureFilter.hydrator")
-    private val exclusionRegion by
-        hydrator.hydratedStateOf(
-            traceName = "exclusionRegion",
-            initialValue = null,
-            source = interactor.exclusionRegion(displayId),
-        )
-
-    override suspend fun onActivated(): Nothing {
-        hydrator.activate()
-    }
-
-    /**
-     * Returns `true` if a drag gesture starting at [startPosition] should be filtered out (e.g.
-     * ignored, `false` otherwise.
-     *
-     * Invoke this and pass in the position of the `ACTION_DOWN` pointer event that began the
-     * gesture.
-     */
-    fun shouldFilterGesture(startPosition: Offset): Boolean {
-        check(isActive) { "Must be activated to use!" }
-
-        return exclusionRegion?.contains(startPosition.x.roundToInt(), startPosition.y.roundToInt())
-            ?: false
-    }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(displayId: Int): SceneContainerGestureFilter
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index f505385..889380a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -19,7 +19,6 @@
 import android.view.MotionEvent
 import android.view.View
 import androidx.compose.runtime.getValue
-import androidx.compose.ui.geometry.Offset
 import com.android.app.tracing.coroutines.launch
 import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.DefaultEdgeDetector
@@ -61,10 +60,8 @@
     shadeInteractor: ShadeInteractor,
     private val splitEdgeDetector: SplitEdgeDetector,
     private val logger: SceneLogger,
-    gestureFilterFactory: SceneContainerGestureFilter.Factory,
     hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory,
     @Assisted view: View,
-    @Assisted displayId: Int,
     @Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
 ) : ExclusiveActivatable() {
 
@@ -92,8 +89,6 @@
                 },
         )
 
-    private val gestureFilter: SceneContainerGestureFilter = gestureFilterFactory.create(displayId)
-
     override suspend fun onActivated(): Nothing {
         try {
             // Sends a MotionEventHandler to the owner of the view-model so they can report
@@ -112,7 +107,6 @@
 
             coroutineScope {
                 launch { hydrator.activate() }
-                launch { gestureFilter.activate() }
                 launch("SceneContainerHapticsViewModel") { hapticsViewModel.activate() }
             }
             awaitCancellation()
@@ -262,17 +256,6 @@
         }
     }
 
-    /**
-     * Returns `true` if a drag gesture starting at [startPosition] should be filtered out (e.g.
-     * ignored, `false` otherwise.
-     *
-     * Invoke this and pass in the position of the `ACTION_DOWN` pointer event that began the
-     * gesture.
-     */
-    fun shouldFilterGesture(startPosition: Offset): Boolean {
-        return gestureFilter.shouldFilterGesture(startPosition)
-    }
-
     /** Defines interface for classes that can handle externally-reported [MotionEvent]s. */
     interface MotionEventHandler {
         /** Notifies that a [MotionEvent] has occurred. */
@@ -289,7 +272,6 @@
     interface Factory {
         fun create(
             view: View,
-            displayId: Int,
             motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
         ): SceneContainerViewModel
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt b/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt
index 6730d2d..7b56688 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt
@@ -49,7 +49,7 @@
     override fun handleScreenshot(
         screenshot: ScreenshotData,
         finisher: Consumer<Uri?>,
-        requestCallback: TakeScreenshotService.RequestCallback
+        requestCallback: TakeScreenshotService.RequestCallback,
     ) {
         if (screenshot.type == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) {
             screenshot.bitmap = imageCapture.captureDisplay(screenshot.displayId, crop = null)
@@ -69,8 +69,8 @@
                 Executors.newSingleThreadExecutor(),
                 UUID.randomUUID(),
                 screenshot.bitmap,
-                screenshot.getUserOrDefault(),
-                screenshot.displayId
+                screenshot.userHandle,
+                screenshot.displayId,
             )
         future.addListener(
             {
@@ -86,7 +86,7 @@
                     requestCallback.reportError()
                 }
             },
-            mainExecutor
+            mainExecutor,
         )
     }
 
@@ -98,11 +98,11 @@
                 .notifyScreenshotError(R.string.screenshot_failed_to_save_text)
         } else {
             uiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, screenshot.packageNameString)
-            if (userManager.isManagedProfile(screenshot.getUserOrDefault().identifier)) {
+            if (userManager.isManagedProfile(screenshot.userHandle.identifier)) {
                 uiEventLogger.log(
                     ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE,
                     0,
-                    screenshot.packageNameString
+                    screenshot.packageNameString,
                 )
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
index 7724abd..e589600 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
@@ -301,7 +301,7 @@
         saveScreenshotInBackground(screenshot, requestId, finisher, result -> {
             if (result.uri != null) {
                 ScreenshotSavedResult savedScreenshot = new ScreenshotSavedResult(
-                        result.uri, screenshot.getUserOrDefault(), result.timestamp);
+                        result.uri, screenshot.getUserHandle(), result.timestamp);
                 mActionsController.setCompletedScreenshot(requestId, savedScreenshot);
             }
         });
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
index 29208f8..0806be8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
@@ -214,11 +214,7 @@
         saveScreenshotInBackground(screenshot, requestId, finisher) { result ->
             if (result.uri != null) {
                 val savedScreenshot =
-                    ScreenshotSavedResult(
-                        result.uri,
-                        screenshot.getUserOrDefault(),
-                        result.timestamp,
-                    )
+                    ScreenshotSavedResult(result.uri, screenshot.userHandle, result.timestamp)
                 actionsController.setCompletedScreenshot(requestId, savedScreenshot)
             }
         }
@@ -235,7 +231,7 @@
         window.setFocusable(true)
         viewProxy.requestFocus()
 
-        enqueueScrollCaptureRequest(requestId, screenshot.userHandle!!)
+        enqueueScrollCaptureRequest(requestId, screenshot.userHandle)
 
         window.attachWindow()
 
@@ -267,7 +263,7 @@
 
     private fun prepareViewForNewScreenshot(screenshot: ScreenshotData, oldPackageName: String?) {
         window.whenWindowAttached {
-            announcementResolver.getScreenshotAnnouncement(screenshot.userHandle!!.identifier) {
+            announcementResolver.getScreenshotAnnouncement(screenshot.userHandle.identifier) {
                 viewProxy.announceForAccessibility(it)
             }
         }
@@ -517,7 +513,7 @@
                 bgExecutor,
                 requestId,
                 screenshot.bitmap,
-                screenshot.getUserOrDefault(),
+                screenshot.userHandle,
                 display.displayId,
             )
         future.addListener(
@@ -525,7 +521,7 @@
                 try {
                     val result = future.get()
                     Log.d(TAG, "Saved screenshot: $result")
-                    logScreenshotResultStatus(result.uri, screenshot.userHandle!!)
+                    logScreenshotResultStatus(result.uri, screenshot.userHandle)
                     onResult.accept(result)
                     if (LogConfig.DEBUG_CALLBACK) {
                         Log.d(TAG, "finished bg processing, calling back with uri: ${result.uri}")
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
index fb7c34f..2df1e8a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
@@ -18,7 +18,7 @@
     @ScreenshotType val type: Int,
     @ScreenshotSource val source: Int,
     /** UserHandle for the owner of the app being screenshotted, if known. */
-    val userHandle: UserHandle?,
+    val userHandle: UserHandle,
     /** ComponentName of the top-most app in the screenshot. */
     val topComponent: ComponentName?,
     var screenBounds: Rect?,
@@ -40,7 +40,7 @@
             ScreenshotData(
                 type = request.type,
                 source = request.source,
-                userHandle = if (request.userId >= 0) UserHandle.of(request.userId) else null,
+                userHandle = UserHandle.of(request.userId),
                 topComponent = request.topComponent,
                 screenBounds = request.boundsInScreen,
                 taskId = request.taskId,
@@ -51,7 +51,7 @@
 
         @VisibleForTesting
         fun forTesting(
-            userHandle: UserHandle? = null,
+            userHandle: UserHandle = UserHandle.CURRENT,
             source: Int = ScreenshotSource.SCREENSHOT_KEY_CHORD,
             topComponent: ComponentName? = null,
             bitmap: Bitmap? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index ab8a953..a755746 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.FocusedDisplayRepository
 import com.android.systemui.res.R
 import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
 import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
@@ -83,6 +84,7 @@
     private val uiEventLogger: UiEventLogger,
     private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory,
     private val headlessScreenshotHandler: HeadlessScreenshotHandler,
+    private val focusedDisplayRepository: FocusedDisplayRepository,
 ) : TakeScreenshotExecutor {
     private val displays = displayRepository.displays
     private var screenshotController: InteractiveScreenshotHandler? = null
@@ -216,14 +218,13 @@
                     ?: error("Can't find default display")
 
             // All other invocations use the focused display
-            else -> focusedDisplay()
+            else ->
+                displayRepository.getDisplay(focusedDisplayRepository.focusedDisplayId.value)
+                    ?: displayRepository.getDisplay(Display.DEFAULT_DISPLAY)
+                    ?: error("Can't find default display")
         }
     }
 
-    // TODO(b/367394043): Determine the focused display here.
-    private suspend fun focusedDisplay() =
-        displayRepository.getDisplay(Display.DEFAULT_DISPLAY) ?: error("Can't find default display")
-
     /** Propagates the close system dialog signal to the ScreenshotController. */
     override fun onCloseSystemDialogsReceived() {
         if (screenshotController?.isPendingSharedTransition() == false) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
index b3d5c9e..b67ad8a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
@@ -92,7 +92,7 @@
                         updates.component,
                         updates.owner,
                         type.taskId,
-                        type.taskBounds
+                        type.taskBounds,
                     )
                 is FullScreen ->
                     replaceWithScreenshot(
@@ -122,7 +122,7 @@
             componentName = topMainRootTask?.topActivity ?: defaultComponent,
             taskId = topMainRootTask?.taskId,
             owner = defaultOwner,
-            displayId = original.displayId
+            displayId = original.displayId,
         )
     }
 
@@ -141,14 +141,14 @@
             userHandle = owner,
             taskId = taskId,
             topComponent = componentName,
-            screenBounds = taskBounds
+            screenBounds = taskBounds,
         )
     }
 
     private suspend fun replaceWithScreenshot(
         original: ScreenshotData,
         componentName: ComponentName?,
-        owner: UserHandle?,
+        owner: UserHandle,
         displayId: Int,
         taskId: Int? = null,
     ): ScreenshotData {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
index a4906c1..91efa0a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
@@ -41,7 +41,7 @@
 
 class ScreenshotAnimationController(
     private val view: ScreenshotShelfView,
-    private val viewModel: ScreenshotViewModel
+    private val viewModel: ScreenshotViewModel,
 ) {
     private var animator: Animator? = null
     private val screenshotPreview = view.requireViewById<ImageView>(R.id.screenshot_preview)
@@ -56,7 +56,7 @@
         listOf<View>(
             view.requireViewById(R.id.screenshot_preview_border),
             view.requireViewById(R.id.screenshot_badge),
-            view.requireViewById(R.id.screenshot_dismiss_button)
+            view.requireViewById(R.id.screenshot_dismiss_button),
         )
     private val fadeUI =
         listOf<View>(
@@ -70,9 +70,11 @@
     fun getEntranceAnimation(
         bounds: Rect,
         showFlash: Boolean,
-        onRevealMilestone: () -> Unit
+        onRevealMilestone: () -> Unit,
     ): Animator {
         val entranceAnimation = AnimatorSet()
+        view.alpha = 1f
+        view.translationX = 0f
 
         val previewAnimator = getPreviewAnimator(bounds)
 
@@ -142,7 +144,7 @@
     fun runLongScreenshotTransition(
         destRect: Rect,
         longScreenshot: ScrollCaptureController.LongScreenshot,
-        onTransitionEnd: Runnable
+        onTransitionEnd: Runnable,
     ): Animator {
         val animSet = AnimatorSet()
 
@@ -165,7 +167,7 @@
             matrix.setScale(currentScale, currentScale)
             matrix.postTranslate(
                 longScreenshot.left * currentScale,
-                longScreenshot.top * currentScale
+                longScreenshot.top * currentScale,
             )
             scrollTransitionPreview.setImageMatrix(matrix)
             val destinationScale: Float = destRect.width() / scrollTransitionPreview.width.toFloat()
@@ -315,7 +317,7 @@
     }
 
     private fun getAdjustedVelocity(requestedVelocity: Float?): Float {
-        return if (requestedVelocity == null) {
+        return if (requestedVelocity == null || abs(requestedVelocity) < .005f) {
             val isLTR = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
             // dismiss to the left in LTR locales, to the right in RTL
             if (isLTR) -MINIMUM_VELOCITY else MINIMUM_VELOCITY
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt
index 61d4489..ecb45e1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt
@@ -25,7 +25,7 @@
 class SwipeGestureListener(
     private val view: View,
     private val onDismiss: (Float?) -> Unit,
-    private val onCancel: () -> Unit
+    private val onCancel: () -> Unit,
 ) {
     private val velocityTracker = VelocityTracker.obtain()
     private val displayMetrics = view.resources.displayMetrics
@@ -54,9 +54,9 @@
                     onDismiss.invoke(xVelocity)
                     return true
                 } else {
-                    velocityTracker.clear()
                     onCancel.invoke()
                 }
+                velocityTracker.clear()
             }
             MotionEvent.ACTION_MOVE -> {
                 velocityTracker.addMovement(ev)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 5896659..2bff7c86 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -30,6 +30,7 @@
 import static com.android.systemui.classifier.Classifier.GENERIC;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 import static com.android.systemui.classifier.Classifier.UNLOCK;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.AOD;
 import static com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING;
 import static com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED;
 import static com.android.systemui.keyguard.shared.model.KeyguardState.GONE;
@@ -1213,6 +1214,16 @@
                     }, mMainDispatcher);
         }
 
+        if (MigrateClocksToBlueprint.isEnabled()) {
+            collectFlow(mView, mKeyguardTransitionInteractor.transition(
+                    Edge.Companion.create(AOD, LOCKSCREEN)),
+                    (TransitionStep step) -> {
+                    if (step.getTransitionState() == TransitionState.FINISHED) {
+                        updateExpandedHeightToMaxHeight();
+                    }
+                }, mMainDispatcher);
+        }
+
         // Ensures that flags are updated when an activity launches
         collectFlow(mView,
                 mShadeAnimationInteractor.isLaunchingActivity(),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
index 65b6231..e5f6846 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
@@ -30,41 +30,47 @@
 fun singleShadeActions(
     requireTwoPointersForTopEdgeForQs: Boolean = false
 ): Array<Pair<UserAction, UserActionResult>> {
+    val shadeUserActionResult = UserActionResult(Scenes.Shade, isIrreversible = true)
+    val qsSceneUserActionResult = UserActionResult(Scenes.QuickSettings, isIrreversible = true)
     return arrayOf(
         // Swiping down, not from the edge, always goes to shade.
-        Swipe.Down to Scenes.Shade,
-        swipeDown(pointerCount = 2) to Scenes.Shade,
+        Swipe.Down to shadeUserActionResult,
+        swipeDown(pointerCount = 2) to shadeUserActionResult,
 
         // Swiping down from the top edge.
         swipeDownFromTop(pointerCount = 1) to
             if (requireTwoPointersForTopEdgeForQs) {
-                Scenes.Shade
+                shadeUserActionResult
             } else {
-                Scenes.QuickSettings
+                qsSceneUserActionResult
             },
-        swipeDownFromTop(pointerCount = 2) to Scenes.QuickSettings,
+        swipeDownFromTop(pointerCount = 2) to qsSceneUserActionResult,
     )
 }
 
 /** Returns collection of [UserAction] to [UserActionResult] pairs for opening the split shade. */
 fun splitShadeActions(): Array<Pair<UserAction, UserActionResult>> {
-    val splitShadeSceneKey = UserActionResult(Scenes.Shade, ToSplitShade)
+    val shadeUserActionResult = UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true)
     return arrayOf(
         // Swiping down, not from the edge, always goes to shade.
-        Swipe.Down to splitShadeSceneKey,
-        swipeDown(pointerCount = 2) to splitShadeSceneKey,
+        Swipe.Down to shadeUserActionResult,
+        swipeDown(pointerCount = 2) to shadeUserActionResult,
         // Swiping down from the top edge goes to QS.
-        swipeDownFromTop(pointerCount = 1) to splitShadeSceneKey,
-        swipeDownFromTop(pointerCount = 2) to splitShadeSceneKey,
+        swipeDownFromTop(pointerCount = 1) to shadeUserActionResult,
+        swipeDownFromTop(pointerCount = 2) to shadeUserActionResult,
     )
 }
 
 /** Returns collection of [UserAction] to [UserActionResult] pairs for opening the dual shade. */
 fun dualShadeActions(): Array<Pair<UserAction, UserActionResult>> {
+    val notifShadeUserActionResult =
+        UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+    val qsShadeuserActionResult =
+        UserActionResult.ShowOverlay(Overlays.QuickSettingsShade, isIrreversible = true)
     return arrayOf(
-        Swipe.Down to Overlays.NotificationsShade,
+        Swipe.Down to notifShadeUserActionResult,
         Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to
-            Overlays.QuickSettingsShade,
+            qsShadeuserActionResult,
     )
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
index cc6e8c2..3113dc4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
@@ -32,6 +32,7 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
 
 /**
  * Models the UI state for the user actions that the user can perform to navigate to other scenes.
@@ -50,7 +51,9 @@
         combine(
                 shadeInteractor.shadeMode,
                 qsSceneAdapter.isCustomizerShowing,
-                sceneBackInteractor.backScene.map { it ?: SceneFamilies.Home },
+                sceneBackInteractor.backScene
+                    .filter { it != Scenes.Shade }
+                    .map { it ?: SceneFamilies.Home },
             ) { shadeMode, isCustomizerShowing, backScene ->
                 buildMap<UserAction, UserActionResult> {
                     if (!isCustomizerShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index e47952f..a79b78f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -58,6 +58,7 @@
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -813,11 +814,17 @@
 
     private void notifyNotificationStateChanged() {
         if (!Looper.getMainLooper().isCurrentThread()) {
-            mMainExecutor.execute(() -> {
+            if (Flags.checkLockscreenGoneTransition()) {
                 for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
-                    listener.onNotificationStateChanged();
+                    mMainExecutor.execute(listener::onNotificationStateChanged);
                 }
-            });
+            } else {
+                mMainExecutor.execute(() -> {
+                    for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
+                        listener.onNotificationStateChanged();
+                    }
+                });
+            }
         } else {
             for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
                 listener.onNotificationStateChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index e3c47a4..321593b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -596,7 +596,8 @@
             // NetworkCapabilities, but we need to convert it into TRANSPORT_WIFI in order to
             // distinguish it from VCN over Cellular.
             if (transportTypes[i] == NetworkCapabilities.TRANSPORT_CELLULAR
-                    && Utils.tryGetWifiInfoForVcn(networkCapabilities) != null) {
+                    && Utils.tryGetWifiInfoForVcn(mConnectivityManager, networkCapabilities)
+                            != null) {
                 transportTypes[i] = NetworkCapabilities.TRANSPORT_WIFI;
                 break;
             }
@@ -1112,7 +1113,9 @@
                     continue;
                 }
                 if (transportType == NetworkCapabilities.TRANSPORT_CELLULAR
-                        && Utils.tryGetWifiInfoForVcn(mLastDefaultNetworkCapabilities) != null) {
+                        && Utils.tryGetWifiInfoForVcn(
+                                        mConnectivityManager, mLastDefaultNetworkCapabilities)
+                                != null) {
                     mConnectedTransports.set(NetworkCapabilities.TRANSPORT_WIFI);
                     if (mLastDefaultNetworkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
                         mValidatedTransports.set(NetworkCapabilities.TRANSPORT_WIFI);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
index 5558ab1..0a7f08d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
@@ -2,11 +2,13 @@
 
 # Bug component: 78010
 
-aioana@google.com
-aroederer@google.com
-iyz@google.com
 jeffdq@google.com
 juliacr@google.com
+
+aioana@google.com
+aroederer@google.com
+asc@google.com
+iyz@google.com
 juliatuttle@google.com
 kurucz@google.com
 liuyining@google.com
@@ -15,4 +17,4 @@
 valiiftime@google.com
 yurilin@google.com
 
-per-file MediaNotificationProcessor.java = ethibodeau@google.com, asc@google.com
+per-file MediaNotificationProcessor.java = ethibodeau@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index cb133ec..e74ed8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -68,11 +68,9 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
 import com.android.systemui.statusbar.notification.row.NotificationGuts;
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository;
 import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel;
 import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel;
 import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel;
 import com.android.systemui.statusbar.notification.stack.PriorityBucket;
 import com.android.systemui.util.ListenerSet;
 
@@ -99,7 +97,7 @@
  * At the moment, there are many things here that shouldn't be and vice-versa. Hopefully we can
  * clean this up in the future.
  */
-public final class NotificationEntry extends ListEntry implements NotificationRowRepository {
+public final class NotificationEntry extends ListEntry {
 
     private final String mKey;
     private StatusBarNotification mSbn;
@@ -161,8 +159,6 @@
             StateFlowKt.MutableStateFlow(null);
     private final MutableStateFlow<CharSequence> mHeadsUpStatusBarTextPublic =
             StateFlowKt.MutableStateFlow(null);
-    private final MutableStateFlow<RichOngoingContentModel> mRichOngoingContentModel =
-            StateFlowKt.MutableStateFlow(null);
 
     // indicates when this entry's view was first attached to a window
     // this value will reset when the view is completely removed from the shade (ie: filtered out)
@@ -969,12 +965,6 @@
         return mHeadsUpStatusBarTextPublic;
     }
 
-    /** Gets the current RON content model, which may be null */
-    @NonNull
-    public StateFlow<RichOngoingContentModel> getRichOngoingContentModel() {
-        return mRichOngoingContentModel;
-    }
-
     /**
      * Sets the text to be displayed on the StatusBar, when this notification is the top pinned
      * heads up, and its content is sensitive right now.
@@ -1069,7 +1059,6 @@
         HeadsUpStatusBarModel headsUpStatusBarModel = contentModel.getHeadsUpStatusBarModel();
         this.mHeadsUpStatusBarText.setValue(headsUpStatusBarModel.getPrivateText());
         this.mHeadsUpStatusBarTextPublic.setValue(headsUpStatusBarModel.getPublicText());
-        this.mRichOngoingContentModel.setValue(contentModel.getRichOngoingContentModel());
     }
 
     /** Information about a suggestion that is being edited. */
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 41419f3..8660cd1 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
@@ -22,15 +22,18 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.Edge;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
@@ -85,6 +88,7 @@
     private boolean mNotifPanelLaunchingActivity;
     private boolean mCommunalShowing = false;
     private boolean mLockscreenShowing = false;
+    private boolean mLockscreenInGoneTransition = false;
 
     private boolean mPipelineRunAllowed;
     private boolean mReorderingAllowed;
@@ -158,6 +162,13 @@
                             KeyguardState.LOCKSCREEN),
                     this::onLockscreenKeyguardStateTransitionValueChanged);
         }
+        if (Flags.checkLockscreenGoneTransition()) {
+            mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.isInTransition(
+                            Edge.create(KeyguardState.LOCKSCREEN, Scenes.Gone),
+                            Edge.create(KeyguardState.LOCKSCREEN, KeyguardState.GONE)),
+                    this::onLockscreenInGoneTransitionChanged);
+        }
+
 
         pipeline.setVisualStabilityManager(mNotifStabilityManager);
     }
@@ -239,7 +250,9 @@
     private void updateAllowedStates(String field, boolean value) {
         boolean wasPipelineRunAllowed = mPipelineRunAllowed;
         boolean wasReorderingAllowed = mReorderingAllowed;
-        mPipelineRunAllowed = !isPanelCollapsingOrLaunchingActivity();
+        // No need to run notification pipeline when the lockscreen is in fading animation.
+        mPipelineRunAllowed = !(isPanelCollapsingOrLaunchingActivity()
+                || (Flags.checkLockscreenGoneTransition() && mLockscreenInGoneTransition));
         mReorderingAllowed = isReorderingAllowed();
         if (wasPipelineRunAllowed != mPipelineRunAllowed
                 || wasReorderingAllowed != mReorderingAllowed) {
@@ -330,7 +343,6 @@
                     updateAllowedStates("fullyDozed", fullyDozed);
                 }
             };
-
     final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
         @Override
         public void onFinishedGoingToSleep() {
@@ -353,6 +365,9 @@
         pw.println("pipelineRunAllowed: " + mPipelineRunAllowed);
         pw.println("  notifPanelCollapsing: " + mNotifPanelCollapsing);
         pw.println("  launchingNotifActivity: " + mNotifPanelLaunchingActivity);
+        if (Flags.checkLockscreenGoneTransition()) {
+            pw.println("  lockscreenInGoneTransition: " + mLockscreenInGoneTransition);
+        }
         pw.println("reorderingAllowed: " + mReorderingAllowed);
         pw.println("  sleepy: " + mSleepy);
         pw.println("  fullyDozed: " + mFullyDozed);
@@ -401,4 +416,15 @@
         mLockscreenShowing = isShowing;
         updateAllowedStates("lockscreenShowing", isShowing);
     }
+
+    private void onLockscreenInGoneTransitionChanged(boolean inGoneTransition) {
+        if (!Flags.checkLockscreenGoneTransition()) {
+            return;
+        }
+        if (inGoneTransition == mLockscreenInGoneTransition) {
+            return;
+        }
+        mLockscreenInGoneTransition = inGoneTransition;
+        updateAllowedStates("lockscreenInGoneTransition", mLockscreenInGoneTransition);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index aa203d7..e25127e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -39,10 +39,10 @@
 @Inject
 constructor(
     private val headsUpRepository: HeadsUpRepository,
-    private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
-    private val shadeInteractor: ShadeInteractor,
+    faceAuthInteractor: DeviceEntryFaceAuthInteractor,
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
+    shadeInteractor: ShadeInteractor,
 ) {
 
     /** The top-ranked heads up row, regardless of pinned state */
@@ -56,8 +56,7 @@
             }
             .distinctUntilChanged()
 
-    /** Set of currently pinned top-level heads up rows to be displayed. */
-    val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> by lazy {
+    private val activeHeadsUpRows: Flow<Set<Pair<HeadsUpRowKey, Boolean>>> by lazy {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
             flowOf(emptySet())
         } else {
@@ -67,9 +66,7 @@
                         repositories.map { repo ->
                             repo.isPinned.map { isPinned -> repo to isPinned }
                         }
-                    combine(toCombine) { pairs ->
-                        pairs.filter { (_, isPinned) -> isPinned }.map { (repo, _) -> repo }.toSet()
-                    }
+                    combine(toCombine) { pairs -> pairs.toSet() }
                 } else {
                     // if the set is empty, there are no flows to combine
                     flowOf(emptySet())
@@ -78,6 +75,26 @@
         }
     }
 
+    /** Set of currently active top-level heads up rows to be displayed. */
+    val activeHeadsUpRowKeys: Flow<Set<HeadsUpRowKey>> by lazy {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+            flowOf(emptySet())
+        } else {
+            activeHeadsUpRows.map { it.map { (repo, _) -> repo }.toSet() }
+        }
+    }
+
+    /** Set of currently pinned top-level heads up rows to be displayed. */
+    val pinnedHeadsUpRowKeys: Flow<Set<HeadsUpRowKey>> by lazy {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+            flowOf(emptySet())
+        } else {
+            activeHeadsUpRows.map {
+                it.filter { (_, isPinned) -> isPinned }.map { (repo, _) -> repo }.toSet()
+            }
+        }
+    }
+
     /** Are there any pinned heads up rows to display? */
     val hasPinnedRows: Flow<Boolean> by lazy {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 8c80fd4..36e3e92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -203,13 +203,16 @@
                 messagingStyle = mConversationProcessor
                         .processNotification(entry, builder, mLogger);
             }
-            result.mInflatedSingleLineViewModel = SingleLineViewInflater
+            SingleLineViewModel viewModel = SingleLineViewInflater
                     .inflateSingleLineViewModel(
                             entry.getSbn().getNotification(),
                             messagingStyle,
                             builder,
                             row.getContext()
                     );
+            // If the messagingStyle is null, we want to inflate the normal view
+            isConversation = viewModel.isConversation();
+            result.mInflatedSingleLineViewModel = viewModel;
             result.mInflatedSingleLineView =
                     SingleLineViewInflater.inflatePrivateSingleLineView(
                             isConversation,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 48c974a..9166e7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -76,8 +76,6 @@
 import com.android.systemui.util.Compile;
 import com.android.systemui.util.DumpUtilsKt;
 
-import kotlinx.coroutines.DisposableHandle;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -116,10 +114,6 @@
     @VisibleForTesting
     protected HybridNotificationView mSingleLineView;
 
-    @Nullable public DisposableHandle mContractedBinderHandle;
-    @Nullable public DisposableHandle mExpandedBinderHandle;
-    @Nullable public DisposableHandle mHeadsUpBinderHandle;
-
     private RemoteInputView mExpandedRemoteInput;
     private RemoteInputView mHeadsUpRemoteInput;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index c342bcd..b166def 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -46,9 +46,6 @@
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
 import com.android.systemui.statusbar.notification.InflationException
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
 import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED
 import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED
 import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP
@@ -71,7 +68,6 @@
 import com.android.systemui.statusbar.notification.row.shared.NewRemoteViews
 import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel
 import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
 import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
@@ -95,8 +91,6 @@
     private val remoteViewCache: NotifRemoteViewCache,
     private val remoteInputManager: NotificationRemoteInputManager,
     private val conversationProcessor: ConversationNotificationProcessor,
-    private val ronExtractor: RichOngoingNotificationContentExtractor,
-    private val ronInflater: RichOngoingNotificationViewInflater,
     @NotifInflation private val inflationExecutor: Executor,
     private val smartReplyStateInflater: SmartReplyStateInflater,
     private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
@@ -144,8 +138,6 @@
                 remoteViewCache,
                 entry,
                 conversationProcessor,
-                ronExtractor,
-                ronInflater,
                 row,
                 bindParams.isMinimized,
                 bindParams.usesIncreasedHeight,
@@ -190,7 +182,6 @@
                 notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
                 headsUpStyleProvider = headsUpStyleProvider,
                 conversationProcessor = conversationProcessor,
-                ronExtractor = ronExtractor,
                 logger = logger,
             )
         inflateSmartReplyViews(
@@ -282,22 +273,16 @@
         when (inflateFlag) {
             FLAG_CONTENT_VIEW_CONTRACTED ->
                 row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_CONTRACTED) {
-                    row.privateLayout.mContractedBinderHandle?.dispose()
-                    row.privateLayout.mContractedBinderHandle = null
                     row.privateLayout.setContractedChild(null)
                     remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)
                 }
             FLAG_CONTENT_VIEW_EXPANDED ->
                 row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_EXPANDED) {
-                    row.privateLayout.mExpandedBinderHandle?.dispose()
-                    row.privateLayout.mExpandedBinderHandle = null
                     row.privateLayout.setExpandedChild(null)
                     remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
                 }
             FLAG_CONTENT_VIEW_HEADS_UP ->
                 row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_HEADSUP) {
-                    row.privateLayout.mHeadsUpBinderHandle?.dispose()
-                    row.privateLayout.mHeadsUpBinderHandle = null
                     row.privateLayout.setHeadsUpChild(null)
                     remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
                     row.privateLayout.setHeadsUpInflatedSmartReplies(null)
@@ -378,8 +363,6 @@
         private val remoteViewCache: NotifRemoteViewCache,
         private val entry: NotificationEntry,
         private val conversationProcessor: ConversationNotificationProcessor,
-        private val ronExtractor: RichOngoingNotificationContentExtractor,
-        private val ronInflater: RichOngoingNotificationViewInflater,
         private val row: ExpandableNotificationRow,
         private val isMinimized: Boolean,
         private val usesIncreasedHeight: Boolean,
@@ -459,7 +442,6 @@
                     notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
                     headsUpStyleProvider = headsUpStyleProvider,
                     conversationProcessor = conversationProcessor,
-                    ronExtractor = ronExtractor,
                     logger = logger
                 )
             logger.logAsyncTaskProgress(
@@ -506,90 +488,6 @@
                     }
             }
 
-            val richOngoingContentModel = inflationProgress.contentModel.richOngoingContentModel
-
-            if (
-                richOngoingContentModel != null &&
-                    reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0
-            ) {
-                logger.logAsyncTaskProgress(entry, "inflating RON view")
-                val inflateContractedView = reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0
-                val inflateExpandedView = reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0
-                val inflateHeadsUpView = reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0
-
-                inflationProgress.contractedRichOngoingNotificationViewHolder =
-                    if (inflateContractedView) {
-                        ronInflater.inflateView(
-                            contentModel = richOngoingContentModel,
-                            existingView = row.privateLayout.contractedChild,
-                            entry = entry,
-                            systemUiContext = context,
-                            parentView = row.privateLayout,
-                            viewType = RichOngoingNotificationViewType.Contracted
-                        )
-                    } else {
-                        if (
-                            ronInflater.canKeepView(
-                                contentModel = richOngoingContentModel,
-                                existingView = row.privateLayout.contractedChild,
-                                viewType = RichOngoingNotificationViewType.Contracted
-                            )
-                        ) {
-                            KeepExistingView
-                        } else {
-                            NullContentView
-                        }
-                    }
-
-                inflationProgress.expandedRichOngoingNotificationViewHolder =
-                    if (inflateExpandedView) {
-                        ronInflater.inflateView(
-                            contentModel = richOngoingContentModel,
-                            existingView = row.privateLayout.expandedChild,
-                            entry = entry,
-                            systemUiContext = context,
-                            parentView = row.privateLayout,
-                            viewType = RichOngoingNotificationViewType.Expanded
-                        )
-                    } else {
-                        if (
-                            ronInflater.canKeepView(
-                                contentModel = richOngoingContentModel,
-                                existingView = row.privateLayout.expandedChild,
-                                viewType = RichOngoingNotificationViewType.Expanded
-                            )
-                        ) {
-                            KeepExistingView
-                        } else {
-                            NullContentView
-                        }
-                    }
-
-                inflationProgress.headsUpRichOngoingNotificationViewHolder =
-                    if (inflateHeadsUpView) {
-                        ronInflater.inflateView(
-                            contentModel = richOngoingContentModel,
-                            existingView = row.privateLayout.headsUpChild,
-                            entry = entry,
-                            systemUiContext = context,
-                            parentView = row.privateLayout,
-                            viewType = RichOngoingNotificationViewType.HeadsUp
-                        )
-                    } else {
-                        if (
-                            ronInflater.canKeepView(
-                                contentModel = richOngoingContentModel,
-                                existingView = row.privateLayout.headsUpChild,
-                                viewType = RichOngoingNotificationViewType.HeadsUp
-                            )
-                        ) {
-                            KeepExistingView
-                        } else {
-                            NullContentView
-                        }
-                    }
-            }
-
             logger.logAsyncTaskProgress(entry, "getting row image resolver (on wrong thread!)")
             val imageResolver = row.imageResolver
             // wait for image resolver to finish preloading
@@ -695,9 +593,6 @@
         var inflatedSmartReplyState: InflatedSmartReplyState? = null
         var expandedInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
         var headsUpInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
-        var contractedRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
-        var expandedRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
-        var headsUpRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
 
         // Inflated SingleLineView that lacks the UI State
         var inflatedSingleLineView: HybridNotificationView? = null
@@ -734,7 +629,6 @@
             val inflateHeadsUp =
                 (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0 &&
                     result.remoteViews.headsUp != null)
-
             if (inflateContracted || inflateExpanded || inflateHeadsUp) {
                 logger.logAsyncTaskProgress(entry, "inflating contracted smart reply state")
                 result.inflatedSmartReplyState = inflater.inflateSmartReplyState(entry)
@@ -776,7 +670,6 @@
             notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
             headsUpStyleProvider: HeadsUpStyleProvider,
             conversationProcessor: ConversationNotificationProcessor,
-            ronExtractor: RichOngoingNotificationContentExtractor,
             logger: NotificationRowContentBinderLogger
         ): InflationProgress {
             // process conversations and extract the messaging style
@@ -785,24 +678,9 @@
                     conversationProcessor.processNotification(entry, builder, logger)
                 } else null
 
-            val richOngoingContentModel =
-                if (reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0) {
-                    ronExtractor.extractContentModel(
-                        entry = entry,
-                        builder = builder,
-                        systemUIContext = systemUIContext,
-                        packageContext = packageContext
-                    )
-                } else {
-                    // if we're not re-inflating any RON views, make sure the model doesn't change
-                    entry.richOngoingContentModel.value
-                }
-
-            val remoteViewsFlags = getRemoteViewsFlags(reInflateFlags, richOngoingContentModel)
-
             val remoteViews =
                 createRemoteViews(
-                    reInflateFlags = remoteViewsFlags,
+                    reInflateFlags = reInflateFlags,
                     builder = builder,
                     isMinimized = isMinimized,
                     usesIncreasedHeight = usesIncreasedHeight,
@@ -850,7 +728,6 @@
                     headsUpStatusBarModel = headsUpStatusBarModel,
                     singleLineViewModel = singleLineViewModel,
                     publicSingleLineViewModel = publicSingleLineViewModel,
-                    richOngoingContentModel = richOngoingContentModel,
                 )
 
             return InflationProgress(
@@ -1506,31 +1383,11 @@
             }
             logger.logAsyncTaskProgress(entry, "finishing")
 
-            // before updating the content model, stop existing binding if necessary
-            if (result.contractedRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
-                row.privateLayout.mContractedBinderHandle?.dispose()
-                row.privateLayout.mContractedBinderHandle = null
-            }
-
-            if (result.expandedRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
-                row.privateLayout.mExpandedBinderHandle?.dispose()
-                row.privateLayout.mExpandedBinderHandle = null
-            }
-
-            if (result.headsUpRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
-                row.privateLayout.mHeadsUpBinderHandle?.dispose()
-                row.privateLayout.mHeadsUpBinderHandle = null
-            }
-
-            // set the content model after disposal and before setting new rich ongoing view
             entry.setContentModel(result.contentModel)
             result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
 
-            // set normal remote views (skipping rich ongoing states when that model exists)
-            val remoteViewsFlags =
-                getRemoteViewsFlags(reInflateFlags, result.contentModel.richOngoingContentModel)
             setContentViewsFromRemoteViews(
-                remoteViewsFlags,
+                reInflateFlags,
                 entry,
                 remoteViewCache,
                 result,
@@ -1538,7 +1395,6 @@
                 isMinimized,
             )
 
-            // set single line view
             if (
                 AsyncHybridViewInflation.isEnabled &&
                     reInflateFlags and FLAG_CONTENT_VIEW_SINGLE_LINE != 0
@@ -1563,55 +1419,6 @@
                 }
             }
 
-            val hasRichOngoingViewHolder =
-                result.contractedRichOngoingNotificationViewHolder != null ||
-                    result.expandedRichOngoingNotificationViewHolder != null ||
-                    result.headsUpRichOngoingNotificationViewHolder != null
-
-            if (hasRichOngoingViewHolder) {
-                // after updating the content model, set the view, then start the new binder
-                result.contractedRichOngoingNotificationViewHolder?.let { contractedViewHolder ->
-                    if (contractedViewHolder is InflatedContentViewHolder) {
-                        row.privateLayout.contractedChild = contractedViewHolder.view
-                        row.privateLayout.mContractedBinderHandle =
-                            contractedViewHolder.binder.setupContentViewBinder()
-                    } else if (contractedViewHolder == NullContentView) {
-                        row.privateLayout.contractedChild = null
-                    }
-                }
-
-                result.expandedRichOngoingNotificationViewHolder?.let { expandedViewHolder ->
-                    if (expandedViewHolder is InflatedContentViewHolder) {
-                        row.privateLayout.expandedChild = expandedViewHolder.view
-                        row.privateLayout.mExpandedBinderHandle =
-                            expandedViewHolder.binder.setupContentViewBinder()
-                    } else if (expandedViewHolder == NullContentView) {
-                        row.privateLayout.expandedChild = null
-                    }
-                }
-
-                result.headsUpRichOngoingNotificationViewHolder?.let { headsUpViewHolder ->
-                    if (headsUpViewHolder is InflatedContentViewHolder) {
-                        row.privateLayout.headsUpChild = headsUpViewHolder.view
-                        row.privateLayout.mHeadsUpBinderHandle =
-                            headsUpViewHolder.binder.setupContentViewBinder()
-                    } else if (headsUpViewHolder == NullContentView) {
-                        row.privateLayout.headsUpChild = null
-                    }
-                }
-
-                // clean remoteViewCache when we don't keep existing views.
-                remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)
-                remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
-                remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
-
-                // Since RONs don't support smart reply, remove them from HUNs and Expanded.
-                row.privateLayout.setExpandedInflatedSmartReplies(null)
-                row.privateLayout.setHeadsUpInflatedSmartReplies(null)
-
-                row.setExpandable(row.privateLayout.expandedChild != null)
-            }
-
             Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row))
             endListener?.onAsyncInflationFinished(entry)
             return true
@@ -1775,21 +1582,6 @@
                     !oldView.hasFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED)
         }
 
-        @InflationFlag
-        private fun getRemoteViewsFlags(
-            @InflationFlag reInflateFlags: Int,
-            richOngoingContentModel: RichOngoingContentModel?
-        ): Int =
-            if (richOngoingContentModel != null) {
-                reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING.inv()
-            } else {
-                reInflateFlags
-            }
-
-        @InflationFlag
-        private const val CONTENT_VIEWS_TO_CREATE_RICH_ONGOING =
-            FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
-
         private const val ASYNC_TASK_TRACE_METHOD =
             "NotificationRowContentBinderImpl.AsyncInflationTask"
         private const val APPLY_TRACE_METHOD = "NotificationRowContentBinderImpl#apply"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index c630c4d..84f2f66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -18,8 +18,6 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag;
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.RichOngoingViewModelComponent;
 
 import dagger.Binds;
 import dagger.Module;
@@ -30,7 +28,7 @@
 /**
  * Dagger Module containing notification row and view inflation implementations.
  */
-@Module(subcomponents = {RichOngoingViewModelComponent.class})
+@Module
 public abstract class NotificationRowModule {
 
     /**
@@ -49,25 +47,6 @@
         }
     }
 
-    /** Provides ron content model extractor. */
-    @Provides
-    @SysUISingleton
-    public static RichOngoingNotificationContentExtractor provideRonContentExtractor(
-            Provider<RichOngoingNotificationContentExtractorImpl> realImpl
-    ) {
-        if (RichOngoingNotificationFlag.isEnabled()) {
-            return realImpl.get();
-        } else {
-            return new NoOpRichOngoingNotificationContentExtractor();
-        }
-    }
-
-    /** Provides ron view inflater. */
-    @Binds
-    @SysUISingleton
-    public abstract RichOngoingNotificationViewInflater provideRonViewInflater(
-            RichOngoingNotificationViewInflaterImpl impl);
-
     /**
      * Provides notification remote view cache instance.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
deleted file mode 100644
index ec5ebc36..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row
-
-import android.app.Notification
-import android.app.PendingIntent
-import android.content.Context
-import android.util.Log
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
-import com.android.systemui.statusbar.notification.row.shared.IconModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
-import java.time.Duration
-import java.time.LocalDate
-import java.time.LocalDateTime
-import java.time.LocalTime
-import java.time.ZoneId
-import javax.inject.Inject
-
-/**
- * Interface which provides a [RichOngoingContentModel] for a given [Notification] when one is
- * applicable to the given style.
- */
-interface RichOngoingNotificationContentExtractor {
-    fun extractContentModel(
-        entry: NotificationEntry,
-        builder: Notification.Builder,
-        systemUIContext: Context,
-        packageContext: Context,
-    ): RichOngoingContentModel?
-}
-
-class NoOpRichOngoingNotificationContentExtractor : RichOngoingNotificationContentExtractor {
-    override fun extractContentModel(
-        entry: NotificationEntry,
-        builder: Notification.Builder,
-        systemUIContext: Context,
-        packageContext: Context,
-    ): RichOngoingContentModel? = null
-}
-
-@SysUISingleton
-class RichOngoingNotificationContentExtractorImpl @Inject constructor() :
-    RichOngoingNotificationContentExtractor {
-
-    init {
-        /* check if */ RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()
-    }
-
-    override fun extractContentModel(
-        entry: NotificationEntry,
-        builder: Notification.Builder,
-        systemUIContext: Context,
-        packageContext: Context,
-    ): RichOngoingContentModel? {
-        val sbn = entry.sbn
-        val notification = sbn.notification
-        val icon = IconModel(notification.smallIcon)
-
-        try {
-            return if (sbn.packageName == "com.google.android.deskclock") {
-                when (notification.channelId) {
-                    "Timers v2" -> {
-                        parseTimerNotification(notification, icon)
-                    }
-                    "Stopwatch v2" -> {
-                        Log.i("RONs", "Can't process stopwatch yet")
-                        null
-                    }
-                    else -> {
-                        Log.i("RONs", "Can't process channel '${notification.channelId}'")
-                        null
-                    }
-                }
-            } else if (builder.style is Notification.ProgressStyle) {
-                parseEnRouteNotification(notification, icon)
-            } else null
-        } catch (e: Exception) {
-            Log.e("RONs", "Error parsing RON", e)
-            return null
-        }
-    }
-
-    /**
-     * FOR PROTOTYPING ONLY: create a RON TimerContentModel using the time information available
-     * inside the sortKey of the clock app's timer notifications.
-     */
-    private fun parseTimerNotification(
-        notification: Notification,
-        icon: IconModel,
-    ): TimerContentModel {
-        // sortKey=1 0|↺7|RUNNING|▶16:21:58.523|Σ0:05:00|Δ0:00:03|⏳0:04:57
-        // sortKey=1 0|↺7|PAUSED|Σ0:05:00|Δ0:04:54|⏳0:00:06
-        // sortKey=1 1|↺7|RUNNING|▶16:30:28.433|Σ0:04:05|Δ0:00:06|⏳0:03:59
-        // sortKey=1 0|↺7|RUNNING|▶16:36:18.350|Σ0:05:00|Δ0:01:42|⏳0:03:18
-        // sortKey=1 2|↺7|RUNNING|▶16:38:37.816|Σ0:02:00|Δ0:01:09|⏳0:00:51
-        // ▶ = "current" time (when updated)
-        // Σ = total time
-        // Δ = time elapsed
-        // ⏳ = time remaining
-        val sortKey = notification.sortKey
-        val (_, _, state, extra) = sortKey.split("|", limit = 4)
-        return when (state) {
-            "PAUSED" -> {
-                val (total, _, remaining) = extra.split("|")
-                val timeRemaining = parseTimeDelta(remaining)
-                TimerContentModel(
-                    icon = icon,
-                    // TODO: b/352142761 - define and use a string resource rather than " Timer".
-                    // (The UX isn't final so using " Timer" for now).
-                    name = total.replace("Σ", "") + " Timer",
-                    state =
-                        TimerContentModel.TimerState.Paused(
-                            timeRemaining = timeRemaining,
-                            resumeIntent = notification.findStartIntent(),
-                            addMinuteAction = notification.findAddMinuteAction(),
-                            resetAction = notification.findResetAction(),
-                        ),
-                )
-            }
-            "RUNNING" -> {
-                val (current, total, _, remaining) = extra.split("|")
-                val finishTime = parseCurrentTime(current) + parseTimeDelta(remaining).toMillis()
-                TimerContentModel(
-                    icon = icon,
-                    // TODO: b/352142761 - define and use a string resource rather than " Timer".
-                    // (The UX isn't final so using " Timer" for now).
-                    name = total.replace("Σ", "") + " Timer",
-                    state =
-                        TimerContentModel.TimerState.Running(
-                            finishTime = finishTime,
-                            pauseIntent = notification.findPauseIntent(),
-                            addMinuteAction = notification.findAddMinuteAction(),
-                            resetAction = notification.findResetAction(),
-                        ),
-                )
-            }
-            else -> error("unknown state ($state) in sortKey=$sortKey")
-        }
-    }
-
-    private fun Notification.findPauseIntent(): PendingIntent? {
-        return actions
-            .firstOrNull { it.actionIntent.intent?.action?.endsWith(".PAUSE_TIMER") == true }
-            ?.actionIntent
-    }
-
-    private fun Notification.findStartIntent(): PendingIntent? {
-        return actions
-            .firstOrNull { it.actionIntent.intent?.action?.endsWith(".START_TIMER") == true }
-            ?.actionIntent
-    }
-
-    // TODO: b/352142761 - switch to system attributes for label and icon.
-    //   - We probably want a consistent look for the Reset button. (Double check with UX.)
-    //   - Using the custom assets now since I couldn't an existing "Reset" icon.
-    private fun Notification.findResetAction(): Notification.Action? {
-        return actions.firstOrNull {
-            it.actionIntent.intent?.action?.endsWith(".RESET_TIMER") == true
-        }
-    }
-
-    // TODO: b/352142761 - check with UX on whether this should be required.
-    //   - Alternative is to allow for optional actions in addition to main and reset.
-    //   - For optional actions, we should take the custom label and icon.
-    private fun Notification.findAddMinuteAction(): Notification.Action? {
-        return actions.firstOrNull {
-            it.actionIntent.intent?.action?.endsWith(".ADD_MINUTE_TIMER") == true
-        }
-    }
-
-    private fun parseCurrentTime(current: String): Long {
-        val (hour, minute, second, millis) = current.replace("▶", "").split(":", ".")
-        // NOTE: this won't work correctly at/around midnight.  It's just for prototyping.
-        val localDateTime =
-            LocalDateTime.of(
-                LocalDate.now(),
-                LocalTime.of(hour.toInt(), minute.toInt(), second.toInt(), millis.toInt() * 1000000),
-            )
-        val offset = ZoneId.systemDefault().rules.getOffset(localDateTime)
-        return localDateTime.toInstant(offset).toEpochMilli()
-    }
-
-    private fun parseTimeDelta(delta: String): Duration {
-        val (hour, minute, second) = delta.replace("Σ", "").replace("⏳", "").split(":")
-        return Duration.ofHours(hour.toLong())
-            .plusMinutes(minute.toLong())
-            .plusSeconds(second.toLong())
-    }
-
-    private fun parseEnRouteNotification(
-        notification: Notification,
-        icon: IconModel,
-    ): EnRouteContentModel {
-        return EnRouteContentModel(
-            smallIcon = icon,
-            title = notification.extras.getCharSequence(Notification.EXTRA_TITLE),
-            text = notification.extras.getCharSequence(Notification.EXTRA_TEXT),
-        )
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
deleted file mode 100644
index 77c4130..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row
-
-import android.app.Notification
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
-import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
-import com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
-import com.android.systemui.statusbar.notification.row.ui.view.TimerView
-import com.android.systemui.statusbar.notification.row.ui.viewbinder.EnRouteViewBinder
-import com.android.systemui.statusbar.notification.row.ui.viewbinder.TimerViewBinder
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.EnRouteViewModel
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.RichOngoingViewModelComponent
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.TimerViewModel
-import javax.inject.Inject
-import kotlinx.coroutines.DisposableHandle
-
-fun interface DeferredContentViewBinder {
-    fun setupContentViewBinder(): DisposableHandle
-}
-
-enum class RichOngoingNotificationViewType {
-    Contracted,
-    Expanded,
-    HeadsUp,
-}
-
-/**
- * * Supertype of the 3 different possible result types of
- *   [RichOngoingNotificationViewInflater.inflateView].
- */
-sealed interface ContentViewInflationResult {
-
-    /** Indicates that the content view should be removed if present. */
-    data object NullContentView : ContentViewInflationResult
-
-    /**
-     * Indicates that the content view (which *must be* present) should be unmodified during this
-     * inflation.
-     */
-    data object KeepExistingView : ContentViewInflationResult
-
-    /**
-     * Contains the new view and binder that should replace any existing content view for this slot.
-     */
-    data class InflatedContentViewHolder(val view: View, val binder: DeferredContentViewBinder) :
-        ContentViewInflationResult
-}
-
-fun ContentViewInflationResult?.shouldDisposeViewBinder() = this !is KeepExistingView
-
-/**
- * Interface which provides a [RichOngoingContentModel] for a given [Notification] when one is
- * applicable to the given style.
- */
-interface RichOngoingNotificationViewInflater {
-    fun inflateView(
-        contentModel: RichOngoingContentModel,
-        existingView: View?,
-        entry: NotificationEntry,
-        systemUiContext: Context,
-        parentView: ViewGroup,
-        viewType: RichOngoingNotificationViewType,
-    ): ContentViewInflationResult
-
-    fun canKeepView(
-        contentModel: RichOngoingContentModel,
-        existingView: View?,
-        viewType: RichOngoingNotificationViewType
-    ): Boolean
-}
-
-@SysUISingleton
-class RichOngoingNotificationViewInflaterImpl
-@Inject
-constructor(
-    private val viewModelComponentFactory: RichOngoingViewModelComponent.Factory,
-) : RichOngoingNotificationViewInflater {
-
-    override fun inflateView(
-        contentModel: RichOngoingContentModel,
-        existingView: View?,
-        entry: NotificationEntry,
-        systemUiContext: Context,
-        parentView: ViewGroup,
-        viewType: RichOngoingNotificationViewType,
-    ): ContentViewInflationResult {
-        if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return NullContentView
-        val component = viewModelComponentFactory.create(entry)
-        return when (contentModel) {
-            is TimerContentModel ->
-                inflateTimerView(
-                    existingView,
-                    component::createTimerViewModel,
-                    systemUiContext,
-                    parentView,
-                    viewType
-                )
-            is EnRouteContentModel ->
-                inflateEnRouteView(
-                    existingView,
-                    component::createEnRouteViewModel,
-                    systemUiContext,
-                    parentView,
-                    viewType
-                )
-            else -> TODO("Not yet implemented")
-        }
-    }
-
-    override fun canKeepView(
-        contentModel: RichOngoingContentModel,
-        existingView: View?,
-        viewType: RichOngoingNotificationViewType
-    ): Boolean {
-        if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return false
-        return when (contentModel) {
-            is TimerContentModel -> canKeepTimerView(contentModel, existingView, viewType)
-            is EnRouteContentModel -> canKeepEnRouteView(contentModel, existingView, viewType)
-            else -> TODO("Not yet implemented")
-        }
-    }
-
-    private fun inflateTimerView(
-        existingView: View?,
-        createViewModel: () -> TimerViewModel,
-        systemUiContext: Context,
-        parentView: ViewGroup,
-        viewType: RichOngoingNotificationViewType,
-    ): ContentViewInflationResult {
-        if (existingView is TimerView && !existingView.isReinflateNeeded()) return KeepExistingView
-
-        return when (viewType) {
-            RichOngoingNotificationViewType.Contracted -> {
-                val newView =
-                    LayoutInflater.from(systemUiContext)
-                        .inflate(
-                            R.layout.rich_ongoing_timer_notification,
-                            parentView,
-                            /* attachToRoot= */ false
-                        ) as TimerView
-                InflatedContentViewHolder(newView) {
-                    TimerViewBinder.bindWhileAttached(newView, createViewModel())
-                }
-            }
-            RichOngoingNotificationViewType.Expanded,
-            RichOngoingNotificationViewType.HeadsUp -> NullContentView
-        }
-    }
-
-    private fun canKeepTimerView(
-        contentModel: TimerContentModel,
-        existingView: View?,
-        viewType: RichOngoingNotificationViewType
-    ): Boolean = true
-
-    private fun inflateEnRouteView(
-        existingView: View?,
-        createViewModel: () -> EnRouteViewModel,
-        systemUiContext: Context,
-        parentView: ViewGroup,
-        viewType: RichOngoingNotificationViewType,
-    ): ContentViewInflationResult {
-        if (existingView is EnRouteView && !existingView.isReinflateNeeded())
-            return KeepExistingView
-        return when (viewType) {
-            RichOngoingNotificationViewType.Contracted -> {
-                val newView =
-                    LayoutInflater.from(systemUiContext)
-                        .inflate(
-                            R.layout.notification_template_en_route_contracted,
-                            parentView,
-                            /* attachToRoot= */ false
-                        ) as EnRouteView
-                InflatedContentViewHolder(newView) {
-                    EnRouteViewBinder.bindWhileAttached(newView, createViewModel())
-                }
-            }
-            RichOngoingNotificationViewType.Expanded -> {
-                val newView =
-                    LayoutInflater.from(systemUiContext)
-                        .inflate(
-                            R.layout.notification_template_en_route_expanded,
-                            parentView,
-                            /* attachToRoot= */ false
-                        ) as EnRouteView
-                InflatedContentViewHolder(newView) {
-                    EnRouteViewBinder.bindWhileAttached(newView, createViewModel())
-                }
-            }
-            RichOngoingNotificationViewType.HeadsUp -> NullContentView
-        }
-    }
-
-    private fun canKeepEnRouteView(
-        contentModel: EnRouteContentModel,
-        existingView: View?,
-        viewType: RichOngoingNotificationViewType
-    ): Boolean = true
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepository.kt
deleted file mode 100644
index bac887b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepository.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.data.repository
-
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
-import kotlinx.coroutines.flow.StateFlow
-
-/** A repository of states relating to a specific notification row. */
-interface NotificationRowRepository {
-    /**
-     * A flow of an immutable data class with the current state of the Rich Ongoing Notification
-     * content, if applicable.
-     */
-    val richOngoingContentModel: StateFlow<RichOngoingContentModel?>
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt
deleted file mode 100644
index 72823a7..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- *
- */
-
-package com.android.systemui.statusbar.notification.row.domain.interactor
-
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
-import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filterIsInstance
-
-/** Interactor specific to a particular notification row. */
-class NotificationRowInteractor @Inject constructor(repository: NotificationRowRepository) {
-    /** Content of a rich ongoing timer notification. */
-    val timerContentModel: Flow<TimerContentModel> =
-        repository.richOngoingContentModel.filterIsInstance<TimerContentModel>()
-
-    /** Content of a rich ongoing timer notification. */
-    val enRouteContentModel: Flow<EnRouteContentModel> =
-        repository.richOngoingContentModel.filterIsInstance<EnRouteContentModel>()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt
deleted file mode 100644
index 7e78cca..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.shared
-
-/**
- * Represents something en route.
- *
- * @param smallIcon the main small icon of the EnRoute notification.
- * @param title the title of the EnRoute notification.
- * @param text the text of the EnRoute notification.
- */
-data class EnRouteContentModel(
-    val smallIcon: IconModel,
-    val title: CharSequence?,
-    val text: CharSequence?,
-) : RichOngoingContentModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/IconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/IconModel.kt
deleted file mode 100644
index e611938..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/IconModel.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.shared
-
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.Icon
-
-// TODO: figure out how to support lazy resolution of the drawable, e.g. on unrelated text change
-class IconModel(val icon: Icon) {
-    var drawable: Drawable? = null
-
-    override fun equals(other: Any?): Boolean =
-        when (other) {
-            null -> false
-            (other === this) -> true
-            !is IconModel -> false
-            else -> other.icon.sameAs(icon)
-        }
-
-    override fun toString(): String = "IconModel(icon=$icon, drawable=$drawable)"
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt
index 0f9a5a3..004c66b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt
@@ -22,7 +22,4 @@
     val headsUpStatusBarModel: HeadsUpStatusBarModel,
     val singleLineViewModel: SingleLineViewModel? = null,
     val publicSingleLineViewModel: SingleLineViewModel? = null,
-    val richOngoingContentModel: RichOngoingContentModel? = null,
 )
-
-sealed interface RichOngoingContentModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt
deleted file mode 100644
index 33b2564..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.shared
-
-import android.app.Notification
-import android.app.PendingIntent
-import java.time.Duration
-
-/**
- * Represents a simple timer that counts down to a time.
- *
- * @param name the label for the timer
- * @param state state of the timer, including time and whether it is paused or running
- */
-data class TimerContentModel(
-    val icon: IconModel,
-    val name: String,
-    val state: TimerState,
-) : RichOngoingContentModel {
-    /** The state (paused or running) of the timer, and relevant time */
-    sealed interface TimerState {
-        val addMinuteAction: Notification.Action?
-        val resetAction: Notification.Action?
-
-        /**
-         * Indicates a running timer
-         *
-         * @param finishTime the time in ms since epoch that the timer will finish
-         * @param pauseIntent the action for pausing the timer
-         */
-        data class Running(
-            val finishTime: Long,
-            val pauseIntent: PendingIntent?,
-            override val addMinuteAction: Notification.Action?,
-            override val resetAction: Notification.Action?,
-        ) : TimerState
-
-        /**
-         * Indicates a paused timer
-         *
-         * @param timeRemaining the time in ms remaining on the paused timer
-         * @param resumeIntent the action for resuming the timer
-         */
-        data class Paused(
-            val timeRemaining: Duration,
-            val resumeIntent: PendingIntent?,
-            override val addMinuteAction: Notification.Action?,
-            override val resetAction: Notification.Action?,
-        ) : TimerState
-    }
-}
-
-/**
- * Represents a simple stopwatch that counts up and allows tracking laps.
- *
- * @param state state of the stopwatch, including time and whether it is paused or running
- * @param lapDurations a list of durations of each completed lap
- */
-data class StopwatchContentModel(
-    val icon: IconModel,
-    val state: StopwatchState,
-    val lapDurations: List<Long>,
-) : RichOngoingContentModel {
-    /** The state (paused or running) of the stopwatch, and relevant time */
-    sealed interface StopwatchState {
-        /**
-         * Indicates a running stopwatch
-         *
-         * @param startTime the time in ms since epoch that the stopwatch started, plus any
-         *   accumulated pause time
-         * @param pauseIntent the action for pausing the stopwatch
-         */
-        data class Running(
-            val startTime: Long,
-            val pauseIntent: PendingIntent,
-        ) : StopwatchState
-
-        /**
-         * Indicates a paused stopwatch
-         *
-         * @param timeElapsed the time in ms elapsed on the stopwatch
-         * @param resumeIntent the action for resuming the stopwatch
-         */
-        data class Paused(
-            val timeElapsed: Duration,
-            val resumeIntent: PendingIntent,
-        ) : StopwatchState
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingNotificationFlag.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingNotificationFlag.kt
deleted file mode 100644
index 4a7f7cd..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingNotificationFlag.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.shared
-
-import android.app.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the api rich ongoing flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object RichOngoingNotificationFlag {
-    /** The aconfig flag name */
-    const val FLAG_NAME = Flags.FLAG_API_RICH_ONGOING
-
-    /** A token used for dependency declaration */
-    val token: FlagToken
-        get() = FlagToken(FLAG_NAME, isEnabled)
-
-    /** Is the refactor enabled */
-    @JvmStatic
-    inline val isEnabled
-        get() = Flags.apiRichOngoing()
-
-    /**
-     * Called to ensure code is only run when the flag is enabled. This protects users from the
-     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
-     * build to ensure that the refactor author catches issues in testing.
-     */
-    @JvmStatic
-    inline fun isUnexpectedlyInLegacyMode() =
-        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
-
-    /**
-     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
-     * the flag is enabled to ensure that the refactor author catches issues in testing.
-     */
-    @JvmStatic
-    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/ConfigurationTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/ConfigurationTracker.kt
deleted file mode 100644
index 95c507c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/ConfigurationTracker.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.view
-
-import android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS
-import android.content.pm.ActivityInfo.CONFIG_DENSITY
-import android.content.pm.ActivityInfo.CONFIG_FONT_SCALE
-import android.content.pm.ActivityInfo.CONFIG_LAYOUT_DIRECTION
-import android.content.pm.ActivityInfo.CONFIG_LOCALE
-import android.content.pm.ActivityInfo.CONFIG_UI_MODE
-import android.content.res.Configuration
-import android.content.res.Resources
-
-/**
- * Tracks the active configuration when constructed and returns (when queried) whether the
- * configuration has unhandled changes.
- */
-class ConfigurationTracker(
-    private val resources: Resources,
-    private val unhandledConfigChanges: Int
-) {
-    private val initialConfig = Configuration(resources.configuration)
-
-    constructor(
-        resources: Resources,
-        handlesDensityFontScale: Boolean = false,
-        handlesTheme: Boolean = false,
-        handlesLocaleAndLayout: Boolean = true,
-    ) : this(
-        resources,
-        unhandledConfigChanges =
-            (if (handlesDensityFontScale) 0 else CONFIG_DENSITY or CONFIG_FONT_SCALE) or
-                (if (handlesTheme) 0 else CONFIG_ASSETS_PATHS or CONFIG_UI_MODE) or
-                (if (handlesLocaleAndLayout) 0 else CONFIG_LOCALE or CONFIG_LAYOUT_DIRECTION)
-    )
-
-    /**
-     * Whether the current configuration has unhandled changes relative to the initial configuration
-     */
-    fun hasUnhandledConfigChange(): Boolean =
-        initialConfig.diff(resources.configuration) and unhandledConfigChanges != 0
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt
deleted file mode 100644
index e5c2b5f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.view
-
-import android.content.Context
-import android.graphics.drawable.Icon
-import android.util.AttributeSet
-import android.widget.FrameLayout
-import android.widget.ImageView
-import android.widget.TextView
-import com.android.internal.R
-import com.android.internal.widget.NotificationExpandButton
-
-class EnRouteView
-@JvmOverloads
-constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    defStyleAttr: Int = 0,
-    defStyleRes: Int = 0,
-) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
-
-    private val configTracker = ConfigurationTracker(resources)
-
-    private lateinit var icon: ImageView
-    private lateinit var title: TextView
-    private lateinit var text: TextView
-    private lateinit var expandButton: NotificationExpandButton
-
-    override fun onFinishInflate() {
-        super.onFinishInflate()
-        icon = requireViewById(R.id.icon)
-        title = requireViewById(R.id.title)
-        text = requireViewById(R.id.text)
-
-        expandButton = requireViewById(R.id.expand_button)
-        expandButton.setExpanded(false)
-    }
-
-    /** the resources configuration has changed such that the view needs to be reinflated */
-    fun isReinflateNeeded(): Boolean = configTracker.hasUnhandledConfigChange()
-
-    fun setIcon(icon: Icon?) {
-        this.icon.setImageIcon(icon)
-    }
-
-    fun setTitle(title: CharSequence?) {
-        this.title.text = title
-    }
-
-    fun setText(text: CharSequence?) {
-        this.text.text = text
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt
deleted file mode 100644
index 8c95187..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.view
-
-import android.annotation.DrawableRes
-import android.content.Context
-import android.graphics.BlendMode
-import android.util.AttributeSet
-import com.android.internal.widget.EmphasizedNotificationButton
-
-class TimerButtonView
-@JvmOverloads
-constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    defStyleAttr: Int = 0,
-    defStyleRes: Int = 0,
-) : EmphasizedNotificationButton(context, attrs, defStyleAttr, defStyleRes) {
-
-    private val Int.dp: Int
-        get() = (this * context.resources.displayMetrics.density).toInt()
-
-    fun setIcon(@DrawableRes icon: Int) {
-        val drawable = context.getDrawable(icon)
-
-        drawable?.mutate()
-        drawable?.setTintList(textColors)
-        drawable?.setTintBlendMode(BlendMode.SRC_IN)
-        drawable?.setBounds(0, 0, 24.dp, 24.dp)
-
-        setCompoundDrawablesRelative(drawable, null, null, null)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt
deleted file mode 100644
index d481b50..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.view
-
-import android.content.Context
-import android.graphics.drawable.Icon
-import android.os.SystemClock
-import android.util.AttributeSet
-import android.widget.Chronometer
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.core.view.isVisible
-import com.android.systemui.res.R
-
-class TimerView
-@JvmOverloads
-constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    defStyleAttr: Int = 0,
-    defStyleRes: Int = 0,
-) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) {
-
-    private val configTracker = ConfigurationTracker(resources)
-
-    private lateinit var icon: ImageView
-    private lateinit var label: TextView
-    private lateinit var chronometer: Chronometer
-    private lateinit var pausedTimeRemaining: TextView
-    lateinit var mainButton: TimerButtonView
-        private set
-
-    lateinit var altButton: TimerButtonView
-        private set
-
-    lateinit var resetButton: TimerButtonView
-        private set
-
-    override fun onFinishInflate() {
-        super.onFinishInflate()
-        icon = requireViewById(R.id.icon)
-        label = requireViewById(R.id.label)
-        chronometer = requireViewById(R.id.chronoRemaining)
-        pausedTimeRemaining = requireViewById(R.id.pausedTimeRemaining)
-        mainButton = requireViewById(R.id.mainButton)
-        altButton = requireViewById(R.id.altButton)
-        resetButton = requireViewById(R.id.resetButton)
-    }
-
-    /** the resources configuration has changed such that the view needs to be reinflated */
-    fun isReinflateNeeded(): Boolean = configTracker.hasUnhandledConfigChange()
-
-    fun setIcon(icon: Icon?) {
-        this.icon.setImageIcon(icon)
-    }
-
-    fun setLabel(label: String) {
-        this.label.text = label
-    }
-
-    fun setPausedTime(pausedTime: String?) {
-        if (pausedTime != null) {
-            pausedTimeRemaining.text = pausedTime
-            pausedTimeRemaining.isVisible = true
-        } else {
-            pausedTimeRemaining.isVisible = false
-        }
-    }
-
-    fun setCountdownTime(countdownTimeMs: Long?) {
-        if (countdownTimeMs != null) {
-            chronometer.base =
-                countdownTimeMs - System.currentTimeMillis() + SystemClock.elapsedRealtime()
-            chronometer.isVisible = true
-            chronometer.start()
-        } else {
-            chronometer.isVisible = false
-            chronometer.stop()
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt
deleted file mode 100644
index 3b8957c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.viewbinder
-
-import androidx.lifecycle.lifecycleScope
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.EnRouteViewModel
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-
-/** Binds a [EnRouteView] to its [view model][EnRouteViewModel]. */
-object EnRouteViewBinder {
-    fun bindWhileAttached(
-        view: EnRouteView,
-        viewModel: EnRouteViewModel,
-    ): DisposableHandle {
-        return view.repeatWhenAttached { lifecycleScope.launch { bind(view, viewModel) } }
-    }
-
-    suspend fun bind(
-        view: EnRouteView,
-        viewModel: EnRouteViewModel,
-    ) = coroutineScope {
-        launch { viewModel.icon.collect { view.setIcon(it) } }
-        launch { viewModel.title.collect { view.setTitle(it) } }
-        launch { viewModel.text.collect { view.setText(it) } }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
index 3b0f1ee..a17197c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
@@ -24,14 +24,14 @@
 object SingleLineViewBinder {
     @JvmStatic
     fun bind(viewModel: SingleLineViewModel?, view: HybridNotificationView?) {
-        if (viewModel?.isConversation() == true && view is HybridConversationNotificationView) {
+        if (view is HybridConversationNotificationView) {
             if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return
 
-            viewModel.conversationData?.avatar?.let { view.setAvatar(it) }
+            viewModel?.conversationData?.avatar?.let { view.setAvatar(it) }
             view.setText(
-                viewModel.titleText,
-                viewModel.contentText,
-                viewModel.conversationData?.conversationSenderName
+                viewModel?.titleText,
+                viewModel?.contentText,
+                viewModel?.conversationData?.conversationSenderName,
             )
         } else {
             // bind the title and content text views
@@ -39,7 +39,7 @@
                 bind(
                     /* title = */ viewModel?.titleText,
                     /* text = */ viewModel?.contentText,
-                    /* contentView = */ null
+                    /* contentView = */ null,
                 )
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt
deleted file mode 100644
index 042d1bc..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.viewbinder
-
-import android.content.res.ColorStateList
-import android.graphics.drawable.Icon
-import android.view.View
-import androidx.core.view.isGone
-import androidx.lifecycle.lifecycleScope
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView
-import com.android.systemui.statusbar.notification.row.ui.view.TimerView
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.TimerViewModel
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-
-/** Binds a [TimerView] to its [view model][TimerViewModel]. */
-object TimerViewBinder {
-    fun bindWhileAttached(
-        view: TimerView,
-        viewModel: TimerViewModel,
-    ): DisposableHandle {
-        return view.repeatWhenAttached { lifecycleScope.launch { bind(view, viewModel) } }
-    }
-
-    suspend fun bind(
-        view: TimerView,
-        viewModel: TimerViewModel,
-    ) = coroutineScope {
-        launch { viewModel.icon.collect { view.setIcon(it) } }
-        launch { viewModel.label.collect { view.setLabel(it) } }
-        launch { viewModel.pausedTime.collect { view.setPausedTime(it) } }
-        launch { viewModel.countdownTime.collect { view.setCountdownTime(it) } }
-        launch { viewModel.mainButtonModel.collect { bind(view.mainButton, it) } }
-        launch { viewModel.altButtonModel.collect { bind(view.altButton, it) } }
-        launch { viewModel.resetButtonModel.collect { bind(view.resetButton, it) } }
-    }
-
-    fun bind(buttonView: TimerButtonView, model: TimerViewModel.ButtonViewModel?) {
-        if (model != null) {
-            buttonView.setButtonBackground(
-                ColorStateList.valueOf(
-                    buttonView.context.getColor(com.android.internal.R.color.system_accent2_100)
-                )
-            )
-            buttonView.setTextColor(
-                buttonView.context.getColor(
-                    com.android.internal.R.color.notification_primary_text_color_light
-                )
-            )
-
-            when (model) {
-                is TimerViewModel.ButtonViewModel.WithSystemAttrs -> {
-                    buttonView.setIcon(model.iconRes)
-                    buttonView.setText(model.labelRes)
-                }
-                is TimerViewModel.ButtonViewModel.WithCustomAttrs -> {
-                    // TODO: b/352142761 - is there a better way to deal with TYPE_RESOURCE icons
-                    // with empty resPackage? RemoteViews handles this by using a  different
-                    // `contextForResources` for inflation.
-                    val icon =
-                        if (model.icon.type == Icon.TYPE_RESOURCE && model.icon.resPackage == "")
-                            Icon.createWithResource(
-                                "com.google.android.deskclock",
-                                model.icon.resId
-                            )
-                        else model.icon
-                    buttonView.setImageIcon(icon)
-                    buttonView.text = model.label
-                }
-            }
-
-            buttonView.setOnClickListener(
-                model.pendingIntent?.let { pendingIntent ->
-                    View.OnClickListener { pendingIntent.send() }
-                }
-            )
-            buttonView.isEnabled = model.pendingIntent != null
-        }
-        buttonView.isGone = model == null
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt
deleted file mode 100644
index 307a983..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.viewmodel
-
-import android.graphics.drawable.Icon
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.row.domain.interactor.NotificationRowInteractor
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.util.kotlin.FlowDumperImpl
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapNotNull
-
-/** A view model for EnRoute notifications. */
-class EnRouteViewModel
-@Inject
-constructor(
-    dumpManager: DumpManager,
-    rowInteractor: NotificationRowInteractor,
-) : FlowDumperImpl(dumpManager) {
-    init {
-        /* check if */ RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()
-    }
-
-    val icon: Flow<Icon?> = rowInteractor.enRouteContentModel.mapNotNull { it.smallIcon.icon }
-
-    val title: Flow<CharSequence?> = rowInteractor.enRouteContentModel.map { it.title }
-
-    val text: Flow<CharSequence?> = rowInteractor.enRouteContentModel.map { it.text }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt
deleted file mode 100644
index 5552d89..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.viewmodel
-
-// noinspection CleanArchitectureDependencyViolation
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
-import dagger.BindsInstance
-import dagger.Subcomponent
-
-@Subcomponent
-interface RichOngoingViewModelComponent {
-
-    @Subcomponent.Factory
-    interface Factory {
-        /** Creates an instance of [RichOngoingViewModelComponent]. */
-        fun create(
-            @BindsInstance repository: NotificationRowRepository
-        ): RichOngoingViewModelComponent
-    }
-
-    fun createTimerViewModel(): TimerViewModel
-
-    fun createEnRouteViewModel(): EnRouteViewModel
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt
deleted file mode 100644
index 768a093..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.viewmodel
-
-import android.annotation.DrawableRes
-import android.annotation.StringRes
-import android.app.PendingIntent
-import android.graphics.drawable.Icon
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.row.domain.interactor.NotificationRowInteractor
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel.TimerState
-import com.android.systemui.util.kotlin.FlowDumperImpl
-import java.time.Duration
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapNotNull
-
-/** A view model for Timer notifications. */
-class TimerViewModel
-@Inject
-constructor(
-    dumpManager: DumpManager,
-    rowInteractor: NotificationRowInteractor,
-) : FlowDumperImpl(dumpManager) {
-    init {
-        /* check if */ RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()
-    }
-
-    private val state: Flow<TimerState> = rowInteractor.timerContentModel.mapNotNull { it.state }
-
-    val icon: Flow<Icon?> = rowInteractor.timerContentModel.mapNotNull { it.icon.icon }
-
-    val label: Flow<String> = rowInteractor.timerContentModel.mapNotNull { it.name }
-
-    val countdownTime: Flow<Long?> = state.map { (it as? TimerState.Running)?.finishTime }
-
-    val pausedTime: Flow<String?> =
-        state.map { (it as? TimerState.Paused)?.timeRemaining?.format() }
-
-    val mainButtonModel: Flow<ButtonViewModel> =
-        state.map {
-            when (it) {
-                is TimerState.Paused ->
-                    ButtonViewModel.WithSystemAttrs(
-                        it.resumeIntent,
-                        com.android.systemui.res.R.string.controls_media_resume, // "Resume",
-                        com.android.systemui.res.R.drawable.ic_media_play
-                    )
-                is TimerState.Running ->
-                    ButtonViewModel.WithSystemAttrs(
-                        it.pauseIntent,
-                        com.android.systemui.res.R.string.controls_media_button_pause, // "Pause",
-                        com.android.systemui.res.R.drawable.ic_media_pause
-                    )
-            }
-        }
-
-    val altButtonModel: Flow<ButtonViewModel?> =
-        state.map {
-            it.addMinuteAction?.let { action ->
-                ButtonViewModel.WithCustomAttrs(
-                    action.actionIntent,
-                    action.title, // "1:00",
-                    action.getIcon()
-                )
-            }
-        }
-
-    val resetButtonModel: Flow<ButtonViewModel?> =
-        state.map {
-            it.resetAction?.let { action ->
-                ButtonViewModel.WithCustomAttrs(
-                    action.actionIntent,
-                    action.title, // "Reset",
-                    action.getIcon()
-                )
-            }
-        }
-
-    sealed interface ButtonViewModel {
-        val pendingIntent: PendingIntent?
-
-        data class WithSystemAttrs(
-            override val pendingIntent: PendingIntent?,
-            @StringRes val labelRes: Int,
-            @DrawableRes val iconRes: Int,
-        ) : ButtonViewModel
-
-        data class WithCustomAttrs(
-            override val pendingIntent: PendingIntent?,
-            val label: CharSequence,
-            val icon: Icon,
-        ) : ButtonViewModel
-    }
-}
-
-private fun Duration.format(): String {
-    val hours = this.toHours()
-    return if (hours > 0) {
-        String.format("%d:%02d:%02d", hours, toMinutesPart(), toSecondsPart())
-    } else {
-        String.format("%d:%02d", toMinutes(), toSecondsPart())
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 935e2a3..38390e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -356,11 +356,23 @@
         }
     }
 
-    val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> by lazy {
+    val activeHeadsUpRowKeys: Flow<Set<HeadsUpRowKey>> by lazy {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
             flowOf(emptySet())
         } else {
-            headsUpNotificationInteractor.pinnedHeadsUpRows.dumpWhileCollecting("pinnedHeadsUpRows")
+            headsUpNotificationInteractor.activeHeadsUpRowKeys.dumpWhileCollecting(
+                "pinnedHeadsUpRows"
+            )
+        }
+    }
+
+    val pinnedHeadsUpRowKeys: Flow<Set<HeadsUpRowKey>> by lazy {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+            flowOf(emptySet())
+        } else {
+            headsUpNotificationInteractor.pinnedHeadsUpRowKeys.dumpWhileCollecting(
+                "pinnedHeadsUpRows"
+            )
         }
     }
 
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 c9eaec7..aec81b0 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
@@ -84,9 +84,9 @@
     private fun fullyExpandedDuringSceneChange(change: ChangeScene): Boolean {
         // The lockscreen stack is visible during all transitions away from the lockscreen, so keep
         // the stack expanded until those transitions finish.
-        return if (change.isFrom({ it == Scenes.Lockscreen }, to = { true })) {
+        return if (change.isTransitioning(from = Scenes.Lockscreen)) {
             true
-        } else if (change.isFrom({ it == Scenes.Shade }, to = { it == Scenes.Lockscreen })) {
+        } else if (change.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)) {
             false
         } else {
             (expandedInScene(change.fromScene) && expandedInScene(change.toScene))
@@ -101,11 +101,11 @@
         return if (fullyExpandedDuringSceneChange(change)) {
             1f
         } else if (
-            change.isBetween({ it == Scenes.Gone }, { it == Scenes.Shade }) ||
-                change.isFrom({ it == Scenes.Shade }, to = { it == Scenes.Lockscreen })
+            change.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
+                change.isTransitioning(from = Scenes.Gone, to = Scenes.Lockscreen)
         ) {
             shadeExpansion
-        } else if (change.isBetween({ it == Scenes.Gone }, { it == Scenes.QuickSettings })) {
+        } else if (change.isTransitioningBetween(Scenes.Gone, Scenes.QuickSettings)) {
             // during QS expansion, increase fraction at same rate as scrim alpha,
             // but start when scrim alpha is at EXPANSION_FOR_DELAYED_STACK_FADE_IN.
             (qsExpansion / EXPANSION_FOR_MAX_SCRIM_ALPHA - EXPANSION_FOR_DELAYED_STACK_FADE_IN)
@@ -213,7 +213,11 @@
 
     private val qsAllowsClipping: Flow<Boolean> =
         combine(shadeInteractor.shadeMode, shadeInteractor.qsExpansion) { shadeMode, qsExpansion ->
-                qsExpansion < 0.5f || shadeMode != ShadeMode.Single
+                when (shadeMode) {
+                    is ShadeMode.Dual -> false
+                    is ShadeMode.Split -> true
+                    is ShadeMode.Single -> qsExpansion < 0.5f
+                }
             }
             .distinctUntilChanged()
 
@@ -325,9 +329,3 @@
         fun create(): NotificationScrollViewModel
     }
 }
-
-private fun ChangeScene.isBetween(a: (SceneKey) -> Boolean, b: (SceneKey) -> Boolean): Boolean =
-    (a(fromScene) && b(toScene)) || (b(fromScene) && a(toScene))
-
-private fun ChangeScene.isFrom(from: (SceneKey) -> Boolean, to: (SceneKey) -> Boolean): Boolean =
-    from(fromScene) && to(toScene)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
index dc15970..e2e5c59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
@@ -26,6 +26,7 @@
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 class HeadsUpNotificationViewBinder
@@ -35,18 +36,21 @@
         coroutineScope {
             launch {
                 var previousKeys = emptySet<HeadsUpRowKey>()
-                viewModel.pinnedHeadsUpRows
+                combine(viewModel.pinnedHeadsUpRowKeys, viewModel.activeHeadsUpRowKeys, ::Pair)
                     .sample(viewModel.headsUpAnimationsEnabled, ::Pair)
                     .collect { (newKeys, animationsEnabled) ->
-                        val added = newKeys - previousKeys
-                        val removed = previousKeys - newKeys
-                        previousKeys = newKeys
+                        val pinned = newKeys.first
+                        val all = newKeys.second
+                        val added = all.union(pinned) - previousKeys
+                        val removed = previousKeys - pinned
+                        previousKeys = pinned
+                        Pair(added, removed)
 
                         if (animationsEnabled) {
                             added.forEach { key ->
                                 parentView.generateHeadsUpAnimation(
                                     obtainView(key),
-                                    /* isHeadsUp = */ true
+                                    /* isHeadsUp = */ true,
                                 )
                             }
                             removed.forEach { key ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 8c03538..f8eae36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -411,7 +411,8 @@
                     mainActiveMode.getIcon().key().resPackage(),
                     mainActiveMode.getIcon().key().resId(),
                     mainActiveMode.getIcon().drawable(),
-                    mainActiveMode.getName(),
+                    mResources.getString(R.string.active_mode_content_description,
+                            mainActiveMode.getName()),
                     StatusBarIcon.Shape.FIXED_SPACE);
         }
         if (visible != mZenVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 5ae24f7..479ffb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -55,6 +55,7 @@
 import com.android.keyguard.KeyguardViewController;
 import com.android.keyguard.TrustGrantFlags;
 import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.DejankUtils;
 import com.android.systemui.Flags;
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
@@ -1203,6 +1204,11 @@
     @Override
     public void hide(long startTime, long fadeoutDuration) {
         Trace.beginSection("StatusBarKeyguardViewManager#hide");
+        if (Flags.checkLockscreenGoneTransition()) {
+            DejankUtils.notifyRendererOfExpensiveFrame(
+                    mNotificationShadeWindowController.getWindowRootView(),
+                    "StatusBarKeyguardViewManager#hide");
+        }
         mKeyguardStateController.notifyKeyguardState(false,
                 mKeyguardStateController.isOccluded());
         launchPendingWakeupAction();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index eea4c21..9c168be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -95,7 +95,8 @@
                                         primaryChipView.show(shouldAnimateChange = true)
                                     is OngoingActivityChipModel.Hidden ->
                                         primaryChipView.hide(
-                                            shouldAnimateChange = primaryChipModel.shouldAnimate
+                                            state = View.GONE,
+                                            shouldAnimateChange = primaryChipModel.shouldAnimate,
                                         )
                                 }
                             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerExt.kt
new file mode 100644
index 0000000..441cbb3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerExt.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onStart
+
+/** [DevicePostureController.getDevicePosture] as a [Flow]. */
+@DevicePostureInt
+fun DevicePostureController.devicePosture(): Flow<Int> =
+    conflatedCallbackFlow {
+            val callback = DevicePostureController.Callback { posture -> trySend(posture) }
+            addCallback(callback)
+            awaitClose { removeCallback(callback) }
+        }
+        .onStart { emit(devicePosture) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 6764839c..4f595ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -76,7 +76,7 @@
                         // can be manually toggled on
                         mode.rule.isEnabled -> mode.isActive || mode.rule.isManualInvocationAllowed
                         // Mode was created as disabled, or disabled by the app that owns it ->
-                        // will be shown with a "Set up" text
+                        // will be shown with a "Not set" text
                         !mode.rule.isEnabled -> mode.status == ZenMode.Status.DISABLED_BY_OTHER
                         else -> false
                     }
@@ -120,7 +120,7 @@
                         },
                         onLongClick = { openSettings(mode) },
                         onLongClickLabel =
-                            context.resources.getString(R.string.accessibility_long_click_tile)
+                            context.resources.getString(R.string.accessibility_long_click_tile),
                     )
                 }
             }
@@ -137,10 +137,10 @@
 
     /**
      * Returns a description of the mode, which is:
-     *   * a prompt to set up the mode if it is not enabled
-     *   * if it cannot be manually activated, text that says so
-     *   * otherwise, the trigger description of the mode if it exists...
-     *   * ...or null if it doesn't
+     * * a prompt to set up the mode if it is not enabled
+     * * if it cannot be manually activated, text that says so
+     * * otherwise, the trigger description of the mode if it exists...
+     * * ...or null if it doesn't
      *
      * This description is used directly for the content description of a mode tile for screen
      * readers, and for the tile subtext will be augmented with the current status of the mode.
@@ -174,7 +174,7 @@
                     context,
                     R.style.Theme_SystemUI_Dialog,
                     /* cancelIsNeutral= */ true,
-                    zenDialogMetricsLogger
+                    zenDialogMetricsLogger,
                 )
                 .createDialog()
         SystemUIDialog.applyFlags(dialog)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
index 6acc891..94e19de 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
@@ -20,19 +20,27 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.ArrowBack
 import androidx.compose.material3.Button
 import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
 import androidx.compose.ui.unit.dp
 import com.android.systemui.inputdevice.tutorial.ui.composable.DoneButton
 import com.android.systemui.res.R
@@ -47,20 +55,17 @@
     Column(
         verticalArrangement = Arrangement.Center,
         modifier =
-            Modifier.background(
-                    color = MaterialTheme.colorScheme.surfaceContainer,
-                )
-                .fillMaxSize()
+            Modifier.background(color = MaterialTheme.colorScheme.surfaceContainer).fillMaxSize(),
     ) {
         TutorialSelectionButtons(
             onBackTutorialClicked = onBackTutorialClicked,
             onHomeTutorialClicked = onHomeTutorialClicked,
             onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
-            modifier = Modifier.padding(60.dp)
+            modifier = Modifier.padding(60.dp),
         )
         DoneButton(
             onDoneButtonClicked = onDoneButtonClicked,
-            modifier = Modifier.padding(horizontal = 60.dp)
+            modifier = Modifier.padding(horizontal = 60.dp),
         )
     }
 }
@@ -70,30 +75,36 @@
     onBackTutorialClicked: () -> Unit,
     onHomeTutorialClicked: () -> Unit,
     onRecentAppsTutorialClicked: () -> Unit,
-    modifier: Modifier = Modifier
+    modifier: Modifier = Modifier,
 ) {
     Row(
         horizontalArrangement = Arrangement.spacedBy(20.dp),
         verticalAlignment = Alignment.CenterVertically,
-        modifier = modifier
+        modifier = modifier,
     ) {
         TutorialButton(
             text = stringResource(R.string.touchpad_tutorial_home_gesture_button),
+            icon = Icons.AutoMirrored.Outlined.ArrowBack,
+            iconColor = MaterialTheme.colorScheme.onPrimary,
             onClick = onHomeTutorialClicked,
-            color = MaterialTheme.colorScheme.primary,
-            modifier = Modifier.weight(1f)
+            backgroundColor = MaterialTheme.colorScheme.primary,
+            modifier = Modifier.weight(1f),
         )
         TutorialButton(
             text = stringResource(R.string.touchpad_tutorial_back_gesture_button),
+            icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_home_icon),
+            iconColor = MaterialTheme.colorScheme.onTertiary,
             onClick = onBackTutorialClicked,
-            color = MaterialTheme.colorScheme.tertiary,
-            modifier = Modifier.weight(1f)
+            backgroundColor = MaterialTheme.colorScheme.tertiary,
+            modifier = Modifier.weight(1f),
         )
         TutorialButton(
             text = stringResource(R.string.touchpad_tutorial_recent_apps_gesture_button),
+            icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_recents_icon),
+            iconColor = MaterialTheme.colorScheme.onSecondary,
             onClick = onRecentAppsTutorialClicked,
-            color = MaterialTheme.colorScheme.secondary,
-            modifier = Modifier.weight(1f)
+            backgroundColor = MaterialTheme.colorScheme.secondary,
+            modifier = Modifier.weight(1f),
         )
     }
 }
@@ -101,16 +112,30 @@
 @Composable
 private fun TutorialButton(
     text: String,
+    icon: ImageVector,
+    iconColor: Color,
     onClick: () -> Unit,
-    color: Color,
-    modifier: Modifier = Modifier
+    backgroundColor: Color,
+    modifier: Modifier = Modifier,
 ) {
     Button(
         onClick = onClick,
         shape = RoundedCornerShape(16.dp),
-        colors = ButtonDefaults.buttonColors(containerColor = color),
-        modifier = modifier.aspectRatio(0.66f)
+        colors = ButtonDefaults.buttonColors(containerColor = backgroundColor),
+        modifier = modifier.aspectRatio(0.66f),
     ) {
-        Text(text = text, style = MaterialTheme.typography.headlineLarge)
+        Column(
+            verticalArrangement = Arrangement.Center,
+            horizontalAlignment = Alignment.CenterHorizontally,
+        ) {
+            Icon(
+                imageVector = icon,
+                contentDescription = text,
+                modifier = Modifier.width(30.dp).height(30.dp),
+                tint = iconColor,
+            )
+            Spacer(modifier = Modifier.height(16.dp))
+            Text(text = text, style = MaterialTheme.typography.headlineLarge)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialog.kt
deleted file mode 100644
index 869b3c6..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialog.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.dialog
-
-import android.app.Dialog
-import android.content.Context
-import android.os.Bundle
-import android.view.ContextThemeWrapper
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.res.R
-import javax.inject.Inject
-
-class NewVolumeDialog @Inject constructor(@Application context: Context) :
-    Dialog(ContextThemeWrapper(context, R.style.volume_dialog_theme)) {
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        setContentView(R.layout.volume_dialog)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialogPlugin.kt
deleted file mode 100644
index b93714a..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/NewVolumeDialogPlugin.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.dialog
-
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.plugins.VolumeDialog
-import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
-import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-
-class NewVolumeDialogPlugin
-@Inject
-constructor(
-    @Application private val applicationCoroutineScope: CoroutineScope,
-    private val volumeDialogPluginComponentFactory: VolumeDialogPluginComponent.Factory,
-) : VolumeDialog {
-
-    private var volumeDialogPluginComponent: VolumeDialogPluginComponent? = null
-    private var job: Job? = null
-
-    override fun init(windowType: Int, callback: VolumeDialog.Callback?) {
-        job =
-            applicationCoroutineScope.launch {
-                coroutineScope {
-                    volumeDialogPluginComponent = volumeDialogPluginComponentFactory.create(this)
-                }
-            }
-    }
-
-    private fun showDialog() {
-        val volumeDialogPluginComponent =
-            volumeDialogPluginComponent ?: error("Creating dialog before init was called")
-        volumeDialogPluginComponent.coroutineScope().launch {
-            coroutineScope {
-                val volumeDialogComponent: VolumeDialogComponent =
-                    volumeDialogPluginComponent.volumeDialogComponentFactory().create(this)
-                with(volumeDialogComponent.volumeDialog()) {
-                    setOnDismissListener { volumeDialogComponent.coroutineScope().cancel() }
-                    show()
-                }
-            }
-        }
-    }
-
-    override fun destroy() {
-        job?.cancel()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
index 74e823e..7476c6a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -20,15 +20,39 @@
 import android.content.Context
 import android.os.Bundle
 import android.view.ContextThemeWrapper
+import android.view.MotionEvent
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.res.R
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.ui.binder.VolumeDialogBinder
 import javax.inject.Inject
 
-class VolumeDialog @Inject constructor(@Application context: Context) :
-    Dialog(ContextThemeWrapper(context, R.style.volume_dialog_theme)) {
+class VolumeDialog
+@Inject
+constructor(
+    @Application context: Context,
+    private val dialogBinder: VolumeDialogBinder,
+    private val visibilityInteractor: VolumeDialogVisibilityInteractor,
+) : Dialog(ContextThemeWrapper(context, R.style.volume_dialog_theme)) {
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        setContentView(R.layout.volume_dialog)
+        dialogBinder.bind(this)
+    }
+
+    /**
+     * NOTE: This will be called with ACTION_OUTSIDE MotionEvents for touches that occur outside of
+     * the touchable region of the volume dialog (as returned by [.onComputeInternalInsets]) even if
+     * those touches occurred within the bounds of the volume dialog.
+     */
+    override fun onTouchEvent(event: MotionEvent): Boolean {
+        if (isShowing) {
+            if (event.action == MotionEvent.ACTION_OUTSIDE) {
+                visibilityInteractor.dismissDialog(Events.DISMISS_REASON_TOUCH_OUTSIDE)
+                return true
+            }
+        }
+        return false
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
index a2e81d9..4b7a978 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
@@ -18,12 +18,10 @@
 
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.plugins.VolumeDialog
-import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
 import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 
@@ -34,31 +32,17 @@
     private val volumeDialogPluginComponentFactory: VolumeDialogPluginComponent.Factory,
 ) : VolumeDialog {
 
-    private var volumeDialogPluginComponent: VolumeDialogPluginComponent? = null
     private var job: Job? = null
 
     override fun init(windowType: Int, callback: VolumeDialog.Callback?) {
         job =
             applicationCoroutineScope.launch {
                 coroutineScope {
-                    volumeDialogPluginComponent = volumeDialogPluginComponentFactory.create(this)
-                }
-            }
-    }
+                    val component = volumeDialogPluginComponentFactory.create(this)
 
-    private fun showDialog() {
-        val volumeDialogPluginComponent =
-            volumeDialogPluginComponent ?: error("Creating dialog before init was called")
-        volumeDialogPluginComponent.coroutineScope().launch {
-            coroutineScope {
-                val volumeDialogComponent: VolumeDialogComponent =
-                    volumeDialogPluginComponent.volumeDialogComponentFactory().create(this)
-                with(volumeDialogComponent.volumeDialog()) {
-                    setOnDismissListener { volumeDialogComponent.coroutineScope().cancel() }
-                    show()
+                    component.viewModel().activate()
                 }
             }
-        }
     }
 
     override fun destroy() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt
index 82612a7..4e0098c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.volume.dialog.dagger.module.VolumeDialogPluginModule
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogPluginViewModel
 import dagger.BindsInstance
 import dagger.Subcomponent
 import kotlinx.coroutines.CoroutineScope
@@ -31,15 +32,7 @@
 @Subcomponent(modules = [VolumeDialogPluginModule::class])
 interface VolumeDialogPluginComponent {
 
-    /**
-     * Provides a coroutine scope to use inside [VolumeDialogPluginScope].
-     * [com.android.systemui.volume.dialog.VolumeDialogPlugin] manages the lifecycle of this scope.
-     * It's cancelled when the dialog is disposed. This helps to free occupied resources when volume
-     * dialog is not shown.
-     */
-    @VolumeDialogPlugin fun coroutineScope(): CoroutineScope
-
-    fun volumeDialogComponentFactory(): VolumeDialogComponent.Factory
+    fun viewModel(): VolumeDialogPluginViewModel
 
     @Subcomponent.Factory
     interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
index ec7c6ce..2e26fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
@@ -20,7 +20,8 @@
 import android.os.Handler
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.plugins.VolumeDialogController
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
 import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
 import com.android.systemui.volume.dialog.domain.model.VolumeDialogStateModel
 import javax.inject.Inject
@@ -40,12 +41,12 @@
  *
  * @see VolumeDialogController.Callbacks
  */
-@VolumeDialog
+@VolumeDialogPluginScope
 class VolumeDialogCallbacksInteractor
 @Inject
 constructor(
     private val volumeDialogController: VolumeDialogController,
-    @VolumeDialog private val coroutineScope: CoroutineScope,
+    @VolumeDialogPlugin private val coroutineScope: CoroutineScope,
     @Background private val bgHandler: Handler,
 ) {
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
index dd51108..4a709a44b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
@@ -17,7 +17,8 @@
 package com.android.systemui.volume.dialog.domain.interactor
 
 import com.android.systemui.plugins.VolumeDialogController
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
 import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
 import com.android.systemui.volume.dialog.domain.model.VolumeDialogStateModel
 import javax.inject.Inject
@@ -35,13 +36,13 @@
  *
  * @see [VolumeDialogController]
  */
-@VolumeDialog
+@VolumeDialogPluginScope
 class VolumeDialogStateInteractor
 @Inject
 constructor(
     volumeDialogCallbacksInteractor: VolumeDialogCallbacksInteractor,
     private val volumeDialogController: VolumeDialogController,
-    @VolumeDialog private val coroutineScope: CoroutineScope,
+    @VolumeDialogPlugin private val coroutineScope: CoroutineScope,
 ) {
 
     val volumeDialogState: Flow<VolumeDialogStateModel> =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
new file mode 100644
index 0000000..6c92754
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.interactor
+
+import android.annotation.SuppressLint
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel
+import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.update
+
+private val maxDialogShowTime: Duration = 3.seconds
+
+/**
+ * Handles Volume Dialog visibility state. It might change from several sources:
+ * - [com.android.systemui.plugins.VolumeDialogController] requests visibility change;
+ * - it might be dismissed by the inactivity timeout;
+ * - it can be dismissed by the user;
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@VolumeDialogPluginScope
+class VolumeDialogVisibilityInteractor
+@Inject
+constructor(
+    @VolumeDialogPlugin coroutineScope: CoroutineScope,
+    callbacksInteractor: VolumeDialogCallbacksInteractor,
+) {
+
+    @SuppressLint("SharedFlowCreation")
+    private val mutableDismissDialogEvents = MutableSharedFlow<Unit>()
+    private val mutableDialogVisibility =
+        MutableStateFlow<VolumeDialogVisibilityModel>(VolumeDialogVisibilityModel.Invisible)
+
+    val dialogVisibility: Flow<VolumeDialogVisibilityModel> = mutableDialogVisibility.asStateFlow()
+
+    init {
+        merge(
+                mutableDismissDialogEvents.mapLatest {
+                    delay(maxDialogShowTime)
+                    VolumeDialogEventModel.DismissRequested(Events.DISMISS_REASON_TIMEOUT)
+                },
+                callbacksInteractor.event,
+            )
+            .onEach { event ->
+                VolumeDialogVisibilityModel.fromEvent(event)?.let { model ->
+                    mutableDialogVisibility.value = model
+                    if (model is VolumeDialogVisibilityModel.Visible) {
+                        resetDismissTimeout()
+                    }
+                }
+            }
+            .launchIn(coroutineScope)
+    }
+
+    /**
+     * Dismisses the dialog with a given [reason]. The new state will be emitted in the
+     * [dialogVisibility].
+     */
+    fun dismissDialog(reason: Int) {
+        mutableDialogVisibility.update {
+            if (it is VolumeDialogVisibilityModel.Dismissed) {
+                it
+            } else {
+                VolumeDialogVisibilityModel.Dismissed(reason)
+            }
+        }
+    }
+
+    /** Resets current dialog timeout. */
+    suspend fun resetDismissTimeout() {
+        mutableDismissDialogEvents.emit(Unit)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogVisibilityModel.kt
new file mode 100644
index 0000000..646445d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogVisibilityModel.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.model
+
+/** Models current Volume Dialog visibility state. */
+sealed interface VolumeDialogVisibilityModel {
+
+    /** Dialog is currently visible. */
+    data class Visible(val reason: Int, val keyguardLocked: Boolean, val lockTaskModeState: Int) :
+        VolumeDialogVisibilityModel
+
+    /** Dialog has never been shown. So it's just invisible. */
+    interface Invisible : VolumeDialogVisibilityModel {
+        companion object : Invisible
+    }
+
+    /** Dialog has been shown and then dismissed. */
+    data class Dismissed(val reason: Int) : Invisible
+
+    companion object {
+
+        /**
+         * Creates [VolumeDialogVisibilityModel] from appropriate events and returns null otherwise.
+         */
+        fun fromEvent(event: VolumeDialogEventModel): VolumeDialogVisibilityModel? {
+            return when (event) {
+                is VolumeDialogEventModel.DismissRequested -> Dismissed(event.reason)
+                is VolumeDialogEventModel.ShowRequested ->
+                    Visible(event.reason, event.keyguardLocked, event.lockTaskModeState)
+                else -> null
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
new file mode 100644
index 0000000..59c38c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.volume.dialog.shared
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.VolumeLog
+import com.android.systemui.volume.Events
+import javax.inject.Inject
+
+private const val TAG = "SysUI_VolumeDialog"
+
+/** Logs events related to the Volume Panel. */
+class VolumeDialogLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuffer) {
+
+    fun onShow(reason: Int) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { int1 = reason },
+            { "Show: ${Events.SHOW_REASONS[int1]}" },
+        )
+    }
+
+    fun onDismiss(reason: Int) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { int1 = reason },
+            { "Dismiss: ${Events.DISMISS_REASONS[int1]}" },
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
new file mode 100644
index 0000000..3f2c39b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ui.binder
+
+import android.app.Dialog
+import android.graphics.Color
+import android.graphics.PixelFormat
+import android.graphics.drawable.ColorDrawable
+import android.view.View
+import android.view.ViewGroup
+import android.view.Window
+import android.view.WindowManager
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogGravityViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+@VolumeDialogScope
+class VolumeDialogBinder
+@Inject
+constructor(
+    @VolumeDialog private val coroutineScope: CoroutineScope,
+    private val volumeDialogViewBinder: VolumeDialogViewBinder,
+    private val gravityViewModel: VolumeDialogGravityViewModel,
+) {
+
+    fun bind(dialog: Dialog) {
+        with(dialog) {
+            setupWindow(window!!)
+            dialog.setContentView(R.layout.volume_dialog)
+
+            val volumeDialogView: View = dialog.requireViewById(R.id.volume_dialog_container)
+            volumeDialogView.repeatWhenAttached {
+                lifecycleScope.launch { volumeDialogViewBinder.bind(volumeDialogView) }
+            }
+        }
+    }
+
+    /** Configures [Window] for the [Dialog]. */
+    private fun setupWindow(window: Window) =
+        with(window) {
+            clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+            addFlags(
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+                    WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+                    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+            )
+            addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+
+            requestFeature(Window.FEATURE_NO_TITLE)
+            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+            setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
+            setWindowAnimations(-1)
+            setFormat(PixelFormat.TRANSLUCENT)
+
+            attributes =
+                attributes.apply {
+                    title = "VolumeDialog" // Not the same as Window#setTitle
+                }
+            setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+
+            gravityViewModel.dialogGravity.onEach { window.setGravity(it) }.launchIn(coroutineScope)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt
new file mode 100644
index 0000000..df6523c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ui.viewmodel
+
+import android.content.Context
+import android.content.res.Configuration
+import android.view.Gravity
+import androidx.annotation.GravityInt
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.devicePosture
+import com.android.systemui.statusbar.policy.onConfigChanged
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+@VolumeDialogScope
+class VolumeDialogGravityViewModel
+@Inject
+constructor(
+    @Application private val context: Context,
+    @VolumeDialog private val coroutineScope: CoroutineScope,
+    @UiBackground private val uiBackgroundCoroutineContext: CoroutineContext,
+    configurationController: ConfigurationController,
+    private val devicePostureController: DevicePostureController,
+) {
+
+    @GravityInt private var originalGravity: Int = context.getAbsoluteGravity()
+
+    val dialogGravity: Flow<Int> =
+        combine(
+                devicePostureController.devicePosture(),
+                configurationController.onConfigChanged.onEach { onConfigurationChanged() },
+            ) { devicePosture, configuration ->
+                context.calculateGravity(devicePosture, configuration)
+            }
+            .stateIn(
+                scope = coroutineScope,
+                started = SharingStarted.Eagerly,
+                context.calculateGravity(),
+            )
+
+    private suspend fun onConfigurationChanged() {
+        withContext(uiBackgroundCoroutineContext) { originalGravity = context.getAbsoluteGravity() }
+    }
+
+    @GravityInt
+    private fun Context.calculateGravity(
+        devicePosture: Int = devicePostureController.devicePosture,
+        config: Configuration = resources.configuration,
+    ): Int {
+        val isLandscape = config.orientation == Configuration.ORIENTATION_LANDSCAPE
+        val isHalfOpen = devicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED
+        val gravity =
+            if (isLandscape && isHalfOpen) {
+                originalGravity or Gravity.TOP
+            } else {
+                originalGravity
+            }
+        return getAbsoluteGravity(gravity)
+    }
+}
+
+@GravityInt
+private fun Context.getAbsoluteGravity(
+    gravity: Int = resources.getInteger(R.integer.volume_dialog_gravity)
+): Int = with(resources) { Gravity.getAbsoluteGravity(gravity, configuration.layoutDirection) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
new file mode 100644
index 0000000..329a947
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ui.viewmodel
+
+import android.app.Dialog
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel
+import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@VolumeDialogPluginScope
+class VolumeDialogPluginViewModel
+@Inject
+constructor(
+    private val componentFactory: VolumeDialogComponent.Factory,
+    private val dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
+    private val controller: VolumeDialogController,
+    private val logger: VolumeDialogLogger,
+) : ExclusiveActivatable() {
+
+    override suspend fun onActivated(): Nothing {
+        coroutineScope {
+            dialogVisibilityInteractor.dialogVisibility
+                .mapLatest { visibilityModel ->
+                    with(visibilityModel) {
+                        if (this is VolumeDialogVisibilityModel.Visible) {
+                            showDialog(reason, keyguardLocked, lockTaskModeState)
+                        }
+                        if (this is VolumeDialogVisibilityModel.Dismissed) {
+                            Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason)
+                            logger.onDismiss(reason)
+                        }
+                    }
+                }
+                .launchIn(this)
+        }
+        awaitCancellation()
+    }
+
+    suspend fun showDialog(reason: Int, keyguardLocked: Boolean, lockTaskModeState: Int): Unit =
+        coroutineScope {
+            logger.onShow(reason)
+
+            controller.notifyVisible(true)
+
+            val volumeDialogComponent: VolumeDialogComponent = componentFactory.create(this)
+            val dialog =
+                volumeDialogComponent.volumeDialog().apply {
+                    setOnDismissListener {
+                        volumeDialogComponent.coroutineScope().cancel()
+                        dialogVisibilityInteractor.dismissDialog(Events.DISMISS_REASON_UNKNOWN)
+                    }
+                }
+            launch { dialog.awaitShow() }
+
+            Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked)
+        }
+}
+
+/** Shows [Dialog] until suspend function is cancelled. */
+private suspend fun Dialog.awaitShow() =
+    suspendCancellableCoroutine<Unit> {
+        show()
+        it.invokeOnCancellation { dismiss() }
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
index f9e91ae..30c8c15 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
@@ -19,11 +19,12 @@
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
 
 class VolumeDialogViewModel @AssistedInject constructor() : ExclusiveActivatable() {
 
     override suspend fun onActivated(): Nothing {
-        TODO("Not yet implemented")
+        awaitCancellation()
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 5f6ad92..02d0b57 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -61,7 +61,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedEventCallback;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -393,7 +393,7 @@
 
     void initDesktopMode(DesktopMode desktopMode) {
         desktopMode.addVisibleTasksListener(
-                new DesktopModeTaskRepository.VisibleTasksListener() {
+                new DesktopRepository.VisibleTasksListener() {
                     @Override
                     public void onTasksVisibilityChanged(int displayId, int visibleTasksCount) {
                         if (displayId == Display.DEFAULT_DISPLAY) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 9cd5215..8206c21 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -85,8 +85,9 @@
         `when`(telephonyManager.createForSubscriptionId(anyInt())).thenReturn(telephonyManager)
         `when`(telephonyManager.supplyIccLockPin(anyString())).thenReturn(mock())
         simPinView =
-            LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
-                as KeyguardSimPinView
+            LayoutInflater.from(context)
+                .inflate(R.layout.keyguard_sim_pin_view, null)
+                .requireViewById(R.id.keyguard_sim_pin_view) as KeyguardSimPinView
         val fakeFeatureFlags = FakeFeatureFlags()
         val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 8075d11..6061063 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -19,6 +19,7 @@
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
 import static com.android.systemui.Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS;
+import static com.android.systemui.Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED;
@@ -166,6 +167,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE)
     public void test_setClipData_invalidImageData_legacy() {
         initController();
 
@@ -238,6 +240,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE)
     public void test_setClipData_invalidImageData() {
         initController();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
index 3ed0977..1d74e8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
@@ -17,12 +17,12 @@
 package com.android.systemui.screenshot
 
 import android.content.ComponentName
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import android.graphics.Insets
 import android.graphics.Rect
 import android.os.UserHandle
 import android.view.Display
 import android.view.WindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.internal.util.ScreenshotRequest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
@@ -71,15 +71,6 @@
     }
 
     @Test
-    fun testNegativeUserId() {
-        val request = ScreenshotRequest.Builder(type, source).setUserId(-1).build()
-
-        val data = ScreenshotData.fromRequest(request)
-
-        assertThat(data.userHandle).isNull()
-    }
-
-    @Test
     fun testPackageNameAsString() {
         val request = ScreenshotRequest.Builder(type, source).setTopComponent(component).build()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index 0bea560..27e9f07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.display.data.repository.FakeFocusedDisplayRepository
 import com.android.systemui.display.data.repository.display
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -58,6 +59,7 @@
     private val testScope = TestScope(UnconfinedTestDispatcher())
     private val eventLogger = UiEventLoggerFake()
     private val headlessHandler = mock<HeadlessScreenshotHandler>()
+    private val focusedDisplayRepository = FakeFocusedDisplayRepository()
 
     private val screenshotExecutor =
         TakeScreenshotExecutorImpl(
@@ -68,6 +70,7 @@
             eventLogger,
             notificationControllerFactory,
             headlessHandler,
+            focusedDisplayRepository,
         )
 
     @Before
@@ -309,6 +312,59 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
+    fun executeScreenshots_keyOther_usesFocusedDisplay() =
+        testScope.runTest {
+            val displayId = 1
+            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = displayId))
+            val onSaved = { _: Uri? -> }
+            focusedDisplayRepository.emit(displayId)
+
+            screenshotExecutor.executeScreenshots(
+                createScreenshotRequest(
+                    source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
+                ),
+                onSaved,
+                callback,
+            )
+
+            val dataCaptor = ArgumentCaptor<ScreenshotData>()
+
+            verify(controller).handleScreenshot(dataCaptor.capture(), any(), any())
+
+            assertThat(dataCaptor.value.displayId).isEqualTo(displayId)
+
+            screenshotExecutor.onDestroy()
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
+    fun executeScreenshots_keyOtherInvalidDisplay_usesDefault() =
+        testScope.runTest {
+            setDisplays(
+                display(TYPE_INTERNAL, id = Display.DEFAULT_DISPLAY),
+                display(TYPE_EXTERNAL, id = 1),
+            )
+            focusedDisplayRepository.emit(5) // invalid display
+            val onSaved = { _: Uri? -> }
+            screenshotExecutor.executeScreenshots(
+                createScreenshotRequest(
+                    source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
+                ),
+                onSaved,
+                callback,
+            )
+
+            val dataCaptor = ArgumentCaptor<ScreenshotData>()
+
+            verify(controller).handleScreenshot(dataCaptor.capture(), any(), any())
+
+            assertThat(dataCaptor.value.displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+
+            screenshotExecutor.onDestroy()
+        }
+
+    @Test
     fun onDestroy_propagatedToControllers() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
index bab9bbb..2fcacb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
@@ -58,13 +58,13 @@
             ScreenshotData(
                 TAKE_SCREENSHOT_FULLSCREEN,
                 SCREENSHOT_KEY_CHORD,
-                null,
+                UserHandle.CURRENT,
                 topComponent = null,
                 screenBounds = Rect(0, 0, 1, 1),
                 taskId = -1,
                 insets = Insets.NONE,
                 bitmap = null,
-                displayId = DEFAULT_DISPLAY
+                displayId = DEFAULT_DISPLAY,
             )
 
         /* Create a policy request processor with no capture policies */
@@ -75,7 +75,7 @@
                 policies = emptyList(),
                 defaultOwner = UserHandle.of(PERSONAL),
                 defaultComponent = ComponentName("default", "Component"),
-                displayTasks = fullScreenWork
+                displayTasks = fullScreenWork,
             )
 
         val result = runBlocking { requestProcessor.process(request) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index f5a90196..0e9ef06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -556,11 +556,13 @@
             return null;
         }).when(mView).setOnTouchListener(any(NotificationPanelViewController.TouchHandler.class));
 
-        // Dreaming->Lockscreen
+        // Any edge transition
         when(mKeyguardTransitionInteractor.transition(any()))
                 .thenReturn(emptyFlow());
         when(mKeyguardTransitionInteractor.transition(any(), any()))
                 .thenReturn(emptyFlow());
+
+        // Dreaming->Lockscreen
         when(mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha())
                 .thenReturn(emptyFlow());
         when(mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY(anyInt()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 98315d0c..83dbfa0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -95,6 +95,7 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 public class NetworkControllerBaseTest extends SysuiTestCase {
@@ -332,10 +333,15 @@
     }
 
     public void setConnectivityViaCallbackInNetworkControllerForVcn(
-            int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) {
+            int networkType,
+            boolean validated,
+            boolean isConnected,
+            VcnTransportInfo info,
+            Network underlyingNetwork) {
         final NetworkCapabilities.Builder builder =
                 new NetworkCapabilities.Builder(mNetCapabilities);
-        builder.setTransportInfo(info);
+        builder.setTransportInfo(info)
+                .setUnderlyingNetworks(Collections.singletonList(underlyingNetwork));
         setConnectivityCommon(builder, networkType, validated, isConnected);
         mDefaultCallbackInNetworkController.onCapabilitiesChanged(
                 mock(Network.class), builder.build());
@@ -385,10 +391,15 @@
     }
 
     public void setConnectivityViaCallbackInWifiTrackerForVcn(
-            int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) {
+            int networkType,
+            boolean validated,
+            boolean isConnected,
+            VcnTransportInfo info,
+            Network underlyingNetwork) {
         final NetworkCapabilities.Builder builder =
                 new NetworkCapabilities.Builder(mNetCapabilities);
-        builder.setTransportInfo(info);
+        builder.setTransportInfo(info)
+                .setUnderlyingNetworks(Collections.singletonList(underlyingNetwork));
         setConnectivityCommon(builder, networkType, validated, isConnected);
         if (networkType == NetworkCapabilities.TRANSPORT_CELLULAR) {
             if (isConnected) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
index 6c80a97..6febb91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
@@ -18,6 +18,7 @@
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
 import static junit.framework.Assert.assertEquals;
 
@@ -250,6 +251,17 @@
         assertEquals(testSsid, mNetworkController.mWifiSignalController.mCurrentState.ssid);
     }
 
+    private Network newWifiNetwork(WifiInfo wifiInfo) {
+        final Network network = mock(Network.class);
+        final NetworkCapabilities wifiCaps =
+                new NetworkCapabilities.Builder()
+                        .addTransportType(TRANSPORT_WIFI)
+                        .setTransportInfo(wifiInfo)
+                        .build();
+        when(mMockCm.getNetworkCapabilities(network)).thenReturn(wifiCaps);
+        return network;
+    }
+
     @Test
     public void testVcnWithUnderlyingWifi() {
         String testSsid = "Test VCN SSID";
@@ -266,11 +278,19 @@
             setWifiLevelForVcn(testLevel);
 
             setConnectivityViaCallbackInNetworkControllerForVcn(
-                    NetworkCapabilities.TRANSPORT_CELLULAR, true, true, mVcnTransportInfo);
+                    NetworkCapabilities.TRANSPORT_CELLULAR,
+                    true,
+                    true,
+                    mVcnTransportInfo,
+                    newWifiNetwork(mWifiInfo));
             verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, true);
 
             setConnectivityViaCallbackInNetworkControllerForVcn(
-                    NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
+                    NetworkCapabilities.TRANSPORT_CELLULAR,
+                    false,
+                    true,
+                    mVcnTransportInfo,
+                    newWifiNetwork(mWifiInfo));
             verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, false);
         }
     }
@@ -391,13 +411,15 @@
     }
 
     protected void setWifiLevelForVcn(int level) {
-        when(mVcnTransportInfo.getWifiInfo()).thenReturn(mWifiInfo);
-        when(mVcnTransportInfo.makeCopy(anyLong())).thenReturn(mVcnTransportInfo);
         when(mWifiInfo.getRssi()).thenReturn(calculateRssiForLevel(level));
         when(mWifiInfo.isCarrierMerged()).thenReturn(true);
         when(mWifiInfo.getSubscriptionId()).thenReturn(1);
         setConnectivityViaCallbackInWifiTrackerForVcn(
-                NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
+                NetworkCapabilities.TRANSPORT_CELLULAR,
+                false,
+                true,
+                mVcnTransportInfo,
+                newWifiNetwork(mWifiInfo));
     }
 
     private int calculateRssiForLevel(int level) {
@@ -409,13 +431,15 @@
     }
 
     protected void setWifiStateForVcn(boolean connected, String ssid) {
-        when(mVcnTransportInfo.getWifiInfo()).thenReturn(mWifiInfo);
-        when(mVcnTransportInfo.makeCopy(anyLong())).thenReturn(mVcnTransportInfo);
         when(mWifiInfo.getSSID()).thenReturn(ssid);
         when(mWifiInfo.isCarrierMerged()).thenReturn(true);
         when(mWifiInfo.getSubscriptionId()).thenReturn(1);
         setConnectivityViaCallbackInWifiTrackerForVcn(
-                NetworkCapabilities.TRANSPORT_CELLULAR, false, connected, mVcnTransportInfo);
+                NetworkCapabilities.TRANSPORT_CELLULAR,
+                false,
+                connected,
+                mVcnTransportInfo,
+                newWifiNetwork(mWifiInfo));
     }
 
     protected void verifyLastQsDataDirection(boolean in, boolean out) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index 7d5278e..eb1bcc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -146,9 +146,17 @@
         }
 
     @Test
+    fun activeRows_noRows_isEmpty() =
+        testScope.runTest {
+            val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+
+            assertThat(activeHeadsUpRows).isEmpty()
+        }
+
+    @Test
     fun pinnedRows_noRows_isEmpty() =
         testScope.runTest {
-            val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+            val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
 
             assertThat(pinnedHeadsUpRows).isEmpty()
         }
@@ -156,7 +164,7 @@
     @Test
     fun pinnedRows_noPinnedRows_isEmpty() =
         testScope.runTest {
-            val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+            val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
             // WHEN no rows are pinned
             headsUpRepository.setNotifications(
                 fakeHeadsUpRowRepository("key 0"),
@@ -170,9 +178,27 @@
         }
 
     @Test
+    fun activeRows_noPinnedRows_containsAllRows() =
+        testScope.runTest {
+            val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+            // WHEN no rows are pinned
+            val rows =
+                arrayListOf(
+                    fakeHeadsUpRowRepository("key 0"),
+                    fakeHeadsUpRowRepository("key 1"),
+                    fakeHeadsUpRowRepository("key 2"),
+                )
+            headsUpRepository.setNotifications(rows)
+            runCurrent()
+
+            // THEN all rows are present
+            assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+        }
+
+    @Test
     fun pinnedRows_hasPinnedRows_containsPinnedRows() =
         testScope.runTest {
-            val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+            val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
             // WHEN some rows are pinned
             val rows =
                 arrayListOf(
@@ -188,9 +214,27 @@
         }
 
     @Test
+    fun pinnedRows_hasPinnedRows_containsAllRows() =
+        testScope.runTest {
+            val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+            // WHEN no rows are pinned
+            val rows =
+                arrayListOf(
+                    fakeHeadsUpRowRepository("key 0", isPinned = true),
+                    fakeHeadsUpRowRepository("key 1", isPinned = true),
+                    fakeHeadsUpRowRepository("key 2"),
+                )
+            headsUpRepository.setNotifications(rows)
+            runCurrent()
+
+            // THEN all rows are present
+            assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+        }
+
+    @Test
     fun pinnedRows_rowGetsPinned_containsPinnedRows() =
         testScope.runTest {
-            val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+            val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
             // GIVEN some rows are pinned
             val rows =
                 arrayListOf(
@@ -210,9 +254,34 @@
         }
 
     @Test
+    fun activeRows_rowGetsPinned_containsAllRows() =
+        testScope.runTest {
+            val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+            // GIVEN some rows are pinned
+            val rows =
+                arrayListOf(
+                    fakeHeadsUpRowRepository("key 0", isPinned = true),
+                    fakeHeadsUpRowRepository("key 1", isPinned = true),
+                    fakeHeadsUpRowRepository("key 2"),
+                )
+            headsUpRepository.setNotifications(rows)
+            runCurrent()
+
+            // THEN all rows are present
+            assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+
+            // WHEN all rows gets pinned
+            rows[2].isPinned.value = true
+            runCurrent()
+
+            // THEN no change
+            assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+        }
+
+    @Test
     fun pinnedRows_allRowsPinned_containsAllRows() =
         testScope.runTest {
-            val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+            val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
             // WHEN all rows are pinned
             val rows =
                 arrayListOf(
@@ -228,9 +297,27 @@
         }
 
     @Test
+    fun activeRows_allRowsPinned_containsAllRows() =
+        testScope.runTest {
+            val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+            // WHEN all rows are pinned
+            val rows =
+                arrayListOf(
+                    fakeHeadsUpRowRepository("key 0", isPinned = true),
+                    fakeHeadsUpRowRepository("key 1", isPinned = true),
+                    fakeHeadsUpRowRepository("key 2", isPinned = true),
+                )
+            headsUpRepository.setNotifications(rows)
+            runCurrent()
+
+            // THEN no rows are filtered
+            assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+        }
+
+    @Test
     fun pinnedRows_rowGetsUnPinned_containsPinnedRows() =
         testScope.runTest {
-            val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+            val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
             // GIVEN all rows are pinned
             val rows =
                 arrayListOf(
@@ -250,9 +337,31 @@
         }
 
     @Test
+    fun activeRows_rowGetsUnPinned_containsAllRows() =
+        testScope.runTest {
+            val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+            // GIVEN all rows are pinned
+            val rows =
+                arrayListOf(
+                    fakeHeadsUpRowRepository("key 0", isPinned = true),
+                    fakeHeadsUpRowRepository("key 1", isPinned = true),
+                    fakeHeadsUpRowRepository("key 2", isPinned = true),
+                )
+            headsUpRepository.setNotifications(rows)
+            runCurrent()
+
+            // WHEN a row gets unpinned
+            rows[0].isPinned.value = false
+            runCurrent()
+
+            // THEN all rows are still present
+            assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+        }
+
+    @Test
     fun pinnedRows_rowGetsPinnedAndUnPinned_containsTheSameInstance() =
         testScope.runTest {
-            val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+            val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
 
             val rows =
                 arrayListOf(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index a099c9d..48608eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -35,9 +35,6 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
-import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
@@ -51,33 +48,23 @@
 import com.android.systemui.statusbar.notification.row.shared.NewRemoteViews
 import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel
 import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
-import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState
 import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder
 import com.android.systemui.statusbar.policy.SmartReplyStateInflater
-import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
 import java.util.concurrent.TimeUnit
-import kotlinx.coroutines.DisposableHandle
 import org.junit.Assert
 import org.junit.Before
 import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
-import org.mockito.kotlin.argThat
-import org.mockito.kotlin.clearInvocations
-import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.eq
-import org.mockito.kotlin.inOrder
 import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
 import org.mockito.kotlin.spy
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyNoMoreInteractions
 import org.mockito.kotlin.whenever
 
 @SmallTest
@@ -118,45 +105,6 @@
             }
         }
 
-    private var fakeRonContentModel: RichOngoingContentModel? = null
-    private val fakeRonExtractor =
-        object : RichOngoingNotificationContentExtractor {
-            override fun extractContentModel(
-                entry: NotificationEntry,
-                builder: Notification.Builder,
-                systemUIContext: Context,
-                packageContext: Context
-            ): RichOngoingContentModel? = fakeRonContentModel
-        }
-
-    private var fakeContractedRonViewHolder: ContentViewInflationResult = NullContentView
-    private var fakeExpandedRonViewHolder: ContentViewInflationResult = NullContentView
-    private var fakeHeadsUpRonViewHolder: ContentViewInflationResult = NullContentView
-    private var fakeRonViewInflater =
-        spy(
-            object : RichOngoingNotificationViewInflater {
-                override fun inflateView(
-                    contentModel: RichOngoingContentModel,
-                    existingView: View?,
-                    entry: NotificationEntry,
-                    systemUiContext: Context,
-                    parentView: ViewGroup,
-                    viewType: RichOngoingNotificationViewType
-                ): ContentViewInflationResult =
-                    when (viewType) {
-                        RichOngoingNotificationViewType.Contracted -> fakeContractedRonViewHolder
-                        RichOngoingNotificationViewType.Expanded -> fakeExpandedRonViewHolder
-                        RichOngoingNotificationViewType.HeadsUp -> fakeHeadsUpRonViewHolder
-                    }
-
-                override fun canKeepView(
-                    contentModel: RichOngoingContentModel,
-                    existingView: View?,
-                    viewType: RichOngoingNotificationViewType
-                ): Boolean = false
-            }
-        )
-
     @Before
     fun setUp() {
         allowTestableLooperAsMainThread()
@@ -167,15 +115,12 @@
                 .setContentText("Text")
                 .setStyle(Notification.BigTextStyle().bigText("big text"))
         testHelper = NotificationTestHelper(mContext, mDependency)
-        testHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL)
         row = spy(testHelper.createRow(builder.build()))
         notificationInflater =
             NotificationRowContentBinderImpl(
                 cache,
                 mock(),
                 mock<ConversationNotificationProcessor>(),
-                fakeRonExtractor,
-                fakeRonViewInflater,
                 mock(),
                 smartReplyStateInflater,
                 layoutInflaterFactoryProvider,
@@ -405,496 +350,6 @@
     }
 
     @Test
-    fun testRonModelRequiredForRonView() {
-        fakeRonContentModel = null
-        val contractedRonView = View(context)
-        val expandedRonView = View(context)
-        val headsUpRonView = View(context)
-        fakeContractedRonViewHolder =
-            InflatedContentViewHolder(view = contractedRonView, binder = mock())
-        fakeExpandedRonViewHolder =
-            InflatedContentViewHolder(view = expandedRonView, binder = mock())
-        fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = headsUpRonView, binder = mock())
-
-        // WHEN inflater inflates
-        val contentToInflate =
-            FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
-        inflateAndWait(notificationInflater, contentToInflate, row)
-        verifyNoMoreInteractions(fakeRonViewInflater)
-    }
-
-    @Test
-    fun testRonModelCleansUpRemoteViews() {
-        val ronView = View(context)
-
-        val entry = row.entry
-
-        fakeRonContentModel = mock<TimerContentModel>()
-        fakeContractedRonViewHolder =
-            InflatedContentViewHolder(view = ronView, binder = mock<DeferredContentViewBinder>())
-
-        // WHEN inflater inflates
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
-        // VERIFY
-        verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_CONTRACTED))
-        verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_EXPANDED))
-        verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_HEADS_UP))
-    }
-
-    @Test
-    fun testRonModelCleansUpSmartReplies() {
-        val ronView = View(context)
-
-        val privateLayout = spy(row.privateLayout)
-
-        row.privateLayout = privateLayout
-
-        fakeRonContentModel = mock<TimerContentModel>()
-        fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mock())
-
-        // WHEN inflater inflates
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
-        // VERIFY
-        verify(privateLayout).setExpandedInflatedSmartReplies(eq(null))
-        verify(privateLayout).setHeadsUpInflatedSmartReplies(eq(null))
-    }
-
-    @Test
-    fun testRonModelTriggersInflationOfContractedRonView() {
-        val mockRonModel = mock<TimerContentModel>()
-        val ronView = View(context)
-        val mockBinder = mock<DeferredContentViewBinder>()
-
-        val entry = row.entry
-        val privateLayout = row.privateLayout
-
-        fakeRonContentModel = mockRonModel
-        fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
-        // WHEN inflater inflates
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
-        // VERIFY that the inflater is invoked
-        verify(fakeRonViewInflater)
-            .inflateView(
-                eq(mockRonModel),
-                any(),
-                eq(entry),
-                any(),
-                eq(privateLayout),
-                eq(RichOngoingNotificationViewType.Contracted)
-            )
-        assertThat(row.privateLayout.contractedChild).isSameInstanceAs(ronView)
-        verify(mockBinder).setupContentViewBinder()
-    }
-
-    @Test
-    fun testRonModelTriggersInflationOfExpandedRonView() {
-        val mockRonModel = mock<TimerContentModel>()
-        val ronView = View(context)
-        val mockBinder = mock<DeferredContentViewBinder>()
-
-        val entry = row.entry
-        val privateLayout = row.privateLayout
-
-        fakeRonContentModel = mockRonModel
-        fakeExpandedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
-        // WHEN inflater inflates
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
-
-        // VERIFY that the inflater is invoked
-        verify(fakeRonViewInflater)
-            .inflateView(
-                eq(mockRonModel),
-                any(),
-                eq(entry),
-                any(),
-                eq(privateLayout),
-                eq(RichOngoingNotificationViewType.Expanded)
-            )
-        assertThat(row.privateLayout.expandedChild).isSameInstanceAs(ronView)
-        verify(mockBinder).setupContentViewBinder()
-    }
-
-    @Test
-    fun testRonModelTriggersInflationOfHeadsUpRonView() {
-        val mockRonModel = mock<TimerContentModel>()
-        val ronView = View(context)
-        val mockBinder = mock<DeferredContentViewBinder>()
-
-        val entry = row.entry
-        val privateLayout = row.privateLayout
-
-        fakeRonContentModel = mockRonModel
-        fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
-        // WHEN inflater inflates
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
-
-        // VERIFY that the inflater is invoked
-        verify(fakeRonViewInflater)
-            .inflateView(
-                eq(mockRonModel),
-                any(),
-                eq(entry),
-                any(),
-                eq(privateLayout),
-                eq(RichOngoingNotificationViewType.HeadsUp)
-            )
-        assertThat(row.privateLayout.headsUpChild).isSameInstanceAs(ronView)
-        verify(mockBinder).setupContentViewBinder()
-    }
-
-    @Test
-    fun keepExistingViewForContractedRonNotChangingContractedChild() {
-        val oldHandle = mock<DisposableHandle>()
-        val mockRonModel = mock<TimerContentModel>()
-
-        row.privateLayout.mContractedBinderHandle = oldHandle
-        val entry = spy(row.entry)
-        row.entry = entry
-        val privateLayout = spy(row.privateLayout)
-        row.privateLayout = privateLayout
-
-        fakeRonContentModel = mockRonModel
-        fakeContractedRonViewHolder = KeepExistingView
-
-        // WHEN inflater inflates
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
-        // THEN  do not dispose old contracted binder handle and change contracted child
-        verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
-        verifyNoMoreInteractions(oldHandle)
-        verify(privateLayout, never()).setContractedChild(any())
-    }
-
-    @Test
-    fun keepExistingViewForExpandedRonNotChangingExpandedChild() {
-        val oldHandle = mock<DisposableHandle>()
-        val mockRonModel = mock<TimerContentModel>()
-
-        row.privateLayout.mExpandedBinderHandle = oldHandle
-        val entry = spy(row.entry)
-        row.entry = entry
-        val privateLayout = spy(row.privateLayout)
-        row.privateLayout = privateLayout
-
-        fakeRonContentModel = mockRonModel
-        fakeExpandedRonViewHolder = KeepExistingView
-
-        // WHEN inflater inflates
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
-
-        // THEN  do not dispose old expanded binder handle and change expanded child
-        verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
-        verifyNoMoreInteractions(oldHandle)
-        verify(privateLayout, never()).setExpandedChild(any())
-    }
-
-    @Test
-    fun keepExistingViewForHeadsUpRonNotChangingHeadsUpChild() {
-        val oldHandle = mock<DisposableHandle>()
-        val mockRonModel = mock<TimerContentModel>()
-
-        row.privateLayout.mHeadsUpBinderHandle = oldHandle
-        val entry = spy(row.entry)
-        row.entry = entry
-        val privateLayout = spy(row.privateLayout)
-        row.privateLayout = privateLayout
-
-        fakeRonContentModel = mockRonModel
-        fakeHeadsUpRonViewHolder = KeepExistingView
-
-        // WHEN inflater inflates
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
-
-        // THEN - do not dispose old heads up binder handle and change heads up child
-        verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
-        verifyNoMoreInteractions(oldHandle)
-        verify(privateLayout, never()).setHeadsUpChild(any())
-    }
-
-    @Test
-    fun nullContentViewForContractedRonAppliesElementsInOrder() {
-        val oldHandle = mock<DisposableHandle>()
-        val mockRonModel = mock<TimerContentModel>()
-
-        row.privateLayout.mContractedBinderHandle = oldHandle
-        val entry = spy(row.entry)
-        row.entry = entry
-        val privateLayout = spy(row.privateLayout)
-        row.privateLayout = privateLayout
-
-        fakeRonContentModel = mockRonModel
-        fakeContractedRonViewHolder = NullContentView
-
-        // WHEN inflater inflates
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
-        // Validate that these 4 steps happen in this precise order
-        inOrder(oldHandle, entry, privateLayout, cache) {
-            verify(oldHandle).dispose()
-            verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
-            verify(privateLayout).setContractedChild(eq(null))
-        }
-    }
-
-    @Test
-    fun nullContentViewForExpandedRonAppliesElementsInOrder() {
-        val oldHandle = mock<DisposableHandle>()
-        val mockRonModel = mock<TimerContentModel>()
-
-        row.privateLayout.mExpandedBinderHandle = oldHandle
-        val entry = spy(row.entry)
-        row.entry = entry
-        val privateLayout = spy(row.privateLayout)
-        row.privateLayout = privateLayout
-
-        fakeRonContentModel = mockRonModel
-        fakeExpandedRonViewHolder = NullContentView
-
-        // WHEN inflater inflates
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
-
-        // Validate that these 4 steps happen in this precise order
-        inOrder(oldHandle, entry, privateLayout, cache) {
-            verify(oldHandle).dispose()
-            verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
-            verify(privateLayout).setExpandedChild(eq(null))
-        }
-    }
-
-    @Test
-    fun nullContentViewForHeadsUpRonAppliesElementsInOrder() {
-        val oldHandle = mock<DisposableHandle>()
-        val mockRonModel = mock<TimerContentModel>()
-
-        row.privateLayout.mHeadsUpBinderHandle = oldHandle
-        val entry = spy(row.entry)
-        row.entry = entry
-        val privateLayout = spy(row.privateLayout)
-        row.privateLayout = privateLayout
-
-        fakeRonContentModel = mockRonModel
-        fakeHeadsUpRonViewHolder = NullContentView
-
-        // WHEN inflater inflates
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
-
-        // Validate that these 4 steps happen in this precise order
-        inOrder(oldHandle, entry, privateLayout, cache) {
-            verify(oldHandle).dispose()
-            verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
-            verify(privateLayout).setHeadsUpChild(eq(null))
-        }
-    }
-
-    @Test
-    fun contractedRonViewAppliesElementsInOrder() {
-        val oldHandle = mock<DisposableHandle>()
-        val mockRonModel = mock<TimerContentModel>()
-        val ronView = View(context)
-        val mockBinder = mock<DeferredContentViewBinder>()
-
-        row.privateLayout.mContractedBinderHandle = oldHandle
-        val entry = spy(row.entry)
-        row.entry = entry
-        val privateLayout = spy(row.privateLayout)
-        row.privateLayout = privateLayout
-
-        fakeRonContentModel = mockRonModel
-        fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
-        // WHEN inflater inflates
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-
-        // Validate that these 4 steps happen in this precise order
-        inOrder(oldHandle, entry, privateLayout, mockBinder) {
-            verify(oldHandle).dispose()
-            verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
-            verify(privateLayout).setContractedChild(eq(ronView))
-            verify(mockBinder).setupContentViewBinder()
-        }
-    }
-
-    @Test
-    fun expandedRonViewAppliesElementsInOrder() {
-        val oldHandle = mock<DisposableHandle>()
-        val mockRonModel = mock<TimerContentModel>()
-        val ronView = View(context)
-        val mockBinder = mock<DeferredContentViewBinder>()
-
-        row.privateLayout.mExpandedBinderHandle = oldHandle
-        val entry = spy(row.entry)
-        row.entry = entry
-        val privateLayout = spy(row.privateLayout)
-        row.privateLayout = privateLayout
-
-        fakeRonContentModel = mockRonModel
-        fakeExpandedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
-        // WHEN inflater inflates
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
-
-        // Validate that these 4 steps happen in this precise order
-        inOrder(oldHandle, entry, privateLayout, mockBinder) {
-            verify(oldHandle).dispose()
-            verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
-            verify(privateLayout).setExpandedChild(eq(ronView))
-            verify(mockBinder).setupContentViewBinder()
-        }
-    }
-
-    @Test
-    fun headsUpRonViewAppliesElementsInOrder() {
-        val oldHandle = mock<DisposableHandle>()
-        val mockRonModel = mock<TimerContentModel>()
-        val ronView = View(context)
-        val mockBinder = mock<DeferredContentViewBinder>()
-
-        row.privateLayout.mHeadsUpBinderHandle = oldHandle
-        val entry = spy(row.entry)
-        row.entry = entry
-        val privateLayout = spy(row.privateLayout)
-        row.privateLayout = privateLayout
-
-        fakeRonContentModel = mockRonModel
-        fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
-
-        // WHEN inflater inflates
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
-
-        // Validate that these 4 steps happen in this precise order
-        inOrder(oldHandle, entry, privateLayout, mockBinder) {
-            verify(oldHandle).dispose()
-            verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
-            verify(privateLayout).setHeadsUpChild(eq(ronView))
-            verify(mockBinder).setupContentViewBinder()
-        }
-    }
-
-    @Test
-    fun testRonNotReinflating() {
-        val oldContractedBinderHandle = mock<DisposableHandle>()
-        val oldExpandedBinderHandle = mock<DisposableHandle>()
-        val oldHeadsUpBinderHandle = mock<DisposableHandle>()
-
-        val contractedBinderHandle = mock<DisposableHandle>()
-        val expandedBinderHandle = mock<DisposableHandle>()
-        val headsUpBinderHandle = mock<DisposableHandle>()
-
-        val contractedRonView = View(context)
-        val expandedRonView = View(context)
-        val headsUpRonView = View(context)
-
-        val mockRonModel1 = mock<TimerContentModel>()
-        val mockRonModel2 = mock<TimerContentModel>()
-
-        val mockContractedViewBinder = mock<DeferredContentViewBinder>()
-        val mockExpandedViewBinder = mock<DeferredContentViewBinder>()
-        val mockHeadsUpViewBinder = mock<DeferredContentViewBinder>()
-
-        doReturn(contractedBinderHandle).whenever(mockContractedViewBinder).setupContentViewBinder()
-        doReturn(expandedBinderHandle).whenever(mockExpandedViewBinder).setupContentViewBinder()
-        doReturn(headsUpBinderHandle).whenever(mockHeadsUpViewBinder).setupContentViewBinder()
-
-        row.privateLayout.mContractedBinderHandle = oldContractedBinderHandle
-        row.privateLayout.mExpandedBinderHandle = oldExpandedBinderHandle
-        row.privateLayout.mHeadsUpBinderHandle = oldHeadsUpBinderHandle
-        val entry = spy(row.entry)
-        row.entry = entry
-        val privateLayout = spy(row.privateLayout)
-        row.privateLayout = privateLayout
-
-        // WHEN inflater inflates both a model and a view
-        fakeRonContentModel = mockRonModel1
-        fakeContractedRonViewHolder =
-            InflatedContentViewHolder(view = contractedRonView, binder = mockContractedViewBinder)
-        fakeExpandedRonViewHolder =
-            InflatedContentViewHolder(view = expandedRonView, binder = mockExpandedViewBinder)
-        fakeHeadsUpRonViewHolder =
-            InflatedContentViewHolder(view = headsUpRonView, binder = mockHeadsUpViewBinder)
-
-        val contentToInflate =
-            FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
-        inflateAndWait(notificationInflater, contentToInflate, row)
-
-        // Validate that these 4 steps happen in this precise order
-        inOrder(
-            oldContractedBinderHandle,
-            oldExpandedBinderHandle,
-            oldHeadsUpBinderHandle,
-            entry,
-            privateLayout,
-            mockContractedViewBinder,
-            mockExpandedViewBinder,
-            mockHeadsUpViewBinder,
-            contractedBinderHandle,
-            expandedBinderHandle,
-            headsUpBinderHandle
-        ) {
-            verify(oldContractedBinderHandle).dispose()
-            verify(oldExpandedBinderHandle).dispose()
-            verify(oldHeadsUpBinderHandle).dispose()
-
-            verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel1 })
-
-            verify(privateLayout).setContractedChild(eq(contractedRonView))
-            verify(mockContractedViewBinder).setupContentViewBinder()
-
-            verify(privateLayout).setExpandedChild(eq(expandedRonView))
-            verify(mockExpandedViewBinder).setupContentViewBinder()
-
-            verify(privateLayout).setHeadsUpChild(eq(headsUpRonView))
-            verify(mockHeadsUpViewBinder).setupContentViewBinder()
-
-            verify(contractedBinderHandle, never()).dispose()
-            verify(expandedBinderHandle, never()).dispose()
-            verify(headsUpBinderHandle, never()).dispose()
-        }
-
-        clearInvocations(
-            oldContractedBinderHandle,
-            oldExpandedBinderHandle,
-            oldHeadsUpBinderHandle,
-            entry,
-            privateLayout,
-            mockContractedViewBinder,
-            mockExpandedViewBinder,
-            mockHeadsUpViewBinder,
-            contractedBinderHandle,
-            expandedBinderHandle,
-            headsUpBinderHandle
-        )
-
-        // THEN when the inflater inflates just a model
-        fakeRonContentModel = mockRonModel2
-        fakeContractedRonViewHolder = KeepExistingView
-        fakeExpandedRonViewHolder = KeepExistingView
-        fakeHeadsUpRonViewHolder = KeepExistingView
-
-        inflateAndWait(notificationInflater, contentToInflate, row)
-
-        // Validate that for reinflation, the only thing we do us update the model
-        verify(contractedBinderHandle, never()).dispose()
-        verify(expandedBinderHandle, never()).dispose()
-        verify(headsUpBinderHandle, never()).dispose()
-        verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel2 })
-        verify(privateLayout, never()).setContractedChild(any())
-        verify(privateLayout, never()).setExpandedChild(any())
-        verify(privateLayout, never()).setHeadsUpChild(any())
-        verify(mockContractedViewBinder, never()).setupContentViewBinder()
-        verify(mockExpandedViewBinder, never()).setupContentViewBinder()
-        verify(mockHeadsUpViewBinder, never()).setupContentViewBinder()
-        verify(contractedBinderHandle, never()).dispose()
-        verify(expandedBinderHandle, never()).dispose()
-        verify(headsUpBinderHandle, never()).dispose()
-    }
-
-    @Test
     fun testNotificationViewHeightTooSmallFailsValidation() {
         val validationError =
             getValidationError(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 75376e6..2340d02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -200,8 +200,6 @@
                                 mock(NotifRemoteViewCache.class),
                                 mock(NotificationRemoteInputManager.class),
                                 mock(ConversationNotificationProcessor.class),
-                                mock(RichOngoingNotificationContentExtractor.class),
-                                mock(RichOngoingNotificationViewInflater.class),
                                 mock(Executor.class),
                                 new MockSmartReplyInflater(),
                                 mock(NotifLayoutInflaterFactory.Provider.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
index 6b3fb5b..503fa78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
@@ -29,12 +29,13 @@
 import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflatePublicSingleLineView
 import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
 import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder
-import com.android.systemui.util.mockito.mock
 import kotlin.test.assertEquals
 import kotlin.test.assertNotNull
+import kotlin.test.assertNull
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -69,7 +70,7 @@
                 reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
                 entry = row.entry,
                 context = context,
-                logger = mock()
+                logger = mock(),
             )
 
         val publicView =
@@ -78,7 +79,7 @@
                 reinflateFlags = FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
                 entry = row.entry,
                 context = context,
-                logger = mock()
+                logger = mock(),
             )
         assertNotNull(publicView)
 
@@ -114,7 +115,7 @@
                 .addMessage(
                     "How about lunch?",
                     System.currentTimeMillis(),
-                    Person.Builder().setName("user2").build()
+                    Person.Builder().setName("user2").build(),
                 )
                 .setGroupConversation(true)
         notificationBuilder.setStyle(style).setShortcutId(SHORTCUT_ID)
@@ -127,7 +128,7 @@
                 reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
                 entry = row.entry,
                 context = context,
-                logger = mock()
+                logger = mock(),
             )
                 as HybridConversationNotificationView
 
@@ -137,7 +138,7 @@
                 reinflateFlags = FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
                 entry = row.entry,
                 context = context,
-                logger = mock()
+                logger = mock(),
             )
                 as HybridConversationNotificationView
         assertNotNull(publicView)
@@ -150,10 +151,7 @@
                 systemUiContext = context,
             )
         // WHEN: binds the view
-        SingleLineViewBinder.bind(
-            viewModel,
-            view,
-        )
+        SingleLineViewBinder.bind(viewModel, view)
 
         // THEN: the single-line conversation view should be bound with view model's corresponding
         // fields
@@ -161,10 +159,55 @@
         assertEquals(viewModel.contentText, view.textView.text)
         assertEquals(
             viewModel.conversationData?.conversationSenderName,
-            view.conversationSenderNameView.text
+            view.conversationSenderNameView.text,
         )
     }
 
+    @Test
+    @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+    fun bindConversationSingleLineView_nonConversationViewModel() {
+        // GIVEN: a ConversationSingleLineView, and a nonConversationViewModel
+        val style = Notification.BigTextStyle().bigText(CONTENT_TEXT)
+        notificationBuilder.setStyle(style)
+        val notification = notificationBuilder.build()
+        val row: ExpandableNotificationRow = helper.createRow(notification)
+
+        val view =
+            inflatePrivateSingleLineView(
+                isConversation = true,
+                reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
+                entry = row.entry,
+                context = context,
+                logger = mock(),
+            )
+
+        val publicView =
+            inflatePublicSingleLineView(
+                isConversation = true,
+                reinflateFlags = FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
+                entry = row.entry,
+                context = context,
+                logger = mock(),
+            )
+        assertNotNull(publicView)
+
+        val viewModel =
+            SingleLineViewInflater.inflateSingleLineViewModel(
+                notification = notification,
+                messagingStyle = null,
+                builder = notificationBuilder,
+                systemUiContext = context,
+            )
+        // WHEN: binds the view with the view model
+        SingleLineViewBinder.bind(viewModel, view)
+
+        // THEN: the single-line view should be bound with view model's corresponding
+        // fields as a normal non-conversation single-line view
+        assertEquals(viewModel.titleText, view?.titleView?.text)
+        assertEquals(viewModel.contentText, view?.textView?.text)
+        assertNull(viewModel.conversationData)
+    }
+
     private companion object {
         const val CHANNEL_ID = "CHANNEL_ID"
         const val CONTENT_TITLE = "A Cool New Feature"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 2ed3473..8360042 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -160,11 +160,11 @@
         testableLooper = TestableLooper.get(this)
         context.orCreateTestableResources.addOverride(
             com.android.internal.R.string.status_bar_alarm_clock,
-            ALARM_SLOT
+            ALARM_SLOT,
         )
         context.orCreateTestableResources.addOverride(
             com.android.internal.R.string.status_bar_managed_profile,
-            MANAGED_PROFILE_SLOT
+            MANAGED_PROFILE_SLOT,
         )
         whenever(devicePolicyManager.resources).thenReturn(devicePolicyManagerResources)
         whenever(devicePolicyManagerResources.getString(anyString(), any())).thenReturn("")
@@ -430,8 +430,8 @@
                     eq(mContext.packageName),
                     eq(android.R.drawable.ic_lock_lock),
                     any(), // non-null
-                    eq("Bedtime Mode"),
-                    eq(StatusBarIcon.Shape.FIXED_SPACE)
+                    eq("Bedtime Mode is on"),
+                    eq(StatusBarIcon.Shape.FIXED_SPACE),
                 )
 
             zenModeRepository.deactivateMode("bedtime")
@@ -443,8 +443,8 @@
                     eq(null),
                     eq(android.R.drawable.ic_media_play),
                     any(), // non-null
-                    eq("Other Mode"),
-                    eq(StatusBarIcon.Shape.FIXED_SPACE)
+                    eq("Other Mode is on"),
+                    eq(StatusBarIcon.Shape.FIXED_SPACE),
                 )
 
             zenModeRepository.deactivateMode("other")
@@ -538,7 +538,7 @@
             privacyLogger,
             fakeConnectedDisplayStateProvider,
             kosmos.zenModeInteractor,
-            JavaAdapter(testScope.backgroundScope)
+            JavaAdapter(testScope.backgroundScope),
         )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelKosmos.kt
similarity index 70%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelKosmos.kt
index 15ed1b3..8422942 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelKosmos.kt
@@ -14,12 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.data.repository
+package com.android.systemui.communal.ui.viewmodel
 
-import android.view.windowManagerService
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
 
-val Kosmos.systemGestureExclusionRepository by Fixture {
-    SystemGestureExclusionRepository(windowManager = windowManagerService)
-}
+val Kosmos.resizeableItemFrameViewModel by Kosmos.Fixture { ResizeableItemFrameViewModel() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt
new file mode 100644
index 0000000..83df5d8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.display.data.repository
+
+import android.view.Display
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+/** Fake [FocusedDisplayRepository] for testing. */
+class FakeFocusedDisplayRepository @Inject constructor() : FocusedDisplayRepository {
+    private val flow = MutableStateFlow<Int>(Display.DEFAULT_DISPLAY)
+
+    override val focusedDisplayId: StateFlow<Int>
+        get() = flow.asStateFlow()
+
+    suspend fun emit(focusedDisplay: Int) = flow.emit(focusedDisplay)
+}
+
+@Module
+interface FakeFocusedDisplayRepositoryModule {
+    @Binds fun bindFake(fake: FakeFocusedDisplayRepository): FocusedDisplayRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
new file mode 100644
index 0000000..e4a2a87
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.plugins
+
+import android.media.AudioManager
+import android.media.AudioManager.CsdWarning
+import android.os.Handler
+import android.os.VibrationEffect
+import androidx.core.util.getOrElse
+import java.util.concurrent.CopyOnWriteArraySet
+
+class FakeVolumeDialogController(private val audioManager: AudioManager) : VolumeDialogController {
+
+    var isVisible: Boolean = false
+        private set
+
+    var hasScheduledTouchFeedback: Boolean = false
+        private set
+
+    var vibrationEffect: VibrationEffect? = null
+        private set
+
+    var hasUserActivity: Boolean = false
+        private set
+
+    private var hasVibrator: Boolean = true
+
+    private val state = VolumeDialogController.State()
+    private val callbacks = CopyOnWriteArraySet<VolumeDialogController.Callbacks>()
+
+    override fun setActiveStream(stream: Int) {
+        // ensure streamState existence for the active stream
+        state.states.getOrElse(stream) {
+            VolumeDialogController.StreamState().also { streamState ->
+                state.states.put(stream, streamState)
+            }
+        }
+        state.activeStream = stream
+    }
+
+    override fun setStreamVolume(stream: Int, userLevel: Int) {
+        val streamState =
+            state.states.getOrElse(stream) {
+                VolumeDialogController.StreamState().also { streamState ->
+                    state.states.put(stream, streamState)
+                }
+            }
+        streamState.level = userLevel.coerceIn(streamState.levelMin, streamState.levelMax)
+    }
+
+    override fun setRingerMode(ringerModeNormal: Int, external: Boolean) {
+        if (external) {
+            state.ringerModeExternal = ringerModeNormal
+        } else {
+            state.ringerModeInternal = ringerModeNormal
+        }
+    }
+
+    fun setHasVibrator(hasVibrator: Boolean) {
+        this.hasVibrator = hasVibrator
+    }
+
+    override fun hasVibrator(): Boolean = hasVibrator
+
+    override fun vibrate(effect: VibrationEffect) {
+        vibrationEffect = effect
+    }
+
+    override fun scheduleTouchFeedback() {
+        hasScheduledTouchFeedback = true
+    }
+
+    fun resetScheduledTouchFeedback() {
+        hasScheduledTouchFeedback = false
+    }
+
+    override fun getAudioManager(): AudioManager = audioManager
+
+    override fun notifyVisible(visible: Boolean) {
+        isVisible = visible
+    }
+
+    override fun addCallback(callbacks: VolumeDialogController.Callbacks?, handler: Handler?) {
+        this.callbacks.add(callbacks)
+    }
+
+    override fun removeCallback(callbacks: VolumeDialogController.Callbacks?) {
+        this.callbacks.remove(callbacks)
+    }
+
+    override fun userActivity() {
+        hasUserActivity = true
+    }
+
+    fun resetUserActivity() {
+        hasUserActivity = false
+    }
+
+    override fun getState() {
+        callbacks.sendEvent { it.onStateChanged(state) }
+    }
+
+    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowRequested */
+    fun onShowRequested(reason: Int, keyguardLocked: Boolean, lockTaskModeState: Int) {
+        callbacks.sendEvent { it.onShowRequested(reason, keyguardLocked, lockTaskModeState) }
+    }
+
+    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onDismissRequested */
+    fun onDismissRequested(reason: Int) {
+        callbacks.sendEvent { it.onDismissRequested(reason) }
+    }
+
+    /**
+     * @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onLayoutDirectionChanged
+     */
+    fun onLayoutDirectionChanged(layoutDirection: Int) {
+        callbacks.sendEvent { it.onLayoutDirectionChanged(layoutDirection) }
+    }
+
+    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onConfigurationChanged */
+    fun onConfigurationChanged() {
+        callbacks.sendEvent { it.onConfigurationChanged() }
+    }
+
+    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowVibrateHint */
+    fun onShowVibrateHint() {
+        callbacks.sendEvent { it.onShowVibrateHint() }
+    }
+
+    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowSilentHint */
+    fun onShowSilentHint() {
+        callbacks.sendEvent { it.onShowSilentHint() }
+    }
+
+    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onScreenOff */
+    fun onScreenOff() {
+        callbacks.sendEvent { it.onScreenOff() }
+    }
+
+    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowSafetyWarning */
+    fun onShowSafetyWarning(flags: Int) {
+        callbacks.sendEvent { it.onShowSafetyWarning(flags) }
+    }
+
+    /**
+     * @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onAccessibilityModeChanged
+     */
+    fun onAccessibilityModeChanged(showA11yStream: Boolean?) {
+        callbacks.sendEvent { it.onAccessibilityModeChanged(showA11yStream) }
+    }
+
+    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowCsdWarning */
+    fun onShowCsdWarning(@CsdWarning csdWarning: Int, durationMs: Int) {
+        callbacks.sendEvent { it.onShowCsdWarning(csdWarning, durationMs) }
+    }
+
+    /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onVolumeChangedFromKey */
+    fun onVolumeChangedFromKey() {
+        callbacks.sendEvent { it.onVolumeChangedFromKey() }
+    }
+
+    override fun getCaptionsEnabledState(checkForSwitchState: Boolean) {
+        error("Unsupported for the new Volume Dialog")
+    }
+
+    override fun setCaptionsEnabledState(enabled: Boolean) {
+        error("Unsupported for the new Volume Dialog")
+    }
+
+    override fun getCaptionsComponentState(fromTooltip: Boolean) {
+        error("Unsupported for the new Volume Dialog")
+    }
+}
+
+private inline fun CopyOnWriteArraySet<VolumeDialogController.Callbacks>.sendEvent(
+    event: (callback: VolumeDialogController.Callbacks) -> Unit
+) {
+    for (callback in this) {
+        event(callback)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/VolumeDialogControllerKosmos.kt
similarity index 70%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/plugins/VolumeDialogControllerKosmos.kt
index 15ed1b3..2f6d4fa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/VolumeDialogControllerKosmos.kt
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.data.repository
+package com.android.systemui.plugins
 
-import android.view.windowManagerService
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
+import org.mockito.kotlin.mock
 
-val Kosmos.systemGestureExclusionRepository by Fixture {
-    SystemGestureExclusionRepository(windowManager = windowManagerService)
-}
+val Kosmos.fakeVolumeDialogController by Kosmos.Fixture { FakeVolumeDialogController(mock {}) }
+var Kosmos.volumeDialogController: VolumeDialogController by
+    Kosmos.Fixture { fakeVolumeDialogController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
index dbb3e38..c218ff6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.qs.footerActionsController
 import com.android.systemui.qs.footerActionsViewModelFactory
 import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
+import com.android.systemui.qs.panels.ui.viewmodel.paginatedGridViewModel
 import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModel
 import com.android.systemui.shade.largeScreenHeaderHelper
 import com.android.systemui.shade.transition.largeScreenShadeInterpolator
@@ -48,6 +49,7 @@
                     configurationInteractor,
                     largeScreenHeaderHelper,
                     tileSquishinessInteractor,
+                    paginatedGridViewModel,
                     lifecycleScope,
                 )
             }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index f842db4..4f414d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -8,17 +8,14 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.domain.interactor.systemGestureExclusionInteractor
 import com.android.systemui.scene.shared.logger.sceneLogger
 import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.FakeOverlay
-import com.android.systemui.scene.ui.viewmodel.SceneContainerGestureFilter
 import com.android.systemui.scene.ui.viewmodel.SceneContainerHapticsViewModel
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
-import com.android.systemui.settings.displayTracker
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import kotlinx.coroutines.flow.MutableStateFlow
 import org.mockito.kotlin.mock
@@ -31,6 +28,7 @@
         Scenes.Bouncer,
         Scenes.Gone,
         Scenes.Communal,
+        Scenes.Dream,
     )
 }
 
@@ -52,9 +50,10 @@
             Scenes.Gone to 0,
             Scenes.Lockscreen to 0,
             Scenes.Communal to 1,
-            Scenes.Shade to 2,
-            Scenes.QuickSettings to 3,
-            Scenes.Bouncer to 4,
+            Scenes.Dream to 2,
+            Scenes.Shade to 3,
+            Scenes.QuickSettings to 4,
+            Scenes.Bouncer to 5,
         )
 
     SceneContainerConfig(
@@ -72,16 +71,15 @@
 }
 
 val Kosmos.sceneContainerViewModel by Fixture {
-    sceneContainerViewModelFactory.create(mock<View>(), displayTracker.defaultDisplayId, {}).apply {
-        setTransitionState(transitionState)
-    }
+    sceneContainerViewModelFactory
+        .create(mock<View>()) {}
+        .apply { setTransitionState(transitionState) }
 }
 
 val Kosmos.sceneContainerViewModelFactory by Fixture {
     object : SceneContainerViewModel.Factory {
         override fun create(
             view: View,
-            displayId: Int,
             motionEventHandlerReceiver: (SceneContainerViewModel.MotionEventHandler?) -> Unit,
         ): SceneContainerViewModel =
             SceneContainerViewModel(
@@ -91,26 +89,13 @@
                 shadeInteractor = shadeInteractor,
                 splitEdgeDetector = splitEdgeDetector,
                 logger = sceneLogger,
-                gestureFilterFactory = sceneContainerGestureFilterFactory,
                 hapticsViewModelFactory = sceneContainerHapticsViewModelFactory,
                 view = view,
-                displayId = displayId,
                 motionEventHandlerReceiver = motionEventHandlerReceiver,
             )
     }
 }
 
-val Kosmos.sceneContainerGestureFilterFactory by Fixture {
-    object : SceneContainerGestureFilter.Factory {
-        override fun create(displayId: Int): SceneContainerGestureFilter {
-            return SceneContainerGestureFilter(
-                interactor = systemGestureExclusionInteractor,
-                displayId = displayId,
-            )
-        }
-    }
-}
-
 val Kosmos.sceneContainerHapticsViewModelFactory by Fixture {
     object : SceneContainerHapticsViewModel.Factory {
         override fun create(view: View): SceneContainerHapticsViewModel {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index 2eb1573..fc4f05d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -222,8 +222,6 @@
                     Mockito.mock(NotifRemoteViewCache::class.java, STUB_ONLY),
                     remoteInputManager,
                     conversationProcessor,
-                    Mockito.mock(RichOngoingNotificationContentExtractor::class.java, STUB_ONLY),
-                    Mockito.mock(RichOngoingNotificationViewInflater::class.java, STUB_ONLY),
                     Mockito.mock(Executor::class.java, STUB_ONLY),
                     smartReplyStateInflater,
                     notifLayoutInflaterFactoryProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepositoryKosmos.kt
deleted file mode 100644
index 84ef4b5..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepositoryKosmos.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.data.repository
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
-import kotlinx.coroutines.flow.MutableStateFlow
-
-val Kosmos.fakeNotificationRowRepository by Fixture { FakeNotificationRowRepository() }
-
-class FakeNotificationRowRepository : NotificationRowRepository {
-    override val richOngoingContentModel = MutableStateFlow<RichOngoingContentModel?>(null)
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
deleted file mode 100644
index 3a7d7ba..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.domain.interactor
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
-
-fun Kosmos.getNotificationRowInteractor(repository: NotificationRowRepository) =
-    NotificationRowInteractor(repository = repository)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt
deleted file mode 100644
index 7e51135..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.viewmodel
-
-import com.android.systemui.dump.dumpManager
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
-import com.android.systemui.statusbar.notification.row.domain.interactor.getNotificationRowInteractor
-
-fun Kosmos.getEnRouteViewModel(repository: NotificationRowRepository) =
-    EnRouteViewModel(
-        dumpManager = dumpManager,
-        rowInteractor = getNotificationRowInteractor(repository),
-    )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelKosmos.kt
deleted file mode 100644
index 00f45b2..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelKosmos.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.ui.viewmodel
-
-import com.android.systemui.dump.dumpManager
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
-import com.android.systemui.statusbar.notification.row.domain.interactor.getNotificationRowInteractor
-
-fun Kosmos.getTimerViewModel(repository: NotificationRowRepository) =
-    TimerViewModel(
-        dumpManager = dumpManager,
-        rowInteractor = getNotificationRowInteractor(repository),
-    )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt
new file mode 100644
index 0000000..db9c48d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.interactor
+
+import android.os.Handler
+import android.os.looper
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.plugins.volumeDialogController
+
+val Kosmos.volumeDialogCallbacksInteractor: VolumeDialogCallbacksInteractor by
+    Kosmos.Fixture {
+        VolumeDialogCallbacksInteractor(
+            volumeDialogController = volumeDialogController,
+            coroutineScope = applicationCoroutineScope,
+            bgHandler = Handler(looper),
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
index 15ed1b3..e73539e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.data.repository
+package com.android.systemui.volume.dialog.domain.interactor
 
-import android.view.windowManagerService
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
 
-val Kosmos.systemGestureExclusionRepository by Fixture {
-    SystemGestureExclusionRepository(windowManager = windowManagerService)
-}
+val Kosmos.volumeDialogVisibilityInteractor by
+    Kosmos.Fixture {
+        VolumeDialogVisibilityInteractor(applicationCoroutineScope, volumeDialogCallbacksInteractor)
+    }
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 994bdb5..6489905 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -206,7 +206,7 @@
 
     // Inform that DND settings have changed on OS upgrade
     // Package: android
-    NOTE_ZEN_UPGRADE = 48;
+    NOTE_ZEN_UPGRADE = 48  [deprecated = true];
 
     // Notification to suggest automatic battery saver.
     // Package: android
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index 86246e2..72f62c5 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -5,7 +5,8 @@
     { "name": "hoststubgen-test-tiny-test" },
     { "name": "hoststubgen-invoke-test" },
     { "name": "RavenwoodMockitoTest_device" },
-    { "name": "RavenwoodBivalentTest_device" },
+    // TODO(b/371215487): Re-enable when the test is fixed.
+    // { "name": "RavenwoodBivalentTest_device" },
 
     { "name": "RavenwoodBivalentInstTest_nonself_inst" },
     { "name": "RavenwoodBivalentInstTest_self_inst_device" },
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
index ef795c6..520f050 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -37,8 +37,6 @@
     private RavenwoodCommonUtils() {
     }
 
-    private static final Object sLock = new Object();
-
     /**
      * If set to "1", we enable the verbose logging.
      *
@@ -68,9 +66,6 @@
 
     public static final String RAVENWOOD_VERSION_JAVA_SYSPROP = "android.ravenwood.version";
 
-    // @GuardedBy("sLock")
-    private static boolean sIntegrityChecked = false;
-
     /**
      * @return if we're running on Ravenwood.
      */
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
index 4e7dc5d..ad86135 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
@@ -32,25 +32,20 @@
 
     public static NativeAllocationRegistry createNonmalloced(
             ClassLoader classLoader, long freeFunction, long size) {
-        return new NativeAllocationRegistry(classLoader, freeFunction, size, false);
+        return new NativeAllocationRegistry(classLoader, freeFunction, size);
     }
 
     public static NativeAllocationRegistry createMalloced(
             ClassLoader classLoader, long freeFunction, long size) {
-        return new NativeAllocationRegistry(classLoader, freeFunction, size, true);
+        return new NativeAllocationRegistry(classLoader, freeFunction, size);
     }
 
     public static NativeAllocationRegistry createMalloced(
             ClassLoader classLoader, long freeFunction) {
-        return new NativeAllocationRegistry(classLoader, freeFunction, 0, true);
+        return new NativeAllocationRegistry(classLoader, freeFunction, 0);
     }
 
     public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) {
-        this(classLoader, freeFunction, size, size == 0);
-    }
-
-    private NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size,
-            boolean mallocAllocation) {
         if (size < 0) {
             throw new IllegalArgumentException("Invalid native allocation size: " + size);
         }
diff --git a/ravenwood/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp
similarity index 63%
rename from ravenwood/bivalenttest/Android.bp
rename to ravenwood/tests/bivalenttest/Android.bp
index e897735..ac499b9 100644
--- a/ravenwood/bivalenttest/Android.bp
+++ b/ravenwood/tests/bivalenttest/Android.bp
@@ -54,32 +54,34 @@
     auto_gen_config: true,
 }
 
-android_test {
-    name: "RavenwoodBivalentTest_device",
+// TODO(b/371215487): migrate bivalenttest.ravenizer tests to another architecture
 
-    srcs: [
-        "test/**/*.java",
-    ],
-    static_libs: [
-        "junit",
-        "truth",
-
-        "androidx.annotation_annotation",
-        "androidx.test.ext.junit",
-        "androidx.test.rules",
-
-        "junit-params",
-        "platform-parametric-runner-lib",
-
-        "ravenwood-junit",
-    ],
-    jni_libs: [
-        "libravenwoodbivalenttest_jni",
-    ],
-    test_suites: [
-        "device-tests",
-    ],
-    optimize: {
-        enabled: false,
-    },
-}
+// android_test {
+//     name: "RavenwoodBivalentTest_device",
+//
+//     srcs: [
+//         "test/**/*.java",
+//     ],
+//     static_libs: [
+//         "junit",
+//         "truth",
+//
+//         "androidx.annotation_annotation",
+//         "androidx.test.ext.junit",
+//         "androidx.test.rules",
+//
+//         "junit-params",
+//         "platform-parametric-runner-lib",
+//
+//         "ravenwood-junit",
+//     ],
+//     jni_libs: [
+//         "libravenwoodbivalenttest_jni",
+//     ],
+//     test_suites: [
+//         "device-tests",
+//     ],
+//     optimize: {
+//         enabled: false,
+//     },
+// }
diff --git a/ravenwood/bivalenttest/AndroidManifest.xml b/ravenwood/tests/bivalenttest/AndroidManifest.xml
similarity index 100%
rename from ravenwood/bivalenttest/AndroidManifest.xml
rename to ravenwood/tests/bivalenttest/AndroidManifest.xml
diff --git a/ravenwood/bivalenttest/AndroidTest.xml b/ravenwood/tests/bivalenttest/AndroidTest.xml
similarity index 100%
rename from ravenwood/bivalenttest/AndroidTest.xml
rename to ravenwood/tests/bivalenttest/AndroidTest.xml
diff --git a/ravenwood/bivalenttest/README.md b/ravenwood/tests/bivalenttest/README.md
similarity index 100%
rename from ravenwood/bivalenttest/README.md
rename to ravenwood/tests/bivalenttest/README.md
diff --git a/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp b/ravenwood/tests/bivalenttest/jni/ravenwood_core_test_jni.cpp
similarity index 100%
rename from ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
rename to ravenwood/tests/bivalenttest/jni/ravenwood_core_test_jni.cpp
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java
similarity index 100%
rename from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java
rename to ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java
diff --git a/ravenwood/tests/coretest/Android.bp b/ravenwood/tests/coretest/Android.bp
index d94475c..85f1baf 100644
--- a/ravenwood/tests/coretest/Android.bp
+++ b/ravenwood/tests/coretest/Android.bp
@@ -17,9 +17,15 @@
         "junit-params",
         "platform-parametric-runner-lib",
         "truth",
+
+        // This library should be removed by Ravenizer
+        "mockito-target-minus-junit4",
     ],
     srcs: [
         "test/**/*.java",
     ],
+    ravenizer: {
+        strip_mockito: true,
+    },
     auto_gen_config: true,
 }
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodMockitoTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodMockitoTest.java
new file mode 100644
index 0000000..dd6d259
--- /dev/null
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodMockitoTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.coretest;
+
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Test;
+
+public class RavenwoodMockitoTest {
+
+    @Test
+    public void checkMockitoClasses() {
+        // DexMaker should not exist
+        assertThrows(
+                ClassNotFoundException.class,
+                () -> Class.forName("com.android.dx.DexMaker"));
+        // Mockito 2 should not exist
+        assertThrows(
+                ClassNotFoundException.class,
+                () -> Class.forName("org.mockito.Matchers"));
+    }
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
index f7f9a85..49f0b59 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
@@ -85,18 +85,17 @@
 /**
  * Main class.
  */
-class Ravenizer(val options: RavenizerOptions) {
-    fun run() {
+class Ravenizer {
+    fun run(options: RavenizerOptions) {
         val stats = RavenizerStats()
 
-        val fatalValidation = options.fatalValidation.get
-
         stats.totalTime = log.nTime {
             process(
                 options.inJar.get,
                 options.outJar.get,
                 options.enableValidation.get,
-                fatalValidation,
+                options.fatalValidation.get,
+                options.stripMockito.get,
                 stats,
             )
         }
@@ -108,6 +107,7 @@
         outJar: String,
         enableValidation: Boolean,
         fatalValidation: Boolean,
+        stripMockito: Boolean,
         stats: RavenizerStats,
     ) {
         var allClasses = ClassNodes.loadClassStructures(inJar) {
@@ -126,6 +126,9 @@
                 }
             }
         }
+        if (includeUnsupportedMockito(allClasses)) {
+            log.w("Unsupported Mockito detected in $inJar}!")
+        }
 
         stats.totalProcessTime = log.vTime("$executableName processing $inJar") {
             ZipFile(inJar).use { inZip ->
@@ -145,6 +148,11 @@
                             )
                         }
 
+                        if (stripMockito && entry.name.isMockitoFile()) {
+                            // Skip this entry
+                            continue
+                        }
+
                         val className = zipEntryNameToClassName(entry.name)
 
                         if (className != null) {
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
index ff41818..aee4530 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
@@ -36,6 +36,6 @@
         log.v("Options: $options")
 
         // Run.
-        Ravenizer(options).run()
+        Ravenizer().run(options)
     }
 }
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
index 10fe0a3..32dcbe5 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
@@ -47,6 +47,9 @@
 
     /** Whether the validation failure is fatal or not. */
     var fatalValidation: SetOnce<Boolean> = SetOnce(false),
+
+    /** Whether to remove mockito and dexmaker classes. */
+    var stripMockito: SetOnce<Boolean> = SetOnce(false),
 ) {
     companion object {
 
@@ -85,6 +88,9 @@
                         "--fatal-validation" -> ret.fatalValidation.set(true)
                         "--no-fatal-validation" -> ret.fatalValidation.set(false)
 
+                        "--strip-mockito" -> ret.stripMockito.set(true)
+                        "--no-strip-mockito" -> ret.stripMockito.set(false)
+
                         else -> throw ArgumentsException("Unknown option: $arg")
                     }
                 } catch (e: SetOnce.SetMoreThanOnceException) {
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
index 1aa70c08..37a7975 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
@@ -100,3 +100,19 @@
         // TODO -- anything else?
     )
 }
+
+/**
+ * Files that should be removed when "--strip-mockito" is set.
+ */
+fun String.isMockitoFile(): Boolean {
+    return this.startsWithAny(
+        "org/mockito/", // Mockito
+        "com/android/dx/", // DexMaker
+        "mockito-extensions/", // DexMaker overrides
+    )
+}
+
+fun includeUnsupportedMockito(classes: ClassNodes): Boolean {
+    return classes.findClass("com/android/dx/DexMaker") != null
+            || classes.findClass("org/mockito/Matchers") != null
+}
diff --git a/services/appfunctions/TEST_MAPPING b/services/appfunctions/TEST_MAPPING
index 91cfa06..851d754 100644
--- a/services/appfunctions/TEST_MAPPING
+++ b/services/appfunctions/TEST_MAPPING
@@ -2,11 +2,6 @@
   "presubmit": [
     {
       "name": "FrameworksAppFunctionsTests"
-    }
-  ],
-  "postsubmit": [
-    {
-      "name": "FrameworksAppFunctionsTests"
     },
     {
       "name": "CtsAppFunctionTestCases"
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index ab9cc20..d31ced3 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -16,8 +16,6 @@
 
 package com.android.server.appfunctions;
 
-import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DISABLED;
-import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED;
 import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB;
 import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE;
 
@@ -363,26 +361,14 @@
                                     callingPackage,
                                     functionIdentifier,
                                     runtimeMetadataSearchSession));
-            AppFunctionRuntimeMetadata.Builder newMetadata =
-                    new AppFunctionRuntimeMetadata.Builder(existingMetadata);
-            switch (enabledState) {
-                case AppFunctionManager.APP_FUNCTION_STATE_DEFAULT -> {
-                    newMetadata.setEnabled(null);
-                }
-                case APP_FUNCTION_STATE_ENABLED -> {
-                    newMetadata.setEnabled(true);
-                }
-                case APP_FUNCTION_STATE_DISABLED -> {
-                    newMetadata.setEnabled(false);
-                }
-                default ->
-                        throw new IllegalArgumentException("Value of EnabledState is unsupported.");
-            }
+            AppFunctionRuntimeMetadata newMetadata =
+                    new AppFunctionRuntimeMetadata.Builder(existingMetadata)
+                            .setEnabled(enabledState).build();
             AppSearchBatchResult<String, Void> putDocumentBatchResult =
                     runtimeMetadataSearchSession
                             .put(
                                     new PutDocumentsRequest.Builder()
-                                            .addGenericDocuments(newMetadata.build())
+                                            .addGenericDocuments(newMetadata)
                                             .build())
                             .get();
             if (!putDocumentBatchResult.isSuccess()) {
@@ -438,62 +424,17 @@
                         targetUser,
                         mServiceConfig.getExecuteAppFunctionCancellationTimeoutMillis(),
                         cancellationSignal,
-                        new RunServiceCallCallback<IAppFunctionService>() {
-                            @Override
-                            public void onServiceConnected(
-                                    @NonNull IAppFunctionService service,
-                                    @NonNull
-                                            ServiceUsageCompleteListener
-                                                    serviceUsageCompleteListener) {
-                                try {
-                                    service.executeAppFunction(
-                                            requestInternal.getClientRequest(),
-                                            cancellationCallback,
-                                            new IExecuteAppFunctionCallback.Stub() {
-                                                @Override
-                                                public void onResult(
-                                                        ExecuteAppFunctionResponse response) {
-                                                    safeExecuteAppFunctionCallback.onResult(
-                                                            response);
-                                                    serviceUsageCompleteListener.onCompleted();
-                                                }
-                                            });
-                                } catch (Exception e) {
-                                    safeExecuteAppFunctionCallback.onResult(
-                                            ExecuteAppFunctionResponse.newFailure(
-                                                    ExecuteAppFunctionResponse
-                                                            .RESULT_APP_UNKNOWN_ERROR,
-                                                    e.getMessage(),
-                                                    /* extras= */ null));
-                                    serviceUsageCompleteListener.onCompleted();
-                                }
-                            }
-
-                            @Override
-                            public void onFailedToConnect() {
-                                Slog.e(TAG, "Failed to connect to service");
-                                safeExecuteAppFunctionCallback.onResult(
-                                        ExecuteAppFunctionResponse.newFailure(
-                                                ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
-                                                "Failed to connect to AppFunctionService",
-                                                /* extras= */ null));
-                            }
-
-                            @Override
-                            public void onCancelled() {
-                                // Do not forward the result back to the caller once it has been
-                                // canceled. The caller does not need a notification and should
-                                // proceed after initiating a cancellation.
-                                safeExecuteAppFunctionCallback.disable();
-                            }
-                        },
+                        RunAppFunctionServiceCallback.create(
+                                requestInternal,
+                                cancellationCallback,
+                                safeExecuteAppFunctionCallback),
                         callerBinder);
 
         if (!bindServiceResult) {
             Slog.e(TAG, "Failed to bind to the AppFunctionService");
             safeExecuteAppFunctionCallback.onResult(
                     ExecuteAppFunctionResponse.newFailure(
-                            ExecuteAppFunctionResponse.RESULT_TIMED_OUT,
+                            ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
                             "Failed to bind the AppFunctionService.",
                             /* extras= */ null));
         }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
new file mode 100644
index 0000000..7820390
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
@@ -0,0 +1,103 @@
+/*
+ * 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.appfunctions;
+
+import android.annotation.NonNull;
+import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.app.appfunctions.IAppFunctionService;
+import android.app.appfunctions.ICancellationCallback;
+import android.app.appfunctions.IExecuteAppFunctionCallback;
+import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
+import android.util.Slog;
+
+import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback;
+import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener;
+
+
+/**
+ * A callback to forward a request to the {@link IAppFunctionService} and report back the result.
+ */
+public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAppFunctionService> {
+
+    private final ExecuteAppFunctionAidlRequest mRequestInternal;
+    private final SafeOneTimeExecuteAppFunctionCallback mSafeExecuteAppFunctionCallback;
+    private final ICancellationCallback mCancellationCallback;
+
+    private RunAppFunctionServiceCallback(
+            ExecuteAppFunctionAidlRequest requestInternal,
+            ICancellationCallback cancellationCallback,
+            SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
+        this.mRequestInternal = requestInternal;
+        this.mSafeExecuteAppFunctionCallback = safeExecuteAppFunctionCallback;
+        this.mCancellationCallback = cancellationCallback;
+    }
+
+    /**
+     * Creates a new instance of {@link RunAppFunctionServiceCallback}.
+     *
+     * @param requestInternal a request to send to the service.
+     * @param cancellationCallback a callback to forward cancellation signal to the service.
+     * @param safeExecuteAppFunctionCallback a callback to report back the result of the operation.
+     */
+    public static RunAppFunctionServiceCallback create(
+            ExecuteAppFunctionAidlRequest requestInternal,
+            ICancellationCallback cancellationCallback,
+            SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
+        return new RunAppFunctionServiceCallback(
+                requestInternal, cancellationCallback, safeExecuteAppFunctionCallback);
+    }
+
+    @Override
+    public void onServiceConnected(
+            @NonNull IAppFunctionService service,
+            @NonNull ServiceUsageCompleteListener serviceUsageCompleteListener) {
+        try {
+            service.executeAppFunction(
+                    mRequestInternal.getClientRequest(),
+                    mCancellationCallback,
+                    new IExecuteAppFunctionCallback.Stub() {
+                        @Override
+                        public void onResult(ExecuteAppFunctionResponse response) {
+                            mSafeExecuteAppFunctionCallback.onResult(response);
+                            serviceUsageCompleteListener.onCompleted();
+                        }
+                    });
+        } catch (Exception e) {
+            mSafeExecuteAppFunctionCallback.onResult(
+                    ExecuteAppFunctionResponse.newFailure(
+                            ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
+                            e.getMessage(),
+                            /* extras= */ null));
+            serviceUsageCompleteListener.onCompleted();
+        }
+    }
+
+    @Override
+    public void onFailedToConnect() {
+        Slog.e("AppFunctionManagerServiceImpl", "Failed to connect to service");
+        mSafeExecuteAppFunctionCallback.onResult(
+                ExecuteAppFunctionResponse.newFailure(
+                        ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
+                        "Failed to connect to AppFunctionService",
+                        /* extras= */ null));
+    }
+
+    @Override
+    public void onCancelled() {
+        mSafeExecuteAppFunctionCallback.disable();
+    }
+}
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 9060250..2acedd5 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -47,6 +47,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.EventLog;
 import android.util.IndentingPrintWriter;
 import android.util.LongArrayQueue;
 import android.util.Slog;
@@ -200,6 +201,13 @@
     // aborted.
     private static final String METADATA_FILE = "/metadata/watchdog/mitigation_count.txt";
 
+    /**
+     * EventLog tags used when logging into the event log. Note the values must be sync with
+     * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
+     * name translation.
+     */
+    private static final int LOG_TAG_RESCUE_NOTE = 2900;
+
     private static final Object sPackageWatchdogLock = new Object();
     @GuardedBy("sPackageWatchdogLock")
     private static PackageWatchdog sPackageWatchdog;
@@ -2024,7 +2032,7 @@
             } else {
                 int count = getCount() + 1;
                 setCount(count);
-                EventLogTags.writeRescueNote(Process.ROOT_UID, count, window);
+                EventLog.writeEvent(LOG_TAG_RESCUE_NOTE, Process.ROOT_UID, count, window);
                 if (Flags.recoverabilityDetection()) {
                     // After a reboot (e.g. by WARM_REBOOT or mainline rollback) we apply
                     // mitigations without waiting for DEFAULT_BOOT_LOOP_TRIGGER_COUNT.
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index ada1953..feb5775 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -42,6 +42,7 @@
 import android.sysprop.CrashRecoveryProperties;
 import android.text.TextUtils;
 import android.util.ArraySet;
+import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
 
@@ -154,6 +155,14 @@
     private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
             | ApplicationInfo.FLAG_SYSTEM;
 
+    /**
+     * EventLog tags used when logging into the event log. Note the values must be sync with
+     * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
+     * name translation.
+     */
+    private static final int LOG_TAG_RESCUE_SUCCESS = 2902;
+    private static final int LOG_TAG_RESCUE_FAILURE = 2903;
+
     /** Register the Rescue Party observer as a Package Watchdog health observer */
     public static void registerHealthObserver(Context context) {
         PackageWatchdog.getInstance(context).registerHealthObserver(
@@ -523,7 +532,7 @@
         Slog.w(TAG, "Attempting rescue level " + levelToString(level));
         try {
             executeRescueLevelInternal(context, level, failedPackage);
-            EventLogTags.writeRescueSuccess(level);
+            EventLog.writeEvent(LOG_TAG_RESCUE_SUCCESS, level);
             String successMsg = "Finished rescue level " + levelToString(level);
             if (!TextUtils.isEmpty(failedPackage)) {
                 successMsg += " for package " + failedPackage;
@@ -704,7 +713,7 @@
     private static void logRescueException(int level, @Nullable String failedPackageName,
             Throwable t) {
         final String msg = getCompleteMessage(t);
-        EventLogTags.writeRescueFailure(level, msg);
+        EventLog.writeEvent(LOG_TAG_RESCUE_FAILURE, level, msg);
         String failureMsg = "Failed rescue level " + levelToString(level);
         if (!TextUtils.isEmpty(failedPackageName)) {
             failureMsg += " for package " + failedPackageName;
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index a459ea9..ce66dc3 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -114,6 +114,9 @@
             "options": [
                 {
                     "include-filter": "android.os.storage.cts.StorageManagerTest"
+                },
+                {
+                    "include-filter": "android.os.storage.cts.StorageStatsManagerTest"
                 }
             ]
         }
@@ -173,15 +176,6 @@
                     "include-filter": "com.android.server.wm.BackgroundActivityStart*"
                 }
             ]
-        },
-        {
-            "name": "CtsOsTestCases",
-            "file_patterns": ["StorageManagerService\\.java"],
-            "options": [
-                {
-                    "include-filter": "android.os.storage.cts.StorageStatsManagerTest"
-                }
-            ]
         }
    ]
 }
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index f32031de..7daf158 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import static android.app.Flags.modesApi;
+import static android.app.Flags.enableCurrentModeTypeBinderCache;
 import static android.app.Flags.enableNightModeBinderCache;
 import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
 import static android.app.UiModeManager.DEFAULT_PRIORITY;
@@ -138,7 +139,7 @@
 
     private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
 
-    private final NightMode mNightMode = new NightMode(){
+    private final IntProperty mNightMode = new IntProperty(){
         private int mNightModeValue = UiModeManager.MODE_NIGHT_NO;
 
         @Override
@@ -192,7 +193,22 @@
     // flag set by resource, whether to night mode change for normal all or not.
     private boolean mNightModeLocked = false;
 
-    int mCurUiMode = 0;
+    private final IntProperty mCurUiMode = new IntProperty(){
+        private int mCurrentModeTypeValue = 0;
+
+        @Override
+        public int get() {
+            return mCurrentModeTypeValue;
+        }
+
+        @Override
+        public void set(int mode) {
+            mCurrentModeTypeValue = mode;
+            if (enableCurrentModeTypeBinderCache()) {
+                UiModeManager.invalidateCurrentModeTypeCache();
+            }
+        }
+    };
     private int mSetUiMode = 0;
     private boolean mHoldingConfiguration = false;
     private int mCurrentUser;
@@ -810,7 +826,7 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    return mCurUiMode & Configuration.UI_MODE_TYPE_MASK;
+                    return mCurUiMode.get() & Configuration.UI_MODE_TYPE_MASK;
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -1492,7 +1508,7 @@
             pw.print(" mCarModeEnableFlags="); pw.print(mCarModeEnableFlags);
             pw.print(" mEnableCarDockLaunch="); pw.println(mEnableCarDockLaunch);
 
-            pw.print("  mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode));
+            pw.print("  mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode.get()));
             pw.print(" mUiModeLocked="); pw.print(mUiModeLocked);
             pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode));
 
@@ -1745,7 +1761,7 @@
                     + "; uiMode=" + uiMode);
         }
 
-        mCurUiMode = uiMode;
+        mCurUiMode.set(uiMode);
         if (!mHoldingConfiguration && (!mWaitForDeviceInactive || mPowerSave)) {
             mConfiguration.uiMode = uiMode;
         }
@@ -1892,7 +1908,7 @@
         boolean keepScreenOn = mCharging &&
                 ((mCarModeEnabled && mCarModeKeepsScreenOn &&
                 (mCarModeEnableFlags & UiModeManager.ENABLE_CAR_MODE_ALLOW_SLEEP) == 0) ||
-                (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
+                (mCurUiMode.get() == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
         if (keepScreenOn != mWakeLock.isHeld()) {
             if (keepScreenOn) {
                 mWakeLock.acquire();
@@ -2319,11 +2335,12 @@
     }
 
     /**
-     * Interface to contain the value for system night mode. We make the night mode accessible
-     * through this class to ensure that the reassignment of this value invalidates the cache.
+     * Interface to contain the value for an integral property. We make the property
+     * accessible through this class to ensure that the reassignment of this value invalidates the
+     * cache.
      */
-    private interface NightMode {
+    private interface IntProperty {
         int get();
-        void set(int mode);
+        void set(int value);
     }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 6fd281e..f5a297b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -685,11 +685,6 @@
     // default. Controlled by Settings.Global.FORCE_ENABLE_PSS_PROFILING
     volatile boolean mForceEnablePssProfiling = false;
 
-    // Indicates whether to use ApplicationInfo to determine launched state instead of PM user state
-    // This is a temporary workaround until the trunk-stable flag is pushed to nextfood.
-    // TODO: b/365979852 - remove this workaround when redundant
-    volatile boolean mFlagUseAppInfoNotLaunched = false;
-
     /**
      * Indicates whether the foreground service background start restriction is enabled for
      * caller app that is targeting S+.
@@ -1022,9 +1017,6 @@
     private static final Uri FORCE_ENABLE_PSS_PROFILING_URI =
             Settings.Global.getUriFor(Settings.Global.FORCE_ENABLE_PSS_PROFILING);
 
-    private static final Uri ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI =
-            Settings.Global.getUriFor(Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED);
-
     /**
      * The threshold to decide if a given association should be dumped into metrics.
      */
@@ -1487,7 +1479,6 @@
                     false, this);
         }
         mResolver.registerContentObserver(FORCE_ENABLE_PSS_PROFILING_URI, false, this);
-        mResolver.registerContentObserver(ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI, false, this);
         updateConstants();
         if (mSystemServerAutomaticHeapDumpEnabled) {
             updateEnableAutomaticSystemServerHeapDumps();
@@ -1504,7 +1495,6 @@
         updateActivityStartsLoggingEnabled();
         updateForegroundServiceStartsLoggingEnabled();
         updateForceEnablePssProfiling();
-        updateEnableUseAppInfoNotLaunched();
         // Read DropboxRateLimiter params from flags.
         mService.initDropboxRateLimiter();
     }
@@ -1550,8 +1540,6 @@
             updateEnableAutomaticSystemServerHeapDumps();
         } else if (FORCE_ENABLE_PSS_PROFILING_URI.equals(uri)) {
             updateForceEnablePssProfiling();
-        } else if (ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI.equals(uri)) {
-            updateEnableUseAppInfoNotLaunched();
         }
     }
 
@@ -1671,11 +1659,6 @@
                 Settings.Global.FORCE_ENABLE_PSS_PROFILING, 0) == 1;
     }
 
-    private void updateEnableUseAppInfoNotLaunched() {
-        mFlagUseAppInfoNotLaunched = Settings.Global.getInt(mResolver,
-                Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED, 0) == 1;
-    }
-
     private void updateBackgroundActivityStarts() {
         mFlagBackgroundActivityStartsEnabled = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -2555,8 +2538,6 @@
         pw.print("  OOMADJ_UPDATE_QUICK="); pw.println(OOMADJ_UPDATE_QUICK);
         pw.print("  ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION=");
         pw.println(mEnableWaitForFinishAttachApplication);
-        pw.print("  FLAG_USE_APP_INFO_NOT_LAUNCHED=");
-        pw.println(mFlagUseAppInfoNotLaunched);
 
         pw.print("  "); pw.print(KEY_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION);
         pw.print("="); pw.println(FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 83bc75e..9219cc12 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18398,25 +18398,34 @@
                     "Cannot kill the dependents of a package without its name.");
         }
 
+        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, true, ALLOW_FULL_ONLY, "killPackageDependents", null);
+        final int[] userIds = mUserController.expandUserId(userId);
+
         final long callingId = Binder.clearCallingIdentity();
         IPackageManager pm = AppGlobals.getPackageManager();
-        int pkgUid = -1;
         try {
-            pkgUid = pm.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING, userId);
-        } catch (RemoteException e) {
-        }
-        if (userId != UserHandle.USER_ALL && pkgUid == -1) {
-            throw new IllegalArgumentException(
-                    "Cannot kill dependents of non-existing package " + packageName);
-        }
-        try {
-            synchronized(this) {
-                synchronized (mProcLock) {
-                    mProcessList.killPackageProcessesLSP(packageName, UserHandle.getAppId(pkgUid),
-                            userId, ProcessList.FOREGROUND_APP_ADJ,
-                            ApplicationExitInfo.REASON_DEPENDENCY_DIED,
-                            ApplicationExitInfo.SUBREASON_UNKNOWN,
-                            "dep: " + packageName);
+            for (int targetUserId : userIds) {
+                int pkgUid = -1;
+                try {
+                    pkgUid = pm.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING,
+                            targetUserId);
+                } catch (RemoteException e) {
+                }
+                if (userId != UserHandle.USER_ALL && pkgUid == -1) {
+                    throw new IllegalArgumentException(
+                            "Cannot kill dependents of non-existing package " + packageName);
+                }
+                synchronized (this) {
+                    synchronized (mProcLock) {
+                        mProcessList.killPackageProcessesLSP(packageName,
+                                UserHandle.getAppId(pkgUid),
+                                targetUserId,
+                                ProcessList.FOREGROUND_APP_ADJ,
+                                ApplicationExitInfo.REASON_DEPENDENCY_DIED,
+                                ApplicationExitInfo.SUBREASON_UNKNOWN,
+                                "dep: " + packageName);
+                    }
                 }
             }
         } finally {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 78a0a11..796de19 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -458,7 +458,13 @@
         }
 
         void setThreadPriority(int tid, int priority) {
-            Process.setThreadPriority(tid, priority);
+            if (Flags.resetOnForkEnabled()) {
+                Process.setThreadScheduler(tid,
+                    Process.SCHED_OTHER | Process.SCHED_RESET_ON_FORK,
+                    priority);
+            } else {
+                 Process.setThreadPriority(tid, priority);
+            }
         }
 
     }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index a93ae72..57922d5 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3401,8 +3401,7 @@
         // Check if we should mark the processrecord for first launch after force-stopping
         if (wasStopped) {
             boolean wasEverLaunched = false;
-            if (android.app.Flags.useAppInfoNotLaunched()
-                    || mService.mConstants.mFlagUseAppInfoNotLaunched) {
+            if (android.app.Flags.useAppInfoNotLaunched()) {
                 wasEverLaunched = !info.isNotLaunched();
             } else {
                 try {
@@ -3423,8 +3422,7 @@
                         : STOPPED_STATE_FIRST_LAUNCH;
                 r.getWindowProcessController().setStoppedState(stoppedState);
             } else {
-                if (android.app.Flags.useAppInfoNotLaunched()
-                        || mService.mConstants.mFlagUseAppInfoNotLaunched) {
+                if (android.app.Flags.useAppInfoNotLaunched()) {
                     // If it was launched before, then it must be a force-stop
                     r.setWasForceStopped(wasEverLaunched);
                 } else {
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 4f6da3b..7873d34 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -198,13 +198,10 @@
 
 flag {
     name: "logcat_longer_timeout"
-    namespace: "backstage_power"
+    namespace: "stability"
     description: "Wait longer during the logcat gathering operation"
     bug: "292533246"
     is_fixed_read_only: true
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
 }
 
 flag {
@@ -214,3 +211,10 @@
     description: "Defer submitting display events to frozen processes."
     bug: "326315985"
 }
+
+flag {
+    name: "reset_on_fork_enabled"
+    namespace: "system_performance"
+    description: "Set reset_on_fork flag."
+    bug: "370988407"
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index c2e62d0..596e375 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -72,6 +72,9 @@
 import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
 import static android.permission.flags.Flags.deviceAwareAppOpNewSchemaEnabled;
 
+import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION;
+import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_OPERATION;
+import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_PROXY_OPERATION;
 import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
 
 import android.Manifest;
@@ -160,6 +163,7 @@
 import com.android.internal.pm.pkg.component.ParsedAttribution;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -540,11 +544,11 @@
             this.uid = uid;
         }
 
-        @SuppressWarnings("GuardedBy")
         public void clear() {
             mAppOpsCheckingService.removeUid(uid);
             for (int i = 0; i < pkgOps.size(); i++) {
-                packageRemovedLocked(uid, pkgOps.keyAt(i));
+                String packageName = pkgOps.keyAt(i);
+                mAppOpsCheckingService.removePackage(packageName, UserHandle.getUserId(uid));
             }
         }
 
@@ -2829,12 +2833,26 @@
 
     @Override
     public int checkOperation(int code, int uid, String packageName) {
+        if (Flags.appopAccessTrackingLoggingEnabled()) {
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED,
+                    uid, code,
+                    APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
+                    false);
+        }
         return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null,
                 Context.DEVICE_ID_DEFAULT, false /*raw*/);
     }
 
     @Override
     public int checkOperationForDevice(int code, int uid, String packageName, int virtualDeviceId) {
+        if (Flags.appopAccessTrackingLoggingEnabled()) {
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED,
+                    uid, code,
+                    APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
+                    false);
+        }
         return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null,
                 virtualDeviceId, false /*raw*/);
     }
@@ -3015,6 +3033,14 @@
     public SyncNotedAppOp noteProxyOperationWithState(int code,
             AttributionSourceState attributionSourceState, boolean shouldCollectAsyncNotedOp,
             String message, boolean shouldCollectMessage, boolean skipProxyOperation) {
+        if (Flags.appopAccessTrackingLoggingEnabled()) {
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED,
+                    attributionSourceState.uid, code,
+                    APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_PROXY_OPERATION,
+                    attributionSourceState.attributionTag != null);
+        }
+
         AttributionSource attributionSource = new AttributionSource(attributionSourceState);
         return mCheckOpsDelegateDispatcher.noteProxyOperation(code, attributionSource,
                 shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation);
@@ -3096,6 +3122,14 @@
     public SyncNotedAppOp noteOperation(int code, int uid, String packageName,
             String attributionTag, boolean shouldCollectAsyncNotedOp, String message,
             boolean shouldCollectMessage) {
+        if (Flags.appopAccessTrackingLoggingEnabled()) {
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED,
+                    uid, code,
+                    APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_OPERATION,
+                    attributionTag != null);
+        }
+
         return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
                 attributionTag, Context.DEVICE_ID_DEFAULT, shouldCollectAsyncNotedOp, message,
                 shouldCollectMessage);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 87504154..0475b94 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -384,11 +384,12 @@
     /**
      * Indicates if a Bluetooth SCO activation request owner is controlling
      * the SCO audio state itself or not.
-     * @param uid the UI of the SOC request owner app
+     * @param uid the UID of the SOC request owner app
      * @return true if we should control SCO audio state, false otherwise
      */
     private boolean shouldStartScoForUid(int uid) {
-        return !(uid == Process.BLUETOOTH_UID || uid == Process.PHONE_UID);
+        return !(UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)
+                || UserHandle.isSameApp(uid, Process.PHONE_UID));
     }
 
     @GuardedBy("mDeviceStateLock")
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 5fd12c2..09de894 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -385,11 +385,6 @@
                     || !updatedDevice.getDeviceAddress().equals(ads.getDeviceAddress())) {
                 continue;
             }
-            if (mDeviceBroker.isSADevice(updatedDevice) == mDeviceBroker.isSADevice(ads)) {
-                ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
-                ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
-                ads.setSAEnabled(updatedDevice.isSAEnabled());
-            }
             ads.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
 
             mDeviceBroker.postUpdatedAdiDeviceState(ads, false /*initSA*/);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f9e8392..c37d471 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -10100,9 +10100,6 @@
 
                 case MSG_INIT_SPATIALIZER:
                     onInitSpatializer();
-                    // the device inventory can only be synchronized after the
-                    // spatializer has been initialized
-                    mDeviceBroker.postSynchronizeAdiDevicesInInventory(null);
                     mAudioEventWakeLock.release();
                     break;
 
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index af9c9ac..8d96ba9 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -377,6 +377,7 @@
  *              </point>
  *            </map>
  *          </luxToBrightnessMapping>
+ *          <idleStylusTimeoutMillis>10000</idleStylusTimeoutMillis>
  *      </autoBrightness>
  *
  *      <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease>
@@ -708,6 +709,10 @@
 
     private static final int KEEP_CURRENT_BRIGHTNESS = -1;
 
+    // The default value to 0 which will signify that the stylus usage immediately stopped
+    // after it was started. This will make the system behave as if the stylus was never used
+    private static final int DEFAULT_IDLE_STYLUS_TIMEOUT_MILLIS = 0;
+
     private final Context mContext;
 
     // The details of the ambient light sensor associated with this display.
@@ -754,6 +759,9 @@
     @Nullable
     private DisplayBrightnessMappingConfig mDisplayBrightnessMapping;
 
+    private int mIdleStylusTimeoutMillis =
+            DEFAULT_IDLE_STYLUS_TIMEOUT_MILLIS;
+
     private float mBacklightMinimum = Float.NaN;
     private float mBacklightMaximum = Float.NaN;
     private float mBrightnessDefault = Float.NaN;
@@ -1730,6 +1738,7 @@
                 + ", mDisplayBrightnessMapping= " + mDisplayBrightnessMapping
                 + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
                 + ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
+                + ", mIdleStylusTimeoutMillis= " + mIdleStylusTimeoutMillis
                 + "\n"
                 + "mDefaultLowBlockingZoneRefreshRate= " + mDefaultLowBlockingZoneRefreshRate
                 + ", mDefaultHighBlockingZoneRefreshRate= " + mDefaultHighBlockingZoneRefreshRate
@@ -2389,10 +2398,19 @@
         loadAutoBrightnessDarkeningLightDebounceIdle(autoBrightness);
         mDisplayBrightnessMapping = new DisplayBrightnessMappingConfig(mContext, mFlags,
                 autoBrightness, getBacklightToBrightnessSpline());
+        loadIdleStylusTimeoutMillis(autoBrightness);
         loadEnableAutoBrightness(autoBrightness);
     }
 
     /**
+     * Gets the timeout post the stylus usage after which the automatic brightness will be enabled
+     * again
+     */
+    public int getIdleStylusTimeoutMillis() {
+        return mIdleStylusTimeoutMillis;
+    }
+
+    /**
      * Loads the auto-brightness brightening light debounce. Internally, this takes care of loading
      * the value from the display config, and if not present, falls back to config.xml.
      */
@@ -2923,6 +2941,16 @@
         return levels;
     }
 
+    private void loadIdleStylusTimeoutMillis(AutoBrightness autoBrightness) {
+        if (autoBrightness == null) {
+            return;
+        }
+        BigInteger idleStylusTimeoutMillis = autoBrightness.getIdleStylusTimeoutMillis();
+        if (idleStylusTimeoutMillis != null) {
+            mIdleStylusTimeoutMillis = idleStylusTimeoutMillis.intValue();
+        }
+    }
+
     private void loadEnableAutoBrightness(AutoBrightness autobrightness) {
         // mDdcAutoBrightnessAvailable is initialised to true, so that we fallback to using the
         // config.xml values if the autobrightness tag is not defined in the ddc file.
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 99a7743..bb503aa 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -5646,6 +5646,21 @@
         public void onPresentation(int displayId, boolean isShown) {
             mExternalDisplayPolicy.onPresentation(displayId, isShown);
         }
+
+        @Override
+        public void stylusGestureStarted(long eventTime) {
+            if (mFlags.isBlockAutobrightnessChangesOnStylusUsage()) {
+                DisplayPowerController displayPowerController;
+                synchronized (mSyncRoot) {
+                    displayPowerController = mDisplayPowerControllers.get(
+                            Display.DEFAULT_DISPLAY);
+                }
+                // We assume that the stylus is being used on the default display. This should
+                // be changed to the displayId on which it is being used once we start getting this
+                // information from the input manager service
+                displayPowerController.stylusGestureStarted(eventTime);
+            }
+        }
     }
 
     class DesiredDisplayModeSpecsObserver
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 03fec011..8f07bb3 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -167,12 +167,11 @@
     private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
     private static final int MSG_SET_BRIGHTNESS_FROM_OFFLOAD = 17;
     private static final int MSG_OFFLOADING_SCREEN_ON_UNBLOCKED = 18;
-
-
+    private static final int MSG_SET_STYLUS_BEING_USED = 19;
+    private static final int MSG_SET_STYLUS_USE_ENDED = 20;
 
     private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
 
-
     // State machine constants for tracking initial brightness ramp skipping when enabled.
     private static final int RAMP_STATE_SKIP_NONE = 0;
     private static final int RAMP_STATE_SKIP_INITIAL = 1;
@@ -191,6 +190,10 @@
         0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80,
         90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200,
         1400, 1600, 1800, 2000, 2250, 2500, 2750, 3000};
+
+    private static final int STYLUS_USAGE_DEBOUNCE_TIME  = 1000;
+    private static final int NANO_SECONDS_TO_MILLI_SECONDS_RATIO  = 1_000_000;
+
     private static final int[] BRIGHTNESS_RANGE_INDEX = {
         FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_UNKNOWN,
         FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_0_1,
@@ -498,6 +501,11 @@
     @GuardedBy("mLock")
     private int mPendingOverrideDozeScreenStateLocked;
 
+    private long mLastStylusUsageEventTime = -1;
+
+    // The time of inactivity after which the stylus can be assumed to be no longer in use.
+    private long mIdleStylusTimeoutMillisConfig = 0;
+
     /**
      * Creates the display power controller.
      */
@@ -518,6 +526,7 @@
         mSensorManager = sensorManager;
         mHandler = new DisplayControllerHandler(handler.getLooper());
         mDisplayDeviceConfig = mDisplayDevice.getDisplayDeviceConfig();
+        mIdleStylusTimeoutMillisConfig = mDisplayDeviceConfig.getIdleStylusTimeoutMillis();
         mIsEnabled = logicalDisplay.isEnabledLocked();
         mIsInTransition = logicalDisplay.isInTransitionLocked();
         mIsDisplayInternal = displayDeviceInfo.type == Display.TYPE_INTERNAL;
@@ -893,6 +902,7 @@
                 mPhysicalDisplayName = displayName;
                 mDisplayStatsId = mUniqueDisplayId.hashCode();
                 mDisplayDeviceConfig = config;
+                mIdleStylusTimeoutMillisConfig = mDisplayDeviceConfig.getIdleStylusTimeoutMillis();
                 mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
                 loadFromDisplayDeviceConfig(token, info, hbmMetadata);
                 mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
@@ -2971,6 +2981,18 @@
         return mDisplayId == Display.DEFAULT_DISPLAY || mBootCompleted;
     }
 
+    public void stylusGestureStarted(long eventTimeNanoSeconds) {
+        long eventTimeMs = eventTimeNanoSeconds / NANO_SECONDS_TO_MILLI_SECONDS_RATIO;
+        if (mLastStylusUsageEventTime == -1
+                || eventTimeMs > mLastStylusUsageEventTime + STYLUS_USAGE_DEBOUNCE_TIME) {
+            synchronized (mLock) {
+                // Add a message to notify the stylus usage has started
+                mHandler.sendEmptyMessageAtTime(MSG_SET_STYLUS_BEING_USED, mClock.uptimeMillis());
+            }
+            mLastStylusUsageEventTime = eventTimeMs;
+        }
+    }
+
     private final class DisplayControllerHandler extends Handler {
         DisplayControllerHandler(Looper looper) {
             super(looper, null, true /*async*/);
@@ -3087,6 +3109,20 @@
                         updatePowerState();
                     }
                     break;
+                case MSG_SET_STYLUS_BEING_USED:
+                    // Remove any MSG_SET_STYLUS_USE_ENDED message from the handler queue and
+                    // post a delayed MSG_SET_STYLUS_USE_ENDED message to delay the stylus
+                    // usage ended event processing
+                    mHandler.removeMessages(MSG_SET_STYLUS_USE_ENDED);
+                    Message message = mHandler.obtainMessage(MSG_SET_STYLUS_USE_ENDED);
+                    mHandler.sendMessageAtTime(message,
+                            mClock.uptimeMillis() + mIdleStylusTimeoutMillisConfig);
+                    mDisplayBrightnessController.setStylusBeingUsed(true);
+                    break;
+                case MSG_SET_STYLUS_USE_ENDED:
+                    mDisplayBrightnessController.setStylusBeingUsed(false);
+                    updatePowerState();
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 72a91d5..71fdaf3 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -501,6 +501,13 @@
         return true;
     }
 
+    /**
+     * Notifies if the stylus is currently being used or not.
+     */
+    public void setStylusBeingUsed(boolean isEnabled) {
+        // Todo(b/369977976) - Disable the auto-brightness strategy
+    }
+
     @VisibleForTesting
     static class Injector {
         DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(Context context,
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index df66893..5284d1c 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -203,6 +203,10 @@
             Flags.FLAG_NORMAL_BRIGHTNESS_FOR_DOZE_PARAMETER,
             Flags::normalBrightnessForDozeParameter
     );
+    private final FlagState mBlockAutobrightnessChangesOnStylusUsage = new FlagState(
+            Flags.FLAG_BLOCK_AUTOBRIGHTNESS_CHANGES_ON_STYLUS_USAGE,
+            Flags::blockAutobrightnessChangesOnStylusUsage
+    );
 
     private final FlagState mEnableBatteryStatsForAllDisplays = new FlagState(
             Flags.FLAG_ENABLE_BATTERY_STATS_FOR_ALL_DISPLAYS,
@@ -436,6 +440,13 @@
     }
 
     /**
+     * @return {@code true} if autobrightness is to be blocked when stylus is being used
+     */
+    public boolean isBlockAutobrightnessChangesOnStylusUsage() {
+        return mBlockAutobrightnessChangesOnStylusUsage.isEnabled();
+    }
+
+    /**
      * dumps all flagstates
      * @param pw printWriter
      */
@@ -479,6 +490,7 @@
         pw.println(" " + mNormalBrightnessForDozeParameter);
         pw.println(" " + mIdleScreenConfigInSubscribingLightSensor);
         pw.println(" " + mEnableBatteryStatsForAllDisplays);
+        pw.println(" " + mBlockAutobrightnessChangesOnStylusUsage);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index e3ebe5b..252ed09 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -364,4 +364,12 @@
     description: "Flag to enable battery stats for all displays."
     bug: "366112793"
     is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+    name: "block_autobrightness_changes_on_stylus_usage"
+    namespace: "display_manager"
+    description: "Block the usage of ALS to control the display brightness when stylus is being used"
+    bug: "352411468"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f045576..8acf583 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2694,6 +2694,9 @@
     @SuppressWarnings("unused")
     private void notifyStylusGestureStarted(int deviceId, long eventTime) {
         mBatteryController.notifyStylusGestureStarted(deviceId, eventTime);
+        if (mDisplayManagerInternal != null) {
+            mDisplayManagerInternal.stylusGestureStarted(eventTime);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 3780fbd..bbdac56 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -99,6 +99,7 @@
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.ShellCallback;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -126,6 +127,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.LongSparseArray;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -253,6 +255,8 @@
     private static final String MIGRATED_KEYSTORE_NS = "migrated_keystore_namespace";
     private static final String MIGRATED_SP_CE_ONLY = "migrated_all_users_to_sp_and_bound_ce";
     private static final String MIGRATED_SP_FULL = "migrated_all_users_to_sp_and_bound_keys";
+    private static final String MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS =
+            "migrated_weaver_disabled_on_unsecured_users";
 
     // Duration that LockSettingsService will store the gatekeeper password for. This allows
     // multiple biometric enrollments without prompting the user to enter their password via
@@ -309,6 +313,10 @@
     @GuardedBy("mUserCreationAndRemovalLock")
     private boolean mThirdPartyAppsStarted;
 
+    // This list contains the (protectorId, userId) of any protectors that were by replaced by a
+    // migration and should be destroyed once rollback to the old build is no longer possible.
+    private ArrayList<Pair<Long, Integer>> mProtectorsToDestroyOnBootCompleted = new ArrayList<>();
+
     // Current password metrics for all secured users on the device. Updated when user unlocks the
     // device or changes password. Removed if user is stopped with its CE key evicted.
     @GuardedBy("this")
@@ -363,6 +371,10 @@
                 mLockSettingsService.migrateOldDataAfterSystemReady();
                 mLockSettingsService.deleteRepairModePersistentDataIfNeeded();
             } else if (phase == PHASE_BOOT_COMPLETED) {
+                // In the case of an upgrade, PHASE_BOOT_COMPLETED means that a rollback to the old
+                // build can no longer occur.  This is the time to destroy any migrated protectors.
+                mLockSettingsService.destroyMigratedProtectors();
+
                 mLockSettingsService.loadEscrowData();
             }
         }
@@ -1076,6 +1088,11 @@
         mStorage.deleteRepairModePersistentData();
     }
 
+    private boolean isWeaverDisabledOnUnsecuredUsers() {
+        return mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers);
+    }
+
     // This is called when Weaver is guaranteed to be available (if the device supports Weaver).
     // It does any synthetic password related work that was delayed from earlier in the boot.
     private void onThirdPartyAppsStarted() {
@@ -1114,13 +1131,20 @@
             //
             // - Upgrading from Android 14, where unsecured users didn't have Keystore super keys.
             //
+            // - Upgrading from a build with config_disableWeaverOnUnsecuredUsers=false to one with
+            //   config_disableWeaverOnUnsecuredUsers=true.  (We don't bother to proactively add
+            //   Weaver for the reverse update to false, as it's too late to help in that case.)
+            //
             // The end result is that all users, regardless of whether they are secured or not, have
-            // a synthetic password with all keys initialized and protected by it.
+            // a synthetic password with all keys initialized and protected by it, and honoring
+            // config_disableWeaverOnUnsecuredUsers=true when applicable.
             //
             // Note: if this migration gets interrupted (e.g. by the device powering off), there
             // shouldn't be a problem since this will run again on the next boot, and
             // setCeStorageProtection() and initKeystoreSuperKeys(..., true) are idempotent.
-            if (!getBoolean(MIGRATED_SP_FULL, false, 0)) {
+            if (!getBoolean(MIGRATED_SP_FULL, false, 0)
+                    || (isWeaverDisabledOnUnsecuredUsers()
+                        && !getBoolean(MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS, false, 0))) {
                 for (UserInfo user : mUserManager.getAliveUsers()) {
                     removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber);
                     synchronized (mSpManager) {
@@ -1128,6 +1152,9 @@
                     }
                 }
                 setBoolean(MIGRATED_SP_FULL, true, 0);
+                if (isWeaverDisabledOnUnsecuredUsers()) {
+                    setBoolean(MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS, true, 0);
+                }
             }
 
             mThirdPartyAppsStarted = true;
@@ -1151,13 +1178,61 @@
                 getGateKeeperService(), protectorId, LockscreenCredential.createNone(), userId,
                 null);
         SyntheticPassword sp = result.syntheticPassword;
-        if (sp == null) {
+        if (isWeaverDisabledOnUnsecuredUsers()) {
+            Slog.i(TAG, "config_disableWeaverOnUnsecuredUsers=true");
+
+            // If config_disableWeaverOnUnsecuredUsers=true, then the Weaver HAL may be buggy and
+            // need multiple retries before it works here to unwrap the SP, if the SP was already
+            // protected by Weaver.  Note that the problematic HAL can also deadlock if called with
+            // the ActivityManagerService lock held, but that should not be a problem here since
+            // that lock isn't held here, unlike unlockUserKeyIfUnsecured() where it is.
+            for (int i = 0; i < 12 && sp == null; i++) {
+                Slog.e(TAG, "Failed to unwrap synthetic password. Waiting 5 seconds to retry.");
+                SystemClock.sleep(5000);
+                result = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
+                            LockscreenCredential.createNone(), userId, null);
+                sp = result.syntheticPassword;
+            }
+            if (sp == null) {
+                throw new IllegalStateException(
+                        "Failed to unwrap synthetic password for unsecured user");
+            }
+            // If the SP is protected by Weaver, then remove the Weaver protection in order to make
+            // config_disableWeaverOnUnsecuredUsers=true take effect.
+            if (result.usedWeaver) {
+                Slog.i(TAG, "Removing Weaver protection from the synthetic password");
+                // Create a new protector, which will not use Weaver.
+                long newProtectorId = mSpManager.createLskfBasedProtector(
+                        getGateKeeperService(), LockscreenCredential.createNone(), sp, userId);
+
+                // Out of paranoia, make sure the new protector really works.
+                result = mSpManager.unlockLskfBasedProtector(getGateKeeperService(),
+                            newProtectorId, LockscreenCredential.createNone(), userId, null);
+                sp = result.syntheticPassword;
+                if (sp == null) {
+                    throw new IllegalStateException("New SP protector does not work");
+                }
+
+                // Replace the protector.  Wait until PHASE_BOOT_COMPLETED to destroy the old
+                // protector, since the Weaver slot erasure and freeing cannot be rolled back.
+                setCurrentLskfBasedProtectorId(newProtectorId, userId);
+                mProtectorsToDestroyOnBootCompleted.add(new Pair(protectorId, userId));
+            } else {
+                Slog.i(TAG, "Synthetic password is already not protected by Weaver");
+            }
+        } else if (sp == null) {
             Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
             return;
         }
-        // While setCeStorageProtection() is idempotent, it does log some error messages when called
-        // again.  Skip it if we know it was already handled by an earlier upgrade to Android 14.
-        if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null) {
+
+        // Call setCeStorageProtection(), to re-encrypt the CE key with the SP if it's currently
+        // encrypted by an empty secret.  Skip this if it was definitely already done as part of the
+        // upgrade to Android 14, since while setCeStorageProtection() is idempotent it does log
+        // some error messages when called again.  Do not skip this if
+        // config_disableWeaverOnUnsecuredUsers=true, since in that case we'd like to recover from
+        // the case where an earlier upgrade to Android 14 incorrectly skipped this step.
+        if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null
+                || isWeaverDisabledOnUnsecuredUsers()) {
             Slogf.i(TAG, "Encrypting CE key of user %d with synthetic password", userId);
             setCeStorageProtection(userId, sp);
         }
@@ -1165,6 +1240,17 @@
         initKeystoreSuperKeys(userId, sp, /* allowExisting= */ true);
     }
 
+    private void destroyMigratedProtectors() {
+        if (!mProtectorsToDestroyOnBootCompleted.isEmpty()) {
+            synchronized (mSpManager) {
+                for (Pair<Long, Integer> pair : mProtectorsToDestroyOnBootCompleted) {
+                    mSpManager.destroyLskfBasedProtector(pair.first, pair.second);
+                }
+            }
+        }
+        mProtectorsToDestroyOnBootCompleted = null; // The list is no longer needed.
+    }
+
     /**
      * Returns the lowest password quality that still presents the same UI for entering it.
      *
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 3a429b0..47788f2 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -195,6 +195,8 @@
         // ERROR: password / token fails verification
         // RETRY: password / token verification is throttled at the moment.
         @Nullable public VerifyCredentialResponse gkResponse;
+        // For unlockLskfBasedProtector() this is set to true if the protector uses Weaver.
+        public boolean usedWeaver;
     }
 
     /**
@@ -532,6 +534,11 @@
                 Settings.Global.DEVICE_PROVISIONED, 0) != 0;
     }
 
+    private boolean isWeaverDisabledOnUnsecuredUsers() {
+        return mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers);
+    }
+
     @VisibleForTesting
     protected android.hardware.weaver.V1_0.IWeaver getWeaverHidlService() throws RemoteException {
         try {
@@ -1011,7 +1018,13 @@
 
         Slogf.i(TAG, "Creating LSKF-based protector %016x for user %d", protectorId, userId);
 
-        final IWeaver weaver = getWeaverService();
+        final IWeaver weaver;
+        if (credential.isNone() && isWeaverDisabledOnUnsecuredUsers()) {
+            weaver = null;
+            Slog.w(TAG, "Not using Weaver for unsecured user (disabled by config)");
+        } else {
+            weaver = getWeaverService();
+        }
         if (weaver != null) {
             // Weaver is available, so make the protector use it to verify the LSKF.  Do this even
             // if the LSKF is empty, as that gives us support for securely deleting the protector.
@@ -1404,6 +1417,7 @@
         int weaverSlot = loadWeaverSlot(protectorId, userId);
         if (weaverSlot != INVALID_WEAVER_SLOT) {
             // Protector uses Weaver to verify the LSKF
+            result.usedWeaver = true;
             final IWeaver weaver = getWeaverService();
             if (weaver == null) {
                 Slog.e(TAG, "Protector uses Weaver, but Weaver is unavailable");
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 47f579d..e7e519e 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -42,6 +42,8 @@
 import android.app.IProcessObserver;
 import android.app.KeyguardManager;
 import android.app.compat.CompatChanges;
+import android.app.role.RoleManager;
+import android.companion.AssociationRequest;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.ComponentName;
@@ -94,7 +96,7 @@
 
 /**
  * Manages MediaProjection sessions.
- *
+ * <p>
  * The {@link MediaProjectionManagerService} manages the creation and lifetime of MediaProjections,
  * as well as the capabilities they grant. Any service using MediaProjection tokens as permission
  * grants <b>must</b> validate the token before use by calling {@link
@@ -137,6 +139,7 @@
     private final PackageManager mPackageManager;
     private final WindowManagerInternal mWmInternal;
     private final KeyguardManager mKeyguardManager;
+    private final RoleManager mRoleManager;
 
     private final MediaRouter mMediaRouter;
     private final MediaRouterCallback mMediaRouterCallback;
@@ -173,6 +176,7 @@
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mKeyguardManager.addKeyguardLockedStateListener(
                 mContext.getMainExecutor(), this::onKeyguardLockedStateChanged);
+        mRoleManager = mContext.getSystemService(RoleManager.class);
         Watchdog.getInstance().addMonitor(this);
     }
 
@@ -182,6 +186,7 @@
      *   - be one of the bugreport allowlisted packages, or
      *   - hold the OP_PROJECT_MEDIA AppOp.
      */
+    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
     private boolean canCaptureKeyguard() {
         if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
             return true;
@@ -193,6 +198,9 @@
             if (mPackageManager.checkPermission(RECORD_SENSITIVE_CONTENT,
                     mProjectionGrant.packageName)
                     == PackageManager.PERMISSION_GRANTED) {
+                Slog.v(TAG,
+                        "Allowing keyguard capture for package with RECORD_SENSITIVE_CONTENT "
+                                + "permission");
                 return true;
             }
             if (AppOpsManager.MODE_ALLOWED == mAppOps.noteOpNoThrow(AppOpsManager.OP_PROJECT_MEDIA,
@@ -200,6 +208,13 @@
                     "recording lockscreen")) {
                 // Some tools use media projection by granting the OP_PROJECT_MEDIA app
                 // op via a shell command. Those tools can be granted keyguard capture
+                Slog.v(TAG,
+                        "Allowing keyguard capture for package with OP_PROJECT_MEDIA AppOp ");
+                return true;
+            }
+            if (isProjectionAppHoldingAppStreamingRoleLocked()) {
+                Slog.v(TAG,
+                        "Allowing keyguard capture for package holding app streaming role.");
                 return true;
             }
             return SystemConfig.getInstance().getBugreportWhitelistedPackages()
@@ -698,6 +713,20 @@
         }
     }
 
+    /**
+     * Application holding the app streaming role
+     * ({@value AssociationRequest#DEVICE_PROFILE_APP_STREAMING}) are allowed to record the
+     * lockscreen.
+     *
+     * @return true if the is held by the recording application.
+     */
+    @GuardedBy("mLock")
+    private boolean isProjectionAppHoldingAppStreamingRoleLocked() {
+        return mRoleManager.getRoleHoldersAsUser(AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+                        mContext.getUser())
+                .contains(mProjectionGrant.packageName);
+    }
+
     private void dump(final PrintWriter pw) {
         pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index e8d14cb..9b9be4c 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -832,8 +832,9 @@
                                       FullyQualifiedGroupKey newGroup) { }
 
     /**
-     * Called when a notification channel is updated, so that this helper can adjust
-     * the aggregate groups by moving children if their section has changed.
+     * Called when a notification channel is updated (channel attributes have changed),
+     * so that this helper can adjust the aggregate groups by moving children
+     * if their section has changed.
      * see {@link #onNotificationPostedWithDelay(NotificationRecord, List, Map)}
      * @param userId the userId of the channel
      * @param pkgName the channel's package
@@ -853,24 +854,48 @@
                 }
             }
 
-            // The list of notification operations required after the channel update
-            final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
+            regroupNotifications(userId, pkgName, notificationsToCheck);
+        }
+    }
 
-            // Check any already auto-grouped notifications that may need to be re-grouped
-            // after the channel update
-            notificationsToMove.addAll(
-                    getAutogroupedNotificationsMoveOps(userId, pkgName,
-                        notificationsToCheck));
+    /**
+     * Called when an individuial notification's channel is updated (moved to a new channel),
+     * so that this helper can adjust the aggregate groups by moving children
+     * if their section has changed.
+     * see {@link #onNotificationPostedWithDelay(NotificationRecord, List, Map)}
+     *
+     * @param record the notification which had its channel updated
+     */
+    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
+    public void onChannelUpdated(final NotificationRecord record) {
+        synchronized (mAggregatedNotifications) {
+            ArrayMap<String, NotificationRecord> notificationsToCheck = new ArrayMap<>();
+            notificationsToCheck.put(record.getKey(), record);
+            regroupNotifications(record.getUserId(), record.getSbn().getPackageName(),
+                    notificationsToCheck);
+        }
+    }
 
-            // Check any ungrouped notifications that may need to be auto-grouped
-            // after the channel update
-            notificationsToMove.addAll(
-                    getUngroupedNotificationsMoveOps(userId, pkgName, notificationsToCheck));
+    @GuardedBy("mAggregatedNotifications")
+    private void regroupNotifications(int userId, String pkgName,
+            ArrayMap<String, NotificationRecord> notificationsToCheck) {
+        // The list of notification operations required after the channel update
+        final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
 
-            // Batch move to new section
-            if (!notificationsToMove.isEmpty()) {
-                moveNotificationsToNewSection(userId, pkgName, notificationsToMove);
-            }
+        // Check any already auto-grouped notifications that may need to be re-grouped
+        // after the channel update
+        notificationsToMove.addAll(
+                getAutogroupedNotificationsMoveOps(userId, pkgName,
+                    notificationsToCheck));
+
+        // Check any ungrouped notifications that may need to be auto-grouped
+        // after the channel update
+        notificationsToMove.addAll(
+                getUngroupedNotificationsMoveOps(userId, pkgName, notificationsToCheck));
+
+        // Batch move to new section
+        if (!notificationsToMove.isEmpty()) {
+            moveNotificationsToNewSection(userId, pkgName, notificationsToMove);
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java b/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
index 97bbc23..2dd4f83 100644
--- a/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
@@ -15,6 +15,9 @@
 */
 package com.android.server.notification;
 
+import static android.service.notification.Adjustment.KEY_TYPE;
+import static android.service.notification.Flags.notificationForceGrouping;
+
 import android.content.Context;
 import android.util.Slog;
 
@@ -24,6 +27,7 @@
 public class NotificationAdjustmentExtractor implements NotificationSignalExtractor {
     private static final String TAG = "AdjustmentExtractor";
     private static final boolean DBG = false;
+    private GroupHelper mGroupHelper;
 
 
     public void initialize(Context ctx, NotificationUsageStats usageStats) {
@@ -35,8 +39,27 @@
             if (DBG) Slog.d(TAG, "skipping empty notification");
             return null;
         }
+
+        final boolean hasAdjustedClassification = record.hasAdjustment(KEY_TYPE);
         record.applyAdjustments();
 
+        if (notificationForceGrouping()
+                && android.service.notification.Flags.notificationClassification()) {
+            // Classification adjustments trigger regrouping
+            if (mGroupHelper != null && hasAdjustedClassification) {
+                return new RankingReconsideration(record.getKey(), 0) {
+                    @Override
+                    public void work() {
+                    }
+
+                    @Override
+                    public void applyChangesLocked(NotificationRecord record) {
+                        mGroupHelper.onChannelUpdated(record);
+                    }
+                };
+            }
+        }
+
         return null;
     }
 
@@ -49,4 +72,9 @@
     public void setZenHelper(ZenModeHelper helper) {
 
     }
+
+    @Override
+    public void setGroupHelper(GroupHelper groupHelper) {
+        mGroupHelper = groupHelper;
+    }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 06f419a..ea4a6db 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -17,6 +17,7 @@
 package com.android.server.notification;
 
 import static android.app.Flags.sortSectionByTime;
+import static android.app.Notification.CATEGORY_MESSAGE;
 import static android.app.Notification.FLAG_INSISTENT;
 import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
@@ -42,6 +43,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -1641,7 +1643,7 @@
             }
 
             // recent conversation
-            if (record.isConversation()
+            if ((record.isConversation() || isConversationMessage(record))
                     && record.getNotification().getWhen() > mLastAvalancheTriggerTimestamp) {
                 return true;
             }
@@ -1656,6 +1658,21 @@
 
             return false;
         }
+
+        // Relaxed signals for conversations messages
+        private boolean isConversationMessage(final NotificationRecord record) {
+            if (!CATEGORY_MESSAGE.equals(record.getSbn().getNotification().category)) {
+                return false;
+            }
+            if (record.getChannel().isDemoted()) {
+                return false;
+            }
+            final ShortcutInfo shortcut = record.getShortcutInfo();
+            if (shortcut == null) {
+                return false;
+            }
+            return true;
+        }
     }
 
     //======================  Observers  =============================
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 56e0a89..6c2d4f7 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -519,6 +519,7 @@
 
     private static final long DELAY_FORCE_REGROUP_TIME = 3000;
 
+
     private static final String ACTION_NOTIFICATION_TIMEOUT =
             NotificationManagerService.class.getSimpleName() + ".TIMEOUT";
     private static final int REQUEST_CODE_TIMEOUT = 1;
@@ -2583,7 +2584,7 @@
                 mShowReviewPermissionsNotification,
                 Clock.systemUTC());
         mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper,
-                mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat);
+                mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat, groupHelper);
         mSnoozeHelper = snoozeHelper;
         mGroupHelper = groupHelper;
         mHistoryManager = historyManager;
@@ -6871,22 +6872,9 @@
             }
             if (android.service.notification.Flags.notificationClassification()
                     && adjustments.containsKey(KEY_TYPE)) {
-                NotificationChannel newChannel = null;
-                int type = adjustments.getInt(KEY_TYPE);
-                if (TYPE_NEWS == type) {
-                    newChannel = mPreferencesHelper.getNotificationChannel(
-                            r.getSbn().getPackageName(), r.getUid(), NEWS_ID, false);
-                } else if (TYPE_PROMOTION == type) {
-                    newChannel = mPreferencesHelper.getNotificationChannel(
-                            r.getSbn().getPackageName(), r.getUid(), PROMOTIONS_ID, false);
-                } else if (TYPE_SOCIAL_MEDIA == type) {
-                    newChannel = mPreferencesHelper.getNotificationChannel(
-                            r.getSbn().getPackageName(), r.getUid(), SOCIAL_MEDIA_ID, false);
-                } else if (TYPE_CONTENT_RECOMMENDATION == type) {
-                    newChannel = mPreferencesHelper.getNotificationChannel(
-                            r.getSbn().getPackageName(), r.getUid(), RECS_ID, false);
-                }
-                if (newChannel == null) {
+                final NotificationChannel newChannel = getClassificationChannelLocked(r,
+                        adjustments);
+                if (newChannel == null || newChannel.getId().equals(r.getChannel().getId())) {
                     adjustments.remove(KEY_TYPE);
                 } else {
                     // swap app provided type with the real thing
@@ -6902,6 +6890,27 @@
         }
     }
 
+    @GuardedBy("mNotificationLock")
+    @Nullable
+    private NotificationChannel getClassificationChannelLocked(NotificationRecord r,
+            Bundle adjustments) {
+        int type = adjustments.getInt(KEY_TYPE);
+        if (TYPE_NEWS == type) {
+            return mPreferencesHelper.getNotificationChannel(
+                    r.getSbn().getPackageName(), r.getUid(), NEWS_ID, false);
+        } else if (TYPE_PROMOTION == type) {
+            return mPreferencesHelper.getNotificationChannel(
+                    r.getSbn().getPackageName(), r.getUid(), PROMOTIONS_ID, false);
+        } else if (TYPE_SOCIAL_MEDIA == type) {
+            return mPreferencesHelper.getNotificationChannel(
+                    r.getSbn().getPackageName(), r.getUid(), SOCIAL_MEDIA_ID, false);
+        } else if (TYPE_CONTENT_RECOMMENDATION == type) {
+            return mPreferencesHelper.getNotificationChannel(
+                    r.getSbn().getPackageName(), r.getUid(), RECS_ID, false);
+        }
+        return null;
+    }
+
     @SuppressWarnings("GuardedBy")
     @GuardedBy("mNotificationLock")
     void addAutogroupKeyLocked(String key, String groupName, boolean requestSort) {
@@ -12009,6 +12018,10 @@
         if (record != null && (record.getSbn().getNotification().flags
                 & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0
                 && !record.isCanceledAfterLifetimeExtension()) {
+            // Mark that the notification is being updated due to cancelation, so it won't
+            // be updated again if the app cancels multiple times.
+            record.setCanceledAfterLifetimeExtension(true);
+
             boolean isAppForeground = pkg != null && packageImportance == IMPORTANCE_FOREGROUND;
 
             // Save the original Record's post silently value, so we can restore it after we send
@@ -12024,9 +12037,6 @@
             PostNotificationTracker tracker = mPostNotificationTrackerFactory.newTracker(null);
             tracker.addCleanupRunnable(() -> {
                 synchronized (mNotificationLock) {
-                    // Mark that the notification has been updated due to cancelation, so it won't
-                    // be updated again if the app cancels multiple times.
-                    record.setCanceledAfterLifetimeExtension(true);
                     // Set the post silently status to the record's previous value.
                     record.setPostSilently(savedPostSilentlyState);
                     // Remove FLAG_ONLY_ALERT_ONCE if the notification did not previously have it.
diff --git a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
index f0358d1..be34bee 100644
--- a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
@@ -55,4 +55,9 @@
     void setZenHelper(ZenModeHelper helper);
 
     default void setCompatChangeLogger(IPlatformCompat platformCompat){};
+
+    /**
+     * @param groupHelper Helper for auto-grouping notifications
+     */
+    default void setGroupHelper(GroupHelper groupHelper){};
 }
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 03dd935..f06d6405 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -23,7 +23,6 @@
 import static android.text.TextUtils.formatSimple;
 
 import android.annotation.NonNull;
-import android.app.NotificationManager;
 import android.content.Context;
 import android.service.notification.RankingHelperProto;
 import android.util.ArrayMap;
@@ -61,7 +60,7 @@
             })
     public RankingHelper(Context context, RankingHandler rankingHandler, RankingConfig config,
             ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames,
-            IPlatformCompat platformCompat) {
+            IPlatformCompat platformCompat, GroupHelper groupHelper) {
         mContext = context;
         mRankingHandler = rankingHandler;
         if (sortSectionByTime()) {
@@ -80,6 +79,7 @@
                 extractor.initialize(mContext, usageStats);
                 extractor.setConfig(config);
                 extractor.setZenHelper(zenHelper);
+                extractor.setGroupHelper(groupHelper);
                 if (restrictAudioAttributesAlarm() || restrictAudioAttributesMedia()
                         || restrictAudioAttributesCall()) {
                     extractor.setCompatChangeLogger(platformCompat);
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index ff263d1..bdca555 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -37,7 +37,6 @@
 import android.util.ArraySet;
 import android.util.Slog;
 
-import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.util.NotificationMessagingUtil;
 
 import java.io.PrintWriter;
@@ -173,13 +172,6 @@
             maybeLogInterceptDecision(record, false, "criticalNotification");
             return false;
         }
-        // Make an exception to policy for the notification saying that policy has changed
-        if (NotificationManager.Policy.areAllVisualEffectsSuppressed(policy.suppressedVisualEffects)
-                && "android".equals(record.getSbn().getPackageName())
-                && SystemMessageProto.SystemMessage.NOTE_ZEN_UPGRADE == record.getSbn().getId()) {
-            maybeLogInterceptDecision(record, false, "systemDndChangedNotification");
-            return false;
-        }
         switch (zen) {
             case Global.ZEN_MODE_NO_INTERRUPTIONS:
                 // #notevenalarms
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 626c3dd..ea211a9 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -54,10 +54,8 @@
 import android.app.AppOpsManager;
 import android.app.AutomaticZenRule;
 import android.app.Flags;
-import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.NotificationManager.Policy;
-import android.app.PendingIntent;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
@@ -74,7 +72,6 @@
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
-import android.graphics.drawable.Icon;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
@@ -90,7 +87,6 @@
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.service.notification.Condition;
 import android.service.notification.ConditionProviderService;
@@ -117,8 +113,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
@@ -309,7 +303,6 @@
         mHandler.postMetricsTimer();
         cleanUpZenRules();
         mIsSystemServicesReady = true;
-        showZenUpgradeNotification(mZenMode);
     }
 
     /**
@@ -485,7 +478,7 @@
             populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true);
             rule = maybeRestoreRemovedRule(newConfig, pkg, rule, automaticZenRule, origin);
             newConfig.automaticRules.put(rule.id, rule);
-            maybeReplaceDefaultRule(newConfig, automaticZenRule);
+            maybeReplaceDefaultRule(newConfig, null, automaticZenRule);
 
             if (setConfigLocked(newConfig, origin, reason, rule.component, true, callingUid)) {
                 return rule.id;
@@ -535,13 +528,24 @@
         return ruleToRestore;
     }
 
-    private static void maybeReplaceDefaultRule(ZenModeConfig config, AutomaticZenRule addedRule) {
+    /**
+     * Possibly delete built-in rules if a more suitable rule is added or updated.
+     *
+     * <p>Today, this is done in one case: delete a disabled "Sleeping" rule if a Bedtime Mode is
+     * added (or an existing mode is turned into {@link AutomaticZenRule#TYPE_BEDTIME}, when
+     * upgrading). Because only the {@code config_systemWellbeing} package is allowed to use rules
+     * of this type, this will not trigger wantonly.
+     *
+     * @param oldRule If non-null, {@code rule} is updating {@code oldRule}. Otherwise,
+     *                {@code rule} is being added.
+     */
+    private static void maybeReplaceDefaultRule(ZenModeConfig config, @Nullable ZenRule oldRule,
+            AutomaticZenRule rule) {
         if (!Flags.modesApi()) {
             return;
         }
-        if (addedRule.getType() == AutomaticZenRule.TYPE_BEDTIME) {
-            // Delete a built-in disabled "Sleeping" rule when a BEDTIME rule is added; it may have
-            // smarter triggers and it will prevent confusion about which one to use.
+        if (rule.getType() == AutomaticZenRule.TYPE_BEDTIME
+                && (oldRule == null || oldRule.type != rule.getType())) {
             // Note: we must not verify canManageAutomaticZenRule here, since most likely they
             // won't have the same owner (sleeping - system; bedtime - DWB).
             ZenRule sleepingRule = config.automaticRules.get(
@@ -589,6 +593,10 @@
                 // condition) when no changes happen.
                 return true;
             }
+
+            if (Flags.modesUi()) {
+                maybeReplaceDefaultRule(newConfig, oldRule, automaticZenRule);
+            }
             return setConfigLocked(newConfig, origin, reason,
                     newRule.component, true, callingUid);
         }
@@ -1584,8 +1592,6 @@
             String reason, @Nullable String caller, int callingUid) {
         setManualZenMode(zenMode, conditionId, origin, reason, caller, true /*setRingerMode*/,
                 callingUid);
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 0);
     }
 
     private void setManualZenMode(int zenMode, Uri conditionId, @ConfigOrigin int origin,
@@ -1783,17 +1789,6 @@
                 SystemZenRules.maybeUpgradeRules(mContext, config);
             }
 
-            // Resolve user id for settings.
-            userId = userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId;
-            if (config.version < ZenModeConfig.XML_VERSION_ZEN_UPGRADE) {
-                Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                        Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1, userId);
-            } else {
-                // devices not restoring/upgrading already have updated zen settings
-                Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                        Settings.Secure.ZEN_SETTINGS_UPDATED, 1, userId);
-            }
-
             if (Flags.modesApi() && forRestore) {
                 // Note: forBackup doesn't write deletedRules, but just in case.
                 config.deletedRules.clear();
@@ -2062,7 +2057,6 @@
         Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zen);
         ZenLog.traceSetZenMode(Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, -1),
                 "updated setting");
-        showZenUpgradeNotification(zen);
     }
 
     private int getPreviousRingerModeSetting() {
@@ -2117,12 +2111,6 @@
             for (ZenRule automaticRule : mConfig.automaticRules.values()) {
                 if (automaticRule.isActive()) {
                     if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
-                        // automatic rule triggered dnd and user hasn't seen update dnd dialog
-                        if (Settings.Secure.getInt(mContext.getContentResolver(),
-                                Settings.Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, 1) == 0) {
-                            Settings.Secure.putInt(mContext.getContentResolver(),
-                                    Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 1);
-                        }
                         zen = automaticRule.zenMode;
                     }
                 }
@@ -2702,62 +2690,6 @@
         }
     }
 
-    private void showZenUpgradeNotification(int zen) {
-        final boolean isWatch = mContext.getPackageManager().hasSystemFeature(
-            PackageManager.FEATURE_WATCH);
-        final boolean showNotification = mIsSystemServicesReady
-                && zen != Global.ZEN_MODE_OFF
-                && !isWatch
-                && Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0) != 0
-                && Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.ZEN_SETTINGS_UPDATED, 0) != 1;
-
-        if (isWatch) {
-            Settings.Secure.putInt(mContext.getContentResolver(),
-                    Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
-        }
-
-        if (showNotification) {
-            mNotificationManager.notify(TAG, SystemMessage.NOTE_ZEN_UPGRADE,
-                    createZenUpgradeNotification());
-            Settings.Secure.putInt(mContext.getContentResolver(),
-                    Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
-        }
-    }
-
-    @VisibleForTesting
-    protected Notification createZenUpgradeNotification() {
-        final Bundle extras = new Bundle();
-        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
-                mContext.getResources().getString(R.string.global_action_settings));
-        int title = R.string.zen_upgrade_notification_title;
-        int content = R.string.zen_upgrade_notification_content;
-        int drawable = R.drawable.ic_zen_24dp;
-        if (NotificationManager.Policy.areAllVisualEffectsSuppressed(
-                getConsolidatedNotificationPolicy().suppressedVisualEffects)) {
-            title = R.string.zen_upgrade_notification_visd_title;
-            content = R.string.zen_upgrade_notification_visd_content;
-            drawable = R.drawable.ic_dnd_block_notifications;
-        }
-
-        Intent onboardingIntent = new Intent(Settings.ZEN_MODE_ONBOARDING);
-        onboardingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        return new Notification.Builder(mContext, SystemNotificationChannels.DO_NOT_DISTURB)
-                .setAutoCancel(true)
-                .setSmallIcon(R.drawable.ic_settings_24dp)
-                .setLargeIcon(Icon.createWithResource(mContext, drawable))
-                .setContentTitle(mContext.getResources().getString(title))
-                .setContentText(mContext.getResources().getString(content))
-                .setContentIntent(PendingIntent.getActivity(mContext, 0, onboardingIntent,
-                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
-                .setAutoCancel(true)
-                .setLocalOnly(true)
-                .addExtras(extras)
-                .setStyle(new Notification.BigTextStyle())
-                .build();
-    }
-
     private int drawableResNameToResId(String packageName, String resourceName) {
         if (TextUtils.isEmpty(resourceName)) {
             return 0;
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 89ced12..4665a72 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2208,10 +2208,10 @@
             return true;
         }
         boolean permissionGranted = requireFullPermission ? hasPermission(
-                Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
+                Manifest.permission.INTERACT_ACROSS_USERS_FULL)
                 : (hasPermission(
-                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
-                        || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS, callingUid));
+                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                        || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS));
         if (!permissionGranted) {
             if (Process.isIsolatedUid(callingUid) && isKnownIsolatedComputeApp(callingUid)) {
                 return checkIsolatedOwnerHasPermission(callingUid, requireFullPermission);
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 8657de2..5653da0 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -716,7 +716,7 @@
                     visiblePackages.add(info.getActivityInfo().packageName);
                 }
                 final List<ApplicationInfo> installedPackages =
-                        mPackageManagerInternal.getInstalledApplicationsCrossUser(
+                        mPackageManagerInternal.getInstalledApplications(
                                 /* flags= */ 0, user.getIdentifier(), callingUid);
                 for (ApplicationInfo applicationInfo : installedPackages) {
                     if (!visiblePackages.contains(applicationInfo.packageName)) {
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 4fae798..eb62b56 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -709,7 +709,7 @@
         SparseBooleanArray newDisplayInteractivities = new SparseBooleanArray();
         for (int i = 0; i < displaysByGroupId.size(); i++) {
             final int groupId = displaysByGroupId.keyAt(i);
-            for (int displayId : displaysByGroupId.get(i)) {
+            for (int displayId : displaysByGroupId.get(groupId)) {
                 // If we already know display interactivity, use that
                 if (mDisplayInteractivities.indexOfKey(displayId) > 0) {
                     newDisplayInteractivities.put(
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index dc48242..c969eff 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -21,6 +21,7 @@
 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
 import static com.android.server.power.hint.Flags.adpfSessionTag;
 import static com.android.server.power.hint.Flags.powerhintThreadCleanup;
+import static com.android.server.power.hint.Flags.resetOnForkEnabled;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1057,6 +1058,11 @@
                     Slogf.w(TAG, errMsg);
                     throw new SecurityException(errMsg);
                 }
+                if (resetOnForkEnabled()){
+                    for (int tid : tids) {
+                        Process.setThreadScheduler(tid, Process.SCHED_RESET_ON_FORK, 0);
+                    }
+                }
 
                 if (adpfSessionTag() && tag == SessionTag.APP) {
                     // If the category of the app is a game,
@@ -1447,6 +1453,11 @@
                             Slogf.w(TAG, errMsg);
                             throw new SecurityException(errMsg);
                         }
+                        if (resetOnForkEnabled()){
+                            for (int tid : tids) {
+                                Process.setThreadScheduler(tid, Process.SCHED_RESET_ON_FORK, 0);
+                            }
+                        }
                         if (powerhintThreadCleanup()) {
                             synchronized (mNonIsolatedTidsLock) {
                                 for (int i = nonIsolated.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/power/hint/flags.aconfig b/services/core/java/com/android/server/power/hint/flags.aconfig
index 55afa05..e56b68c 100644
--- a/services/core/java/com/android/server/power/hint/flags.aconfig
+++ b/services/core/java/com/android/server/power/hint/flags.aconfig
@@ -14,3 +14,10 @@
     description: "Feature flag for adding session tag to hint session atom"
     bug: "345011125"
 }
+
+flag {
+    name: "reset_on_fork_enabled"
+    namespace: "game"
+    description: "Set reset_on_fork flag."
+    bug: "370988407"
+}
diff --git a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
index b5a7fcb..e650c52 100644
--- a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
@@ -56,24 +56,26 @@
     }
 
     @Override
+    public long getCreateUptimeMillis() {
+        return stats.getCreateUptimeMillis();
+    }
+
+    @Override
     public CallerInfo getCallerInfo() {
         return callerInfo;
     }
 
     @Override
-    public VibrationSession.DebugInfo getDebugInfo() {
-        return new Vibration.DebugInfoImpl(getStatus(), stats, /* playedEffect= */ null,
-                /* originalEffect= */ null, mScale.scaleLevel, mScale.adaptiveHapticsScale,
-                callerInfo);
+    public IBinder getCallerToken() {
+        return mExternalVibration.getToken();
     }
 
     @Override
-    public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
-        return new VibrationStats.StatsInfo(
-                mExternalVibration.getUid(),
-                FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
-                mExternalVibration.getVibrationAttributes().getUsage(), getStatus(), stats,
-                completionUptimeMillis);
+    public VibrationSession.DebugInfo getDebugInfo() {
+        return new Vibration.DebugInfoImpl(getStatus(), callerInfo,
+                FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL, stats,
+                /* playedEffect= */ null, /* originalEffect= */ null, mScale.scaleLevel,
+                mScale.adaptiveHapticsScale);
     }
 
     @Override
@@ -86,6 +88,12 @@
     }
 
     @Override
+    public boolean wasEndRequested() {
+        // End request is immediate, so just check if vibration has already ended.
+        return hasEnded();
+    }
+
+    @Override
     public boolean linkToDeath(Runnable callback) {
         synchronized (mLock) {
             mBinderDeathCallback = callback;
@@ -104,10 +112,12 @@
 
     @Override
     public void binderDied() {
+        Runnable callback;
         synchronized (mLock) {
-            if (mBinderDeathCallback != null) {
-                mBinderDeathCallback.run();
-            }
+            callback = mBinderDeathCallback;
+        }
+        if (callback != null) {
+            callback.run();
         }
     }
 
@@ -131,6 +141,16 @@
         end(new EndInfo(status, endedBy));
     }
 
+    @Override
+    public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+        // ignored, external control does not expect callbacks from the vibrator
+    }
+
+    @Override
+    public void notifySyncedVibratorsCallback(long vibrationId) {
+        // ignored, external control does not expect callbacks from the vibrator manager
+    }
+
     boolean isHoldingSameVibration(ExternalVibration vib) {
         return mExternalVibration.equals(vib);
     }
diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java
index ce9c47b..fbcc856 100644
--- a/services/core/java/com/android/server/vibrator/HalVibration.java
+++ b/services/core/java/com/android/server/vibrator/HalVibration.java
@@ -19,15 +19,16 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.CombinedVibration;
-import android.os.IBinder;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationEffectSegment;
 import android.util.SparseArray;
 
-import com.android.internal.util.FrameworkStatsLog;
-
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
+import java.util.function.IntFunction;
 
 /**
  * Represents a vibration defined by a {@link CombinedVibration} that will be performed by
@@ -36,7 +37,6 @@
 final class HalVibration extends Vibration {
 
     public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
-    public final IBinder callerToken;
 
     /** A {@link CountDownLatch} to enable waiting for completion. */
     private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
@@ -56,10 +56,9 @@
     private int mScaleLevel;
     private float mAdaptiveScale;
 
-    HalVibration(@NonNull IBinder callerToken, @NonNull CombinedVibration effect,
-            @NonNull VibrationSession.CallerInfo callerInfo) {
+    HalVibration(@NonNull VibrationSession.CallerInfo callerInfo,
+            @NonNull CombinedVibration effect) {
         super(callerInfo);
-        this.callerToken = callerToken;
         mOriginalEffect = effect;
         mEffectToPlay = effect;
         mScaleLevel = VibrationScaler.SCALE_NONE;
@@ -87,11 +86,11 @@
     }
 
     /**
-     * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
-     * which might be necessary for replacement in realtime.
+     * Add a fallback {@link VibrationEffect} to be played for each predefined effect id, which
+     * might be necessary for replacement in realtime.
      */
-    public void addFallback(int effectId, VibrationEffect effect) {
-        mFallbacks.put(effectId, effect);
+    public void fillFallbacks(IntFunction<VibrationEffect> fallbackProvider) {
+        fillFallbacksForEffect(mEffectToPlay, fallbackProvider);
     }
 
     /**
@@ -131,11 +130,6 @@
         // No need to update fallback effects, they are already configured per device.
     }
 
-    @Override
-    public boolean isRepeating() {
-        return mOriginalEffect.getDuration() == Long.MAX_VALUE;
-    }
-
     /** Return the effect that should be played by this vibration. */
     public CombinedVibration getEffectToPlay() {
         return mEffectToPlay;
@@ -146,20 +140,9 @@
         // Clear the original effect if it's the same as the effect that was played, for simplicity
         CombinedVibration originalEffect =
                 Objects.equals(mOriginalEffect, mEffectToPlay) ? null : mOriginalEffect;
-        return new Vibration.DebugInfoImpl(getStatus(), stats, mEffectToPlay, originalEffect,
-                mScaleLevel, mAdaptiveScale, callerInfo);
-    }
-
-    @Override
-    public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
-        int vibrationType = mEffectToPlay.hasVendorEffects()
-                ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR
-                : isRepeating()
-                        ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
-                        : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
-        return new VibrationStats.StatsInfo(
-                callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), getStatus(),
-                stats, completionUptimeMillis);
+        return new Vibration.DebugInfoImpl(getStatus(), callerInfo,
+                VibrationStats.StatsInfo.findVibrationType(mEffectToPlay), stats, mEffectToPlay,
+                originalEffect, mScaleLevel, mAdaptiveScale);
     }
 
     /**
@@ -174,6 +157,42 @@
         return callerInfo.uid == vib.callerInfo.uid && callerInfo.attrs.isFlagSet(
                 VibrationAttributes.FLAG_PIPELINED_EFFECT)
                 && vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
-                && !isRepeating();
+                && (mOriginalEffect.getDuration() != Long.MAX_VALUE);
+    }
+
+    private void fillFallbacksForEffect(CombinedVibration effect,
+            IntFunction<VibrationEffect> fallbackProvider) {
+        if (effect instanceof CombinedVibration.Mono) {
+            fillFallbacksForEffect(((CombinedVibration.Mono) effect).getEffect(), fallbackProvider);
+        } else if (effect instanceof CombinedVibration.Stereo) {
+            SparseArray<VibrationEffect> effects =
+                    ((CombinedVibration.Stereo) effect).getEffects();
+            for (int i = 0; i < effects.size(); i++) {
+                fillFallbacksForEffect(effects.valueAt(i), fallbackProvider);
+            }
+        } else if (effect instanceof CombinedVibration.Sequential) {
+            List<CombinedVibration> effects =
+                    ((CombinedVibration.Sequential) effect).getEffects();
+            for (int i = 0; i < effects.size(); i++) {
+                fillFallbacksForEffect(effects.get(i), fallbackProvider);
+            }
+        }
+    }
+
+    private void fillFallbacksForEffect(VibrationEffect effect,
+            IntFunction<VibrationEffect> fallbackProvider) {
+        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 prebaked) && prebaked.shouldFallback()) {
+                VibrationEffect fallback = fallbackProvider.apply(prebaked.getEffectId());
+                if (fallback != null) {
+                    mFallbacks.put(prebaked.getEffectId(), fallback);
+                }
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
new file mode 100644
index 0000000..f80407d
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
@@ -0,0 +1,173 @@
+/*
+ * 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.annotation.Nullable;
+import android.os.CombinedVibration;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.NoSuchElementException;
+
+/**
+ * A vibration session holding a single {@link CombinedVibration} request, performed by a
+ * {@link VibrationStepConductor}.
+ */
+final class SingleVibrationSession implements VibrationSession, IBinder.DeathRecipient {
+    private static final String TAG = "SingleVibrationSession";
+
+    private final Object mLock = new Object();
+    private final IBinder mCallerToken;
+    private final HalVibration mVibration;
+
+    @GuardedBy("mLock")
+    private VibrationStepConductor mConductor;
+
+    @GuardedBy("mLock")
+    @Nullable
+    private Runnable mBinderDeathCallback;
+
+    SingleVibrationSession(@NonNull IBinder callerToken, @NonNull CallerInfo callerInfo,
+            @NonNull CombinedVibration vibration) {
+        mCallerToken = callerToken;
+        mVibration = new HalVibration(callerInfo, vibration);
+    }
+
+    public void setVibrationConductor(@Nullable VibrationStepConductor conductor) {
+        synchronized (mLock) {
+            mConductor = conductor;
+        }
+    }
+
+    public HalVibration getVibration() {
+        return mVibration;
+    }
+
+    @Override
+    public long getCreateUptimeMillis() {
+        return mVibration.stats.getCreateUptimeMillis();
+    }
+
+    @Override
+    public boolean isRepeating() {
+        return mVibration.getEffectToPlay().getDuration() == Long.MAX_VALUE;
+    }
+
+    @Override
+    public CallerInfo getCallerInfo() {
+        return mVibration.callerInfo;
+    }
+
+    @Override
+    public IBinder getCallerToken() {
+        return mCallerToken;
+    }
+
+    @Override
+    public DebugInfo getDebugInfo() {
+        return mVibration.getDebugInfo();
+    }
+
+    @Override
+    public boolean wasEndRequested() {
+        if (mVibration.hasEnded()) {
+            return true;
+        }
+        synchronized (mLock) {
+            return mConductor != null && mConductor.wasNotifiedToCancel();
+        }
+    }
+
+    @Override
+    public void binderDied() {
+        Slog.d(TAG, "Binder died, cancelling vibration...");
+        requestEnd(Status.CANCELLED_BINDER_DIED, /* endedBy= */ null, /* immediate= */ false);
+        Runnable callback;
+        synchronized (mLock) {
+            callback = mBinderDeathCallback;
+        }
+        if (callback != null) {
+            callback.run();
+        }
+    }
+
+    @Override
+    public boolean linkToDeath(@Nullable Runnable callback) {
+        synchronized (mLock) {
+            mBinderDeathCallback = callback;
+        }
+        try {
+            mCallerToken.linkToDeath(this, 0);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error linking vibration to token death", e);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void unlinkToDeath() {
+        try {
+            mCallerToken.unlinkToDeath(this, 0);
+        } catch (NoSuchElementException e) {
+            Slog.wtf(TAG, "Failed to unlink vibration to token death", e);
+        }
+        synchronized (mLock) {
+            mBinderDeathCallback = null;
+        }
+    }
+
+    @Override
+    public void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy,
+            boolean immediate) {
+        synchronized (mLock) {
+            if (mConductor != null) {
+                mConductor.notifyCancelled(new Vibration.EndInfo(status, endedBy), immediate);
+            } else {
+                mVibration.end(new Vibration.EndInfo(status, endedBy));
+            }
+        }
+    }
+
+    @Override
+    public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+        if (vibrationId != mVibration.id) {
+            return;
+        }
+        synchronized (mLock) {
+            if (mConductor != null) {
+                mConductor.notifyVibratorComplete(vibratorId);
+            }
+        }
+    }
+
+    @Override
+    public void notifySyncedVibratorsCallback(long vibrationId) {
+        if (vibrationId != mVibration.id) {
+            return;
+        }
+        synchronized (mLock) {
+            if (mConductor != null) {
+                mConductor.notifySyncedVibrationComplete();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 21fd4ce..bb2a17c 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -88,15 +88,9 @@
         stats.reportEnded(endInfo.endedBy);
     }
 
-    /** Return true if vibration is a repeating vibration. */
-    abstract boolean isRepeating();
-
     /** Return {@link VibrationSession.DebugInfo} with read-only debug data about this vibration. */
     abstract VibrationSession.DebugInfo getDebugInfo();
 
-    /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
-    abstract VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis);
-
     /** Immutable info passed as a signal to end a vibration. */
     static final class EndInfo {
         /** The vibration status to be set when it ends with this info. */
@@ -146,35 +140,41 @@
      * potentially expensive or resource-linked objects, such as {@link IBinder}.
      */
     static final class DebugInfoImpl implements VibrationSession.DebugInfo {
-        final VibrationSession.Status mStatus;
-        final long mCreateTime;
-        final VibrationSession.CallerInfo mCallerInfo;
+        private final VibrationSession.Status mStatus;
+        private final VibrationStats.StatsInfo mStatsInfo;
+        private final VibrationSession.CallerInfo mCallerInfo;
         @Nullable
-        final CombinedVibration mPlayedEffect;
-
-        private final long mStartTime;
-        private final long mEndTime;
-        private final long mDurationMs;
+        private final CombinedVibration mPlayedEffect;
         @Nullable
         private final CombinedVibration mOriginalEffect;
         private final int mScaleLevel;
         private final float mAdaptiveScale;
 
-        DebugInfoImpl(VibrationSession.Status status, VibrationStats stats,
-                @Nullable CombinedVibration playedEffect,
-                @Nullable CombinedVibration originalEffect, int scaleLevel,
-                float adaptiveScale, @NonNull VibrationSession.CallerInfo callerInfo) {
+        private final long mCreateUptime;
+        private final long mCreateTime;
+        private final long mStartTime;
+        private final long mEndTime;
+        private final long mDurationMs;
+
+        DebugInfoImpl(VibrationSession.Status status,
+                @NonNull VibrationSession.CallerInfo callerInfo, int vibrationType,
+                VibrationStats stats, @Nullable CombinedVibration playedEffect,
+                @Nullable CombinedVibration originalEffect, int scaleLevel, float adaptiveScale) {
             Objects.requireNonNull(callerInfo);
-            mCreateTime = stats.getCreateTimeDebug();
-            mStartTime = stats.getStartTimeDebug();
-            mEndTime = stats.getEndTimeDebug();
-            mDurationMs = stats.getDurationDebug();
+            mCallerInfo = callerInfo;
+            mStatsInfo = stats.toStatsInfo(callerInfo.uid, vibrationType,
+                    callerInfo.attrs.getUsage(), status);
             mPlayedEffect = playedEffect;
             mOriginalEffect = originalEffect;
             mScaleLevel = scaleLevel;
             mAdaptiveScale = adaptiveScale;
-            mCallerInfo = callerInfo;
             mStatus = status;
+
+            mCreateUptime = stats.getCreateUptimeMillis();
+            mCreateTime = stats.getCreateTimeDebug();
+            mStartTime = stats.getStartTimeDebug();
+            mEndTime = stats.getEndTimeDebug();
+            mDurationMs = stats.getDurationDebug();
         }
 
         @Override
@@ -184,7 +184,7 @@
 
         @Override
         public long getCreateUptimeMillis() {
-            return mCreateTime;
+            return mCreateUptime;
         }
 
         @Override
@@ -216,6 +216,7 @@
         @Override
         public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
             statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
+            statsLogger.writeVibrationReportedAsync(mStatsInfo);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/vibrator/VibrationSession.java b/services/core/java/com/android/server/vibrator/VibrationSession.java
index 70477a2..4de8f78 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSession.java
@@ -39,9 +39,18 @@
  */
 interface VibrationSession {
 
+    /** Returns the session creation time from {@link android.os.SystemClock#uptimeMillis()}. */
+    long getCreateUptimeMillis();
+
+    /** Return true if vibration session plays a repeating vibration. */
+    boolean isRepeating();
+
     /** Returns data about the client app that triggered this vibration session. */
     CallerInfo getCallerInfo();
 
+    /** Returns the binder token from the client app attached to this vibration session. */
+    IBinder getCallerToken();
+
     /** Returns debug data for logging and metric reports. */
     DebugInfo getDebugInfo();
 
@@ -58,6 +67,19 @@
     /** Removes link to the app process death. */
     void unlinkToDeath();
 
+    /** Returns true if this session was requested to end by {@link #requestEnd}. */
+    boolean wasEndRequested();
+
+    /**
+     * Request the end of this session, which might be acted upon asynchronously.
+     *
+     * <p>This is the same as {@link #requestEnd(Status, CallerInfo, boolean)}, with no
+     * {@link CallerInfo} and with {@code immediate} flag set to false.
+     */
+    default void requestEnd(@NonNull Status status) {
+        requestEnd(status, /* endedBy= */ null, /* immediate= */ false);
+    }
+
     /**
      * Notify the session end was requested, which might be acted upon asynchronously.
      *
@@ -71,6 +93,25 @@
     void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy, boolean immediate);
 
     /**
+     * Notify a vibrator has completed the last command during the playback of given vibration.
+     *
+     * <p>This will be called by the vibrator hardware callback indicating the last vibrate call is
+     * complete (e.g. on(), perform(), compose()). This does not mean the vibration is complete,
+     * since its playback might have one or more interactions with the vibrator hardware.
+     */
+    void notifyVibratorCallback(int vibratorId, long vibrationId);
+
+    /**
+     * Notify all synced vibrators have completed the last synchronized command during the playback
+     * of given vibration.
+     *
+     * <p>This will be called by the vibrator manager hardware callback indicating the last
+     * synchronized vibrate call is complete. This does not mean the vibration is complete, since
+     * its playback might have one or more interactions with the vibrator hardware.
+     */
+    void notifySyncedVibratorsCallback(long vibrationId);
+
+    /**
      * Session status with reference to values from vibratormanagerservice.proto for logging.
      */
     enum Status {
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
index fc0c6e7..637a5a1 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStats.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.CombinedVibration;
 import android.os.SystemClock;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
@@ -37,11 +38,11 @@
     //           vibrate request.
     // - Start: time a vibration started to play, which is closer to the time that the
     //          VibrationEffect started playing the very first segment.
-    // - End: time a vibration ended, even if it never started to play. This can be as soon as the
-    //        vibrator HAL reports it has finished the last command, or before it has even started
-    //        when the vibration is ignored or cancelled.
-    // Create and end times set by VibratorManagerService only, guarded by its lock.
-    // Start times set by VibrationThread only (single-threaded).
+    // - End: time a vibration ended with a status, even if it never started to play. This can be as
+    //        soon as the vibrator HAL reports it has finished the last command, or before it has
+    //        even started when the vibration is ignored or cancelled.
+    // Created and ended times set by VibratorManagerService only, guarded by its lock.
+    // Start time set by VibrationThread only (single-threaded).
     private long mCreateUptimeMillis;
     private long mStartUptimeMillis;
     private long mEndUptimeMillis;
@@ -97,6 +98,10 @@
         mInterruptedUsage = -1;
     }
 
+    StatsInfo toStatsInfo(int uid, int vibrationType, int usage, VibrationSession.Status status) {
+        return new VibrationStats.StatsInfo(uid, vibrationType, usage, status, this);
+    }
+
     long getCreateUptimeMillis() {
         return mCreateUptimeMillis;
     }
@@ -300,7 +305,7 @@
      * {@link com.android.internal.util.FrameworkStatsLog} as a
      * {@link com.android.internal.util.FrameworkStatsLog#VIBRATION_REPORTED}.
      */
-    static final class StatsInfo {
+    public static final class StatsInfo {
         public final int uid;
         public final int vibrationType;
         public final int usage;
@@ -331,7 +336,7 @@
         private boolean mIsWritten;
 
         StatsInfo(int uid, int vibrationType, int usage, VibrationSession.Status status,
-                VibrationStats stats, long completionUptimeMillis) {
+                VibrationStats stats) {
             this.uid = uid;
             this.vibrationType = vibrationType;
             this.usage = usage;
@@ -342,6 +347,9 @@
             interruptedUsage = stats.mInterruptedUsage;
             repeatCount = stats.mRepeatCount;
 
+            // Consider this vibration is being completed now.
+            long completionUptimeMillis = SystemClock.uptimeMillis();
+
             // This duration goes from the time this object was created until the time it was
             // completed. We can use latencies to detect the times between first and last
             // interaction with vibrator.
@@ -419,5 +427,25 @@
             }
             return res;
         }
+
+        /**
+         * Returns the vibration type value from {@code ReportedVibration} that best represents this
+         * {@link CombinedVibration}.
+         *
+         * <p>This does not include external vibrations, as those are not represented by a single
+         * vibration effect.
+         */
+        public static int findVibrationType(@Nullable CombinedVibration effect) {
+            if (effect == null) {
+                return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+            }
+            if (effect.hasVendorEffects()) {
+                return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR;
+            }
+            if (effect.getDuration() == Long.MAX_VALUE) {
+                return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED;
+            }
+            return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 1d52e3c..4bb0c16 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -20,8 +20,6 @@
 import android.annotation.Nullable;
 import android.os.Build;
 import android.os.CombinedVibration;
-import android.os.IBinder;
-import android.os.RemoteException;
 import android.os.VibrationEffect;
 import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
@@ -39,7 +37,6 @@
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.NoSuchElementException;
 import java.util.PriorityQueue;
 import java.util.Queue;
 import java.util.concurrent.CancellationException;
@@ -55,7 +52,7 @@
  * VibrationThread. The only thread-safe methods for calling from other threads are the "notify"
  * methods (which should never be used from the VibrationThread thread).
  */
-final class VibrationStepConductor implements IBinder.DeathRecipient {
+final class VibrationStepConductor {
     private static final boolean DEBUG = VibrationThread.DEBUG;
     private static final String TAG = VibrationThread.TAG;
 
@@ -346,42 +343,6 @@
     }
 
     /**
-     * Binder death notification. VibrationThread registers this when it's running a conductor.
-     * Note that cancellation could theoretically happen immediately, before the conductor has
-     * started, but in this case it will be processed in the first signals loop.
-     */
-    @Override
-    public void binderDied() {
-        if (DEBUG) {
-            Slog.d(TAG, "Binder died, cancelling vibration...");
-        }
-        notifyCancelled(new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED),
-                /* immediate= */ false);
-    }
-
-    /**
-     * Returns true if successfully linked this conductor to the death of the binder that requested
-     * the vibration.
-     */
-    public boolean linkToDeath() {
-        try {
-            mVibration.callerToken.linkToDeath(this, 0);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Error linking vibration to token death", e);
-            return false;
-        }
-        return true;
-    }
-
-    public void unlinkToDeath() {
-        try {
-            mVibration.callerToken.unlinkToDeath(this, 0);
-        } catch (NoSuchElementException e) {
-            Slog.wtf(TAG, "Failed to unlink vibration to token death", e);
-        }
-    }
-
-    /**
      * Notify the execution that cancellation is requested. This will be acted upon
      * asynchronously in the VibrationThread.
      *
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 07473d1..9b7bdec 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -51,7 +51,6 @@
 import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
-import android.os.SystemClock;
 import android.os.Trace;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
@@ -159,9 +158,9 @@
     @GuardedBy("mLock")
     private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>();
     @GuardedBy("mLock")
-    private VibrationStepConductor mCurrentVibration;
+    private SingleVibrationSession mCurrentVibration;
     @GuardedBy("mLock")
-    private VibrationStepConductor mNextVibration;
+    private SingleVibrationSession mNextVibration;
     @GuardedBy("mLock")
     private ExternalVibrationSession mCurrentExternalVibration;
     @GuardedBy("mLock")
@@ -188,24 +187,20 @@
                     // When the system is entering a non-interactive state, we want to cancel
                     // vibrations in case a misbehaving app has abandoned them.
                     if (shouldCancelOnScreenOffLocked(mNextVibration)) {
-                        clearNextVibrationLocked(new Vibration.EndInfo(
-                                Status.CANCELLED_BY_SCREEN_OFF));
+                        clearNextVibrationLocked(Status.CANCELLED_BY_SCREEN_OFF);
                     }
                     if (shouldCancelOnScreenOffLocked(mCurrentVibration)) {
-                        mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
-                                Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ false);
+                        mCurrentVibration.requestEnd(Status.CANCELLED_BY_SCREEN_OFF);
                     }
                 }
             } else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()
                     && intent.getAction().equals(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)) {
                 synchronized (mLock) {
                     if (shouldCancelOnFgUserRequest(mNextVibration)) {
-                        clearNextVibrationLocked(new Vibration.EndInfo(
-                                Status.CANCELLED_BY_FOREGROUND_USER));
+                        clearNextVibrationLocked(Status.CANCELLED_BY_FOREGROUND_USER);
                     }
                     if (shouldCancelOnFgUserRequest(mCurrentVibration)) {
-                        mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
-                                Status.CANCELLED_BY_FOREGROUND_USER), /* immediate= */ false);
+                        mCurrentVibration.requestEnd(Status.CANCELLED_BY_FOREGROUND_USER);
                     }
                 }
             }
@@ -222,12 +217,10 @@
                     }
                     synchronized (mLock) {
                         if (shouldCancelAppOpModeChangedLocked(mNextVibration)) {
-                            clearNextVibrationLocked(new Vibration.EndInfo(
-                                    Status.CANCELLED_BY_APP_OPS));
+                            clearNextVibrationLocked(Status.CANCELLED_BY_APP_OPS);
                         }
                         if (shouldCancelAppOpModeChangedLocked(mCurrentVibration)) {
-                            mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
-                                    Status.CANCELLED_BY_APP_OPS), /* immediate= */ false);
+                            mCurrentVibration.requestEnd(Status.CANCELLED_BY_APP_OPS);
                         }
                     }
                 }
@@ -602,8 +595,9 @@
             return null;
         }
         // Create Vibration.Stats as close to the received request as possible, for tracking.
-        HalVibration vib = new HalVibration(token, effect, callerInfo);
-        fillVibrationFallbacks(vib, effect);
+        SingleVibrationSession session = new SingleVibrationSession(token, callerInfo, effect);
+        HalVibration vib = session.getVibration();
+        vib.fillFallbacks(mVibrationSettings::getFallbackEffect);
 
         if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
             // Force update of user settings before checking if this vibration effect should
@@ -617,21 +611,26 @@
             }
 
             // Check if user settings or DnD is set to ignore this vibration.
-            Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
+            Status ignoreStatus = shouldIgnoreVibrationLocked(callerInfo);
+            CallerInfo ignoredBy = null;
 
             // Check if ongoing vibration is more important than this vibration.
-            if (vibrationEndInfo == null) {
-                vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vib);
+            if (ignoreStatus == null) {
+                Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(session);
+                if (vibrationEndInfo != null) {
+                    ignoreStatus = vibrationEndInfo.status;
+                    ignoredBy = vibrationEndInfo.endedBy;
+                }
             }
 
             // If not ignored so far then try to start this vibration.
-            if (vibrationEndInfo == null) {
+            if (ignoreStatus == null) {
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     if (mCurrentExternalVibration != null) {
                         vib.stats.reportInterruptedAnotherVibration(
                                 mCurrentExternalVibration.getCallerInfo());
-                        endExternalVibrateLocked(Status.CANCELLED_SUPERSEDED, vib.callerInfo,
+                        endExternalVibrateLocked(Status.CANCELLED_SUPERSEDED, callerInfo,
                                 /* continueExternalControl= */ false);
                     } else if (mCurrentVibration != null) {
                         if (mCurrentVibration.getVibration().canPipelineWith(vib)) {
@@ -645,21 +644,19 @@
                         } else {
                             vib.stats.reportInterruptedAnotherVibration(
                                     mCurrentVibration.getVibration().callerInfo);
-                            mCurrentVibration.notifyCancelled(
-                                    new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
-                                            vib.callerInfo),
+                            mCurrentVibration.requestEnd(Status.CANCELLED_SUPERSEDED, callerInfo,
                                     /* immediate= */ false);
                         }
                     }
-                    vibrationEndInfo = startVibrationLocked(vib);
+                    ignoreStatus = startVibrationLocked(session);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
             }
 
             // Ignored or failed to start the vibration, end it and report metrics right away.
-            if (vibrationEndInfo != null) {
-                endVibrationLocked(vib, vibrationEndInfo);
+            if (ignoreStatus != null) {
+                endVibrationLocked(session, ignoreStatus, ignoredBy);
             }
             return vib;
         }
@@ -677,27 +674,18 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Canceling vibration");
                 }
-                Vibration.EndInfo cancelledByUserInfo =
-                        new Vibration.EndInfo(Status.CANCELLED_BY_USER);
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    if (mNextVibration != null
-                            && shouldCancelVibration(mNextVibration.getVibration(),
-                            usageFilter, token)) {
-                        clearNextVibrationLocked(cancelledByUserInfo);
+                    if (shouldCancelVibration(mNextVibration, usageFilter, token)) {
+                        clearNextVibrationLocked(Status.CANCELLED_BY_USER);
                     }
-                    if (mCurrentVibration != null
-                            && shouldCancelVibration(mCurrentVibration.getVibration(),
-                            usageFilter, token)) {
-                        mCurrentVibration.notifyCancelled(
-                                cancelledByUserInfo, /* immediate= */false);
+                    if (shouldCancelVibration(mCurrentVibration, usageFilter, token)) {
+                        mCurrentVibration.requestEnd(Status.CANCELLED_BY_USER);
                     }
-                    if (mCurrentExternalVibration != null
-                            && shouldCancelVibration(
-                            mCurrentExternalVibration.getCallerInfo().attrs,
-                            usageFilter)) {
-                        endExternalVibrateLocked(cancelledByUserInfo.status,
-                                cancelledByUserInfo.endedBy, /* continueExternalControl= */ false);
+                    // TODO(b/370948466): investigate why token is not checked here and fix it.
+                    if (shouldCancelVibration(mCurrentExternalVibration, usageFilter, null)) {
+                        endExternalVibrateLocked(Status.CANCELLED_BY_USER,
+                                /* endedBy= */ null, /* continueExternalControl= */ false);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -842,18 +830,13 @@
                 return;
             }
 
-            HalVibration vib = mCurrentVibration.getVibration();
-            Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
-
-            if (inputDevicesChanged || (vibrationEndInfo != null)) {
+            Status ignoreStatus = shouldIgnoreVibrationLocked(mCurrentVibration.getCallerInfo());
+            if (inputDevicesChanged || (ignoreStatus != null)) {
                 if (DEBUG) {
                     Slog.d(TAG, "Canceling vibration because settings changed: "
-                            + (inputDevicesChanged ? "input devices changed"
-                            : vibrationEndInfo.status));
+                            + (inputDevicesChanged ? "input devices changed" : ignoreStatus));
                 }
-                mCurrentVibration.notifyCancelled(
-                        new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE),
-                        /* immediate= */ false);
+                mCurrentVibration.requestEnd(Status.CANCELLED_BY_SETTINGS_UPDATE);
             }
         }
     }
@@ -873,8 +856,8 @@
             if (vibrator == null) {
                 continue;
             }
-            Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
-            if (vibrationEndInfo == null) {
+            Status ignoreStatus = shouldIgnoreVibrationLocked(vib.callerInfo);
+            if (ignoreStatus == null) {
                 effect = mVibrationScaler.scale(effect, vib.callerInfo.attrs.getUsage());
             } else {
                 // Vibration should not run, use null effect to remove registered effect.
@@ -886,25 +869,21 @@
 
     @GuardedBy("mLock")
     @Nullable
-    private Vibration.EndInfo startVibrationLocked(HalVibration vib) {
+    private Status startVibrationLocked(SingleVibrationSession session) {
         Trace.traceBegin(TRACE_TAG_VIBRATOR, "startVibrationLocked");
         try {
             if (mInputDeviceDelegate.isAvailable()) {
-                return startVibrationOnInputDevicesLocked(vib);
+                return startVibrationOnInputDevicesLocked(session.getVibration());
             }
-
-            VibrationStepConductor conductor = createVibrationStepConductor(vib);
-
             if (mCurrentVibration == null) {
-                return startVibrationOnThreadLocked(conductor);
+                return startVibrationOnThreadLocked(session);
             }
             // If there's already a vibration queued (waiting for the previous one to finish
             // cancelling), end it cleanly and replace it with the new one.
             // Note that we don't consider pipelining here, because new pipelined ones should
             // replace pending non-executing pipelined ones anyway.
-            clearNextVibrationLocked(
-                    new Vibration.EndInfo(Status.IGNORED_SUPERSEDED, vib.callerInfo));
-            mNextVibration = conductor;
+            clearNextVibrationLocked(Status.IGNORED_SUPERSEDED, session.getCallerInfo());
+            mNextVibration = session;
             return null;
         } finally {
             Trace.traceEnd(TRACE_TAG_VIBRATOR);
@@ -913,50 +892,45 @@
 
     @GuardedBy("mLock")
     @Nullable
-    private Vibration.EndInfo startVibrationOnThreadLocked(VibrationStepConductor conductor) {
-        HalVibration vib = conductor.getVibration();
-        int mode = startAppOpModeLocked(vib.callerInfo);
+    private Status startVibrationOnThreadLocked(SingleVibrationSession session) {
+        VibrationStepConductor conductor = createVibrationStepConductor(session.getVibration());
+        session.setVibrationConductor(conductor);
+        int mode = startAppOpModeLocked(session.getCallerInfo());
         switch (mode) {
             case AppOpsManager.MODE_ALLOWED:
                 Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0);
                 // Make sure mCurrentVibration is set while triggering the VibrationThread.
-                mCurrentVibration = conductor;
-                if (!mCurrentVibration.linkToDeath()) {
+                mCurrentVibration = session;
+                if (!mCurrentVibration.linkToDeath(null)) {
                     // Shouldn't happen. The method call already logs a wtf.
                     mCurrentVibration = null;  // Aborted.
-                    return new Vibration.EndInfo(Status.IGNORED_ERROR_TOKEN);
+                    return Status.IGNORED_ERROR_TOKEN;
                 }
-                if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) {
+                if (!mVibrationThread.runVibrationOnVibrationThread(conductor)) {
                     // Shouldn't happen. The method call already logs a wtf.
+                    mCurrentVibration.setVibrationConductor(null);
                     mCurrentVibration = null;  // Aborted.
-                    return new Vibration.EndInfo(Status.IGNORED_ERROR_SCHEDULING);
+                    return Status.IGNORED_ERROR_SCHEDULING;
                 }
                 return null;
             case AppOpsManager.MODE_ERRORED:
                 Slog.w(TAG, "Start AppOpsManager operation errored for uid "
-                        + vib.callerInfo.uid);
-                return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS);
+                        + session.getCallerInfo().uid);
+                return Status.IGNORED_ERROR_APP_OPS;
             default:
-                return new Vibration.EndInfo(Status.IGNORED_APP_OPS);
+                return Status.IGNORED_APP_OPS;
         }
     }
 
     @GuardedBy("mLock")
-    private void endVibrationLocked(Vibration vib, Status status) {
-        endVibrationLocked(vib, new Vibration.EndInfo(status));
+    private void endVibrationLocked(VibrationSession session, Status status) {
+        endVibrationLocked(session, status, /* endedBy= */ null);
     }
 
     @GuardedBy("mLock")
-    private void endVibrationLocked(Vibration vib, Vibration.EndInfo vibrationEndInfo) {
-        vib.end(vibrationEndInfo);
-        reportEndedVibrationLocked(vib);
-    }
-
-    @GuardedBy("mLock")
-    private void reportEndedVibrationLocked(Vibration vib) {
-        logAndRecordVibration(vib.getDebugInfo());
-        mFrameworkStatsLogger.writeVibrationReportedAsync(
-                vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
+    private void endVibrationLocked(VibrationSession session, Status status, CallerInfo endedBy) {
+        session.requestEnd(status, endedBy, /* immediate= */ false);
+        logAndRecordVibration(session.getDebugInfo());
     }
 
     private VibrationStepConductor createVibrationStepConductor(HalVibration vib) {
@@ -975,12 +949,11 @@
                 mFrameworkStatsLogger, requestVibrationParamsFuture, mVibrationThreadCallbacks);
     }
 
-    private Vibration.EndInfo startVibrationOnInputDevicesLocked(HalVibration vib) {
+    private Status startVibrationOnInputDevicesLocked(HalVibration vib) {
         // Scale resolves the default amplitudes from the effect before scaling them.
         vib.scaleEffects(mVibrationScaler);
         mInputDeviceDelegate.vibrateIfAvailable(vib.callerInfo, vib.getEffectToPlay());
-
-        return new Vibration.EndInfo(Status.FORWARDED_TO_INPUT_DEVICES);
+        return Status.FORWARDED_TO_INPUT_DEVICES;
     }
 
     private void logAndRecordPerformHapticFeedbackAttempt(int uid, int deviceId, String opPkg,
@@ -994,9 +967,10 @@
     private void logAndRecordVibrationAttempt(@Nullable CombinedVibration effect,
             CallerInfo callerInfo, Status status) {
         logAndRecordVibration(
-                new Vibration.DebugInfoImpl(status, new VibrationStats(),
+                new Vibration.DebugInfoImpl(status, callerInfo,
+                        VibrationStats.StatsInfo.findVibrationType(effect), new VibrationStats(),
                         effect, /* originalEffect= */ null, VibrationScaler.SCALE_NONE,
-                        VibrationScaler.ADAPTIVE_SCALE_NONE, callerInfo));
+                        VibrationScaler.ADAPTIVE_SCALE_NONE));
     }
 
     private void logAndRecordVibration(DebugInfo info) {
@@ -1050,39 +1024,25 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private void reportFinishedVibrationLocked() {
-        Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
-        mCurrentVibration.unlinkToDeath();
-        HalVibration vib = mCurrentVibration.getVibration();
-        if (DEBUG) {
-            Slog.d(TAG, "Reporting vibration " + vib.id + " finished with " + vib.getStatus());
-        }
-        finishAppOpModeLocked(vib.callerInfo);
-        reportEndedVibrationLocked(vib);
-    }
-
     private void onSyncedVibrationComplete(long vibrationId) {
         synchronized (mLock) {
-            if (mCurrentVibration != null
-                    && mCurrentVibration.getVibration().id == vibrationId) {
+            if (mCurrentVibration != null) {
                 if (DEBUG) {
                     Slog.d(TAG, "Synced vibration " + vibrationId + " complete, notifying thread");
                 }
-                mCurrentVibration.notifySyncedVibrationComplete();
+                mCurrentVibration.notifySyncedVibratorsCallback(vibrationId);
             }
         }
     }
 
     private void onVibrationComplete(int vibratorId, long vibrationId) {
         synchronized (mLock) {
-            if (mCurrentVibration != null
-                    && mCurrentVibration.getVibration().id == vibrationId) {
+            if (mCurrentVibration != null) {
                 if (DEBUG) {
                     Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
                             + " complete, notifying thread");
                 }
-                mCurrentVibration.notifyVibratorComplete(vibratorId);
+                mCurrentVibration.notifyVibratorCallback(vibratorId, vibrationId);
             }
         }
     }
@@ -1094,14 +1054,14 @@
      */
     @GuardedBy("mLock")
     @Nullable
-    private Vibration.EndInfo shouldIgnoreVibrationForOngoingLocked(Vibration vib) {
+    private Vibration.EndInfo shouldIgnoreVibrationForOngoingLocked(VibrationSession session) {
         if (mCurrentExternalVibration != null) {
-            return shouldIgnoreVibrationForOngoing(vib, mCurrentExternalVibration);
+            return shouldIgnoreVibrationForOngoing(session, mCurrentExternalVibration);
         }
 
         if (mNextVibration != null) {
-            Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoing(vib,
-                    mNextVibration.getVibration());
+            Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoing(session,
+                    mNextVibration);
             if (vibrationEndInfo != null) {
                 // Next vibration has higher importance than the new one, so the new vibration
                 // should be ignored.
@@ -1110,14 +1070,12 @@
         }
 
         if (mCurrentVibration != null) {
-            HalVibration currentVibration = mCurrentVibration.getVibration();
-            if (currentVibration.hasEnded() || mCurrentVibration.wasNotifiedToCancel()) {
-                // Current vibration has ended or is cancelling, should not block incoming
-                // vibrations.
+            if (mCurrentVibration.wasEndRequested()) {
+                // Current session has ended or is cancelling, should not block incoming vibrations.
                 return null;
             }
 
-            return shouldIgnoreVibrationForOngoing(vib, currentVibration);
+            return shouldIgnoreVibrationForOngoing(session, mCurrentVibration);
         }
 
         return null;
@@ -1125,32 +1083,33 @@
 
     /**
      * Checks if the ongoing vibration has higher importance than the new one. If they have similar
-     * importance, then {@link Vibration#isRepeating()} is used as a tiebreaker.
+     * importance, then {@link VibrationSession#isRepeating()} is used as a tiebreaker.
      *
      * @return a Vibration.EndInfo if the vibration should be ignored, null otherwise.
      */
     @Nullable
     private static Vibration.EndInfo shouldIgnoreVibrationForOngoing(
-            @NonNull Vibration newVibration, @NonNull Vibration ongoingVibration) {
+            @NonNull VibrationSession newSession, @NonNull VibrationSession ongoingSession) {
 
-        int newVibrationImportance = getVibrationImportance(newVibration);
-        int ongoingVibrationImportance = getVibrationImportance(ongoingVibration);
+        int newSessionImportance = getVibrationImportance(newSession);
+        int ongoingSessionImportance = getVibrationImportance(ongoingSession);
 
-        if (newVibrationImportance > ongoingVibrationImportance) {
+        if (newSessionImportance > ongoingSessionImportance) {
             // New vibration has higher importance and should not be ignored.
             return null;
         }
 
-        if (ongoingVibrationImportance > newVibrationImportance) {
+        if (ongoingSessionImportance > newSessionImportance) {
             // Existing vibration has higher importance and should not be cancelled.
             return new Vibration.EndInfo(Status.IGNORED_FOR_HIGHER_IMPORTANCE,
-                    ongoingVibration.callerInfo);
+                    ongoingSession.getCallerInfo());
         }
 
         // Same importance, use repeating as a tiebreaker.
-        if (ongoingVibration.isRepeating() && !newVibration.isRepeating()) {
+        if (ongoingSession.isRepeating() && !newSession.isRepeating()) {
             // Ongoing vibration is repeating and new one is not, give priority to ongoing
-            return new Vibration.EndInfo(Status.IGNORED_FOR_ONGOING, ongoingVibration.callerInfo);
+            return new Vibration.EndInfo(Status.IGNORED_FOR_ONGOING,
+                    ongoingSession.getCallerInfo());
         }
         // New vibration is repeating or this is a complete tie between them,
         // give priority to new vibration.
@@ -1164,10 +1123,10 @@
      * @return a numeric representation for the vibration importance, larger values represent a
      * higher importance
      */
-    private static int getVibrationImportance(Vibration vibration) {
-        int usage = vibration.callerInfo.attrs.getUsage();
+    private static int getVibrationImportance(VibrationSession session) {
+        int usage = session.getCallerInfo().attrs.getUsage();
         if (usage == VibrationAttributes.USAGE_UNKNOWN) {
-            if (vibration.isRepeating()) {
+            if (session.isRepeating()) {
                 usage = VibrationAttributes.USAGE_RINGTONE;
             } else {
                 usage = VibrationAttributes.USAGE_TOUCH;
@@ -1201,10 +1160,10 @@
      */
     @GuardedBy("mLock")
     @Nullable
-    private Vibration.EndInfo shouldIgnoreVibrationLocked(CallerInfo callerInfo) {
+    private Status shouldIgnoreVibrationLocked(CallerInfo callerInfo) {
         Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(callerInfo);
         if (statusFromSettings != null) {
-            return new Vibration.EndInfo(statusFromSettings);
+            return statusFromSettings;
         }
 
         int mode = checkAppOpModeLocked(callerInfo);
@@ -1212,9 +1171,9 @@
             if (mode == AppOpsManager.MODE_ERRORED) {
                 // We might be getting calls from within system_server, so we don't actually
                 // want to throw a SecurityException here.
-                return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS);
+                return Status.IGNORED_ERROR_APP_OPS;
             } else {
-                return new Vibration.EndInfo(Status.IGNORED_APP_OPS);
+                return Status.IGNORED_APP_OPS;
             }
         }
 
@@ -1239,32 +1198,29 @@
     /**
      * Return true if the vibration has the same token and usage belongs to given usage class.
      *
-     * @param vib         The ongoing or pending vibration to be cancelled.
+     * @param session     The ongoing or pending vibration session to be cancelled.
      * @param usageFilter The vibration usages to be cancelled, any bitwise combination of
      *                    VibrationAttributes.USAGE_* values.
-     * @param token       The binder token to identify the vibration origin. Only vibrations
+     * @param tokenFilter The binder token to identify the vibration origin. Only vibrations
      *                    started with the same token can be cancelled with it.
      */
-    private boolean shouldCancelVibration(HalVibration vib, int usageFilter, IBinder token) {
-        return (vib.callerToken == token) && shouldCancelVibration(vib.callerInfo.attrs,
-                usageFilter);
-    }
-
-    /**
-     * Return true if the external vibration usage belongs to given usage class.
-     *
-     * @param attrs       The attributes of an ongoing or pending vibration to be cancelled.
-     * @param usageFilter The vibration usages to be cancelled, any bitwise combination of
-     *                    VibrationAttributes.USAGE_* values.
-     */
-    private boolean shouldCancelVibration(VibrationAttributes attrs, int usageFilter) {
-        if (attrs.getUsage() == VibrationAttributes.USAGE_UNKNOWN) {
+    private boolean shouldCancelVibration(@Nullable VibrationSession session, int usageFilter,
+            @Nullable IBinder tokenFilter) {
+        if (session == null) {
+            return false;
+        }
+        if ((tokenFilter != null) && (tokenFilter != session.getCallerToken())) {
+            // Vibration from a different app, this should not cancel it.
+            return false;
+        }
+        int usage = session.getCallerInfo().attrs.getUsage();
+        if (usage == VibrationAttributes.USAGE_UNKNOWN) {
             // Special case, usage UNKNOWN would match all filters. Instead it should only match if
             // it's cancelling that usage specifically, or if cancelling all usages.
             return usageFilter == VibrationAttributes.USAGE_UNKNOWN
                     || usageFilter == VibrationAttributes.USAGE_FILTER_MATCH_ALL;
         }
-        return (usageFilter & attrs.getUsage()) == attrs.getUsage();
+        return (usageFilter & usage) == usage;
     }
 
     /**
@@ -1340,45 +1296,6 @@
     }
 
     /**
-     * Sets fallback effects to all prebaked ones in given combination of effects, based on {@link
-     * VibrationSettings#getFallbackEffect}.
-     */
-    private void fillVibrationFallbacks(HalVibration vib, CombinedVibration effect) {
-        if (effect instanceof CombinedVibration.Mono) {
-            fillVibrationFallbacks(vib, ((CombinedVibration.Mono) effect).getEffect());
-        } else if (effect instanceof CombinedVibration.Stereo) {
-            SparseArray<VibrationEffect> effects =
-                    ((CombinedVibration.Stereo) effect).getEffects();
-            for (int i = 0; i < effects.size(); i++) {
-                fillVibrationFallbacks(vib, effects.valueAt(i));
-            }
-        } else if (effect instanceof CombinedVibration.Sequential) {
-            List<CombinedVibration> effects =
-                    ((CombinedVibration.Sequential) effect).getEffects();
-            for (int i = 0; i < effects.size(); i++) {
-                fillVibrationFallbacks(vib, effects.get(i));
-            }
-        }
-    }
-
-    private void fillVibrationFallbacks(HalVibration vib, VibrationEffect 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 prebaked) {
-                VibrationEffect fallback = mVibrationSettings.getFallbackEffect(
-                        prebaked.getEffectId());
-                if (prebaked.shouldFallback() && fallback != null) {
-                    vib.addFallback(prebaked.getEffectId(), fallback);
-                }
-            }
-        }
-    }
-
-    /**
      * Return new {@link VibrationAttributes} that only applies flags that this user has permissions
      * to use.
      */
@@ -1475,30 +1392,28 @@
     }
 
     @GuardedBy("mLock")
-    private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationStepConductor conductor) {
-        if (conductor == null) {
+    private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationSession session) {
+        if (session == null) {
             return false;
         }
-        HalVibration vib = conductor.getVibration();
-        return mVibrationSettings.shouldCancelVibrationOnScreenOff(vib.callerInfo,
-                vib.stats.getCreateUptimeMillis());
+        return mVibrationSettings.shouldCancelVibrationOnScreenOff(session.getCallerInfo(),
+                session.getCreateUptimeMillis());
     }
 
     @GuardedBy("mLock")
-    private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationStepConductor conductor) {
-        if (conductor == null) {
+    private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationSession session) {
+        if (session == null) {
             return false;
         }
-        return checkAppOpModeLocked(conductor.getVibration().callerInfo)
-                != AppOpsManager.MODE_ALLOWED;
+        return checkAppOpModeLocked(session.getCallerInfo()) != AppOpsManager.MODE_ALLOWED;
     }
 
     @GuardedBy("mLock")
-    private boolean shouldCancelOnFgUserRequest(@Nullable VibrationStepConductor conductor) {
-        if (conductor == null) {
+    private boolean shouldCancelOnFgUserRequest(@Nullable VibrationSession session) {
+        if (session == null) {
             return false;
         }
-        return conductor.getVibration().callerInfo.attrs.getUsageClass() == USAGE_CLASS_ALARM;
+        return session.getCallerInfo().attrs.getUsageClass() == USAGE_CLASS_ALARM;
     }
 
     @GuardedBy("mLock")
@@ -1660,17 +1575,21 @@
                     }
                     if (mCurrentVibration != null) {
                         // This is when we consider the current vibration complete, report metrics.
-                        reportFinishedVibrationLocked();
+                        if (DEBUG) {
+                            Slog.d(TAG, "Reporting vibration " + vibrationId + " finished.");
+                        }
+                        mCurrentVibration.unlinkToDeath();
+                        finishAppOpModeLocked(mCurrentVibration.getCallerInfo());
+                        logAndRecordVibration(mCurrentVibration.getDebugInfo());
+                        Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                         mCurrentVibration = null;
                     }
                     if (mNextVibration != null) {
-                        VibrationStepConductor nextConductor = mNextVibration;
+                        SingleVibrationSession nextVibration = mNextVibration;
                         mNextVibration = null;
-                        Vibration.EndInfo vibrationEndInfo = startVibrationOnThreadLocked(
-                                nextConductor);
-                        if (vibrationEndInfo != null) {
-                            // Failed to start the vibration, end it and report metrics right away.
-                            endVibrationLocked(nextConductor.getVibration(), vibrationEndInfo);
+                        Status startErrorStatus = startVibrationOnThreadLocked(nextVibration);
+                        if (startErrorStatus != null) {
+                            endVibrationLocked(nextVibration, startErrorStatus);
                         }
                     }
                 }
@@ -1892,17 +1811,22 @@
             mInfo.dump(proto, fieldId);
         }
     }
+    /** Clears mNextVibration if set, ending it cleanly */
+    @GuardedBy("mLock")
+    private void clearNextVibrationLocked(Status status) {
+        clearNextVibrationLocked(status, /* endedBy= */ null);
+    }
 
     /** Clears mNextVibration if set, ending it cleanly */
     @GuardedBy("mLock")
-    private void clearNextVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
+    private void clearNextVibrationLocked(Status status, CallerInfo endedBy) {
         if (mNextVibration != null) {
             if (DEBUG) {
-                Slog.d(TAG, "Dropping pending vibration " + mNextVibration.getVibration().id
-                        + " with end info: " + vibrationEndInfo);
+                Slog.d(TAG, "Dropping pending vibration from " + mNextVibration.getCallerInfo()
+                        + " with status: " + status);
             }
             // Clearing next vibration before playing it, end it and report metrics right away.
-            endVibrationLocked(mNextVibration.getVibration(), vibrationEndInfo);
+            endVibrationLocked(mNextVibration, status, endedBy);
             mNextVibration = null;
         }
     }
@@ -1927,7 +1851,7 @@
             setExternalControl(false, mCurrentExternalVibration.stats);
         }
         // The external control was turned off, end it and report metrics right away.
-        reportEndedVibrationLocked(mCurrentExternalVibration);
+        logAndRecordVibration(mCurrentExternalVibration.getDebugInfo());
         mCurrentExternalVibration = null;
     }
 
@@ -1987,16 +1911,16 @@
             try {
                 // Create Vibration.Stats as close to the received request as possible, for
                 // tracking.
-                ExternalVibrationSession externalVibration = new ExternalVibrationSession(vib);
+                ExternalVibrationSession session = new ExternalVibrationSession(vib);
                 // Mute the request until we run all the checks and accept the vibration.
-                externalVibration.muteScale();
+                session.muteScale();
                 boolean alreadyUnderExternalControl = false;
                 boolean waitForCompletion = false;
 
                 synchronized (mLock) {
                     if (!hasExternalControlCapability()) {
-                        endVibrationLocked(externalVibration, Status.IGNORED_UNSUPPORTED);
-                        return externalVibration.getScale();
+                        endVibrationLocked(session, Status.IGNORED_UNSUPPORTED);
+                        return session.getScale();
                     }
 
                     if (ActivityManager.checkComponentPermission(
@@ -2006,29 +1930,30 @@
                         Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
                                 + " tried to play externally controlled vibration"
                                 + " without VIBRATE permission, ignoring.");
-                        endVibrationLocked(externalVibration, Status.IGNORED_MISSING_PERMISSION);
-                        return externalVibration.getScale();
+                        endVibrationLocked(session, Status.IGNORED_MISSING_PERMISSION);
+                        return session.getScale();
                     }
 
-                    Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(
-                            externalVibration.callerInfo);
+                    Status ignoreStatus = shouldIgnoreVibrationLocked(session.callerInfo);
+                    if (ignoreStatus != null) {
+                        endVibrationLocked(session, ignoreStatus);
+                        return session.getScale();
+                    }
 
-                    if (vibrationEndInfo == null
-                            && mCurrentExternalVibration != null
+                    if (mCurrentExternalVibration != null
                             && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
                         // We are already playing this external vibration, so we can return the same
                         // scale calculated in the previous call to this method.
                         return mCurrentExternalVibration.getScale();
                     }
 
-                    if (vibrationEndInfo == null) {
-                        // Check if ongoing vibration is more important than this vibration.
-                        vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(externalVibration);
-                    }
-
+                    // Check if ongoing vibration is more important than this vibration.
+                    Vibration.EndInfo vibrationEndInfo =
+                            shouldIgnoreVibrationForOngoingLocked(session);
                     if (vibrationEndInfo != null) {
-                        endVibrationLocked(externalVibration, vibrationEndInfo);
-                        return externalVibration.getScale();
+                        endVibrationLocked(session, vibrationEndInfo.status,
+                                vibrationEndInfo.endedBy);
+                        return session.getScale();
                     }
 
                     if (mCurrentExternalVibration == null) {
@@ -2036,15 +1961,12 @@
                         // vibration that may be playing and ready the vibrator for external
                         // control.
                         if (mCurrentVibration != null) {
-                            externalVibration.stats.reportInterruptedAnotherVibration(
+                            session.stats.reportInterruptedAnotherVibration(
                                     mCurrentVibration.getVibration().callerInfo);
-                            clearNextVibrationLocked(
-                                    new Vibration.EndInfo(Status.IGNORED_FOR_EXTERNAL,
-                                            externalVibration.callerInfo));
-                            mCurrentVibration.notifyCancelled(
-                                    new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
-                                            externalVibration.callerInfo),
-                                    /* immediate= */ true);
+                            clearNextVibrationLocked(Status.IGNORED_FOR_EXTERNAL,
+                                    session.callerInfo);
+                            mCurrentVibration.requestEnd(Status.CANCELLED_SUPERSEDED,
+                                    session.callerInfo, /* immediate= */ true);
                             waitForCompletion = true;
                         }
                     } else {
@@ -2060,10 +1982,10 @@
                         // as we would need to mute the old one still if it came from a different
                         // controller.
                         alreadyUnderExternalControl = true;
-                        externalVibration.stats.reportInterruptedAnotherVibration(
+                        session.stats.reportInterruptedAnotherVibration(
                                 mCurrentExternalVibration.getCallerInfo());
                         endExternalVibrateLocked(Status.CANCELLED_SUPERSEDED,
-                                externalVibration.callerInfo, /* continueExternalControl= */ true);
+                                session.callerInfo, /* continueExternalControl= */ true);
                     }
                 }
                 // Wait for lock and interact with HAL to set external control outside main lock.
@@ -2071,8 +1993,8 @@
                     if (!mVibrationThread.waitForThreadIdle(VIBRATION_CANCEL_WAIT_MILLIS)) {
                         Slog.e(TAG, "Timed out waiting for vibration to cancel");
                         synchronized (mLock) {
-                            endVibrationLocked(externalVibration, Status.IGNORED_ERROR_CANCELLING);
-                            return externalVibration.getScale();
+                            endVibrationLocked(session, Status.IGNORED_ERROR_CANCELLING);
+                            return session.getScale();
                         }
                     }
                 }
@@ -2080,7 +2002,7 @@
                     if (DEBUG) {
                         Slog.d(TAG, "Vibrator going under external control.");
                     }
-                    setExternalControl(true, externalVibration.stats);
+                    setExternalControl(true, session.stats);
                 }
                 synchronized (mLock) {
                     if (DEBUG) {
@@ -2095,14 +2017,14 @@
                         mVibrationSettings.update();
                     }
 
-                    mCurrentExternalVibration = externalVibration;
-                    externalVibration.linkToDeath(this::onExternalVibrationBinderDied);
-                    externalVibration.scale(mVibrationScaler, attrs.getUsage());
+                    mCurrentExternalVibration = session;
+                    session.linkToDeath(this::onExternalVibrationBinderDied);
+                    session.scale(mVibrationScaler, attrs.getUsage());
 
                     // Vibrator will start receiving data from external channels after this point.
                     // Report current time as the vibration start time, for debugging.
-                    externalVibration.stats.reportStarted();
-                    return externalVibration.getScale();
+                    session.stats.reportStarted();
+                    return session.getScale();
                 }
             } finally {
                 Trace.traceEnd(TRACE_TAG_VIBRATOR);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2972771..0b36c7e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6429,11 +6429,7 @@
             // and the token could be null.
             return;
         }
-        final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy
-                .getAppCompatCameraPolicy(r);
-        if (cameraPolicy != null) {
-            cameraPolicy.onActivityRefreshed(r);
-        }
+        AppCompatCameraPolicy.onActivityRefreshed(r);
     }
 
     static void splashScreenAttachedLocked(IBinder token) {
@@ -9476,11 +9472,7 @@
             return;
         }
 
-        final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
-                this);
-        if (cameraPolicy != null) {
-            cameraPolicy.onActivityConfigurationChanging(this, newConfig, lastReportedConfig);
-        }
+        AppCompatCameraPolicy.onActivityConfigurationChanging(this, newConfig, lastReportedConfig);
     }
 
     /** Get process configuration, or global config if the process is not set. */
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index 0e66629..d59046f 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -255,13 +255,10 @@
                 mActivityRecord.getOverrideOrientation());
         final AppCompatCameraOverrides cameraOverrides =
                 mActivityRecord.mAppCompatController.getAppCompatCameraOverrides();
-        final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
-                mActivityRecord);
         // Don't resize to split screen size when in book mode if letterbox position is centered
         return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape)
                 || cameraOverrides.isCameraCompatSplitScreenAspectRatioAllowed()
-                && (cameraPolicy != null
-                    && cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord));
+                && AppCompatCameraPolicy.isTreatmentEnabledForActivity(mActivityRecord);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 3b023fe..548c0a3 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -74,11 +74,9 @@
             @NonNull Rect parentBounds) {
         // If in camera compat mode, aspect ratio from the camera compat policy has priority over
         // default letterbox aspect ratio.
-        final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
-                mActivityRecord);
-        if (cameraPolicy != null && cameraPolicy.shouldCameraCompatControlAspectRatio(
+        if (AppCompatCameraPolicy.shouldCameraCompatControlAspectRatio(
                 mActivityRecord)) {
-            return cameraPolicy.getCameraCompatAspectRatio(mActivityRecord);
+            return AppCompatCameraPolicy.getCameraCompatAspectRatio(mActivityRecord);
         }
 
         final float letterboxAspectRatioOverride =
@@ -128,12 +126,8 @@
         if (aspectRatioOverrides.shouldApplyUserMinAspectRatioOverride()) {
             return aspectRatioOverrides.getUserMinAspectRatio();
         }
-        final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
-                mActivityRecord);
-        final boolean shouldOverrideMinAspectRatioForCamera = cameraPolicy != null
-                && cameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord);
         if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
-                && !shouldOverrideMinAspectRatioForCamera) {
+                && !AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord)) {
             if (mActivityRecord.isUniversalResizeable()) {
                 return 0;
             }
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
index f6090eb..1d00136 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
@@ -70,9 +70,10 @@
         }
     }
 
-    void onActivityRefreshed(@NonNull ActivityRecord activity) {
-        if (mActivityRefresher != null) {
-            mActivityRefresher.onActivityRefreshed(activity);
+    static void onActivityRefreshed(@NonNull ActivityRecord activity) {
+        final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+        if (cameraPolicy != null && cameraPolicy.mActivityRefresher != null) {
+            cameraPolicy.mActivityRefresher.onActivityRefreshed(activity);
         }
     }
 
@@ -88,10 +89,11 @@
      * camera preview and can lead to sideways or stretching issues persisting even after force
      * rotation.
      */
-    void onActivityConfigurationChanging(@NonNull ActivityRecord activity,
+    static void onActivityConfigurationChanging(@NonNull ActivityRecord activity,
             @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
-        if (mActivityRefresher != null) {
-            mActivityRefresher.onActivityConfigurationChanging(activity, newConfig,
+        final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+        if (cameraPolicy != null && cameraPolicy.mActivityRefresher != null) {
+            cameraPolicy.mActivityRefresher.onActivityConfigurationChanging(activity, newConfig,
                     lastReportedConfig);
         }
     }
@@ -108,11 +110,11 @@
         }
     }
 
-    boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
-        if (mDisplayRotationCompatPolicy != null) {
-            return mDisplayRotationCompatPolicy.isActivityEligibleForOrientationOverride(activity);
-        }
-        return false;
+    static boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
+        final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+        return cameraPolicy != null && cameraPolicy.mDisplayRotationCompatPolicy != null
+                && cameraPolicy.mDisplayRotationCompatPolicy
+                        .isActivityEligibleForOrientationOverride(activity);
     }
 
     /**
@@ -125,11 +127,11 @@
      *     <li>The activity has fixed orientation but not "locked" or "nosensor" one.
      * </ul>
      */
-    boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
-        if (mDisplayRotationCompatPolicy != null) {
-            return mDisplayRotationCompatPolicy.isTreatmentEnabledForActivity(activity);
-        }
-        return false;
+    static boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity) {
+        final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+        return cameraPolicy != null && cameraPolicy.mDisplayRotationCompatPolicy != null
+                && cameraPolicy.mDisplayRotationCompatPolicy
+                        .isTreatmentEnabledForActivity(activity);
     }
 
     void start() {
@@ -176,23 +178,31 @@
     }
 
     // TODO(b/369070416): have policies implement the same interface.
-    boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) {
-        return (mDisplayRotationCompatPolicy != null
-                        && mDisplayRotationCompatPolicy.shouldCameraCompatControlOrientation(
-                                activity))
-                || (mCameraCompatFreeformPolicy != null
-                        && mCameraCompatFreeformPolicy.shouldCameraCompatControlOrientation(
-                                activity));
+    static boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) {
+        final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+        if (cameraPolicy == null) {
+            return false;
+        }
+        return (cameraPolicy.mDisplayRotationCompatPolicy != null
+                        && cameraPolicy.mDisplayRotationCompatPolicy
+                                .shouldCameraCompatControlOrientation(activity))
+                || (cameraPolicy.mCameraCompatFreeformPolicy != null
+                        && cameraPolicy.mCameraCompatFreeformPolicy
+                                .shouldCameraCompatControlOrientation(activity));
     }
 
     // TODO(b/369070416): have policies implement the same interface.
-    boolean shouldCameraCompatControlAspectRatio(@NonNull ActivityRecord activity) {
-        return (mDisplayRotationCompatPolicy != null
-                        && mDisplayRotationCompatPolicy.shouldCameraCompatControlAspectRatio(
-                                activity))
-                || (mCameraCompatFreeformPolicy != null
-                        && mCameraCompatFreeformPolicy.shouldCameraCompatControlAspectRatio(
-                                activity));
+    static boolean shouldCameraCompatControlAspectRatio(@NonNull ActivityRecord activity) {
+        final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+        if (cameraPolicy == null) {
+            return false;
+        }
+        return (cameraPolicy.mDisplayRotationCompatPolicy != null
+                        && cameraPolicy.mDisplayRotationCompatPolicy
+                                .shouldCameraCompatControlAspectRatio(activity))
+                || (cameraPolicy.mCameraCompatFreeformPolicy != null
+                        && cameraPolicy.mCameraCompatFreeformPolicy
+                                .shouldCameraCompatControlAspectRatio(activity));
     }
 
     // TODO(b/369070416): have policies implement the same interface.
@@ -200,29 +210,41 @@
      * @return {@code true} if the Camera is active for the provided {@link ActivityRecord} and
      * any camera compat treatment could be triggered for the current windowing mode.
      */
-    private boolean isCameraRunningAndWindowingModeEligible(@NonNull ActivityRecord activity) {
-        return (mDisplayRotationCompatPolicy != null
-                && mDisplayRotationCompatPolicy.isCameraRunningAndWindowingModeEligible(activity,
-                        /* mustBeFullscreen */ true))
-                || (mCameraCompatFreeformPolicy != null && mCameraCompatFreeformPolicy
-                        .isCameraRunningAndWindowingModeEligible(activity));
+    private static boolean isCameraRunningAndWindowingModeEligible(
+            @NonNull ActivityRecord activity) {
+        final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+        if (cameraPolicy == null) {
+            return false;
+        }
+        return (cameraPolicy.mDisplayRotationCompatPolicy != null
+                && cameraPolicy.mDisplayRotationCompatPolicy
+                        .isCameraRunningAndWindowingModeEligible(activity,
+                                /* mustBeFullscreen */ true))
+                || (cameraPolicy.mCameraCompatFreeformPolicy != null
+                        && cameraPolicy.mCameraCompatFreeformPolicy
+                                .isCameraRunningAndWindowingModeEligible(activity));
     }
 
     @Nullable
     String getSummaryForDisplayRotationHistoryRecord() {
-        if (mDisplayRotationCompatPolicy != null) {
-            return mDisplayRotationCompatPolicy.getSummaryForDisplayRotationHistoryRecord();
-        }
-        return null;
+        return mDisplayRotationCompatPolicy != null
+                ? mDisplayRotationCompatPolicy.getSummaryForDisplayRotationHistoryRecord()
+                : null;
     }
 
     // TODO(b/369070416): have policies implement the same interface.
-    float getCameraCompatAspectRatio(@NonNull ActivityRecord activity) {
-        float displayRotationCompatPolicyAspectRatio = mDisplayRotationCompatPolicy != null
-                ? mDisplayRotationCompatPolicy.getCameraCompatAspectRatio(activity)
+    static float getCameraCompatAspectRatio(@NonNull ActivityRecord activity) {
+        final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+        if (cameraPolicy == null) {
+            return 1.0f;
+        }
+        float displayRotationCompatPolicyAspectRatio =
+                cameraPolicy.mDisplayRotationCompatPolicy != null
+                ? cameraPolicy.mDisplayRotationCompatPolicy.getCameraCompatAspectRatio(activity)
                 : MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
-        float cameraCompatFreeformPolicyAspectRatio = mCameraCompatFreeformPolicy != null
-                ? mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(activity)
+        float cameraCompatFreeformPolicyAspectRatio =
+                cameraPolicy.mCameraCompatFreeformPolicy != null
+                ? cameraPolicy.mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(activity)
                 : MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
         return Math.max(displayRotationCompatPolicyAspectRatio,
                 cameraCompatFreeformPolicyAspectRatio);
@@ -232,8 +254,8 @@
      * Whether we should apply the min aspect ratio per-app override only when an app is connected
      * to the camera.
      */
-    boolean shouldOverrideMinAspectRatioForCamera(@NonNull ActivityRecord activityRecord) {
-        return isCameraRunningAndWindowingModeEligible(activityRecord)
+    static boolean shouldOverrideMinAspectRatioForCamera(@NonNull ActivityRecord activityRecord) {
+        return AppCompatCameraPolicy.isCameraRunningAndWindowingModeEligible(activityRecord)
                 && activityRecord.mAppCompatController.getAppCompatCameraOverrides()
                         .isOverrideMinAspectRatioForCameraEnabled();
     }
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 5bd4aeb..f5d58ea 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -58,10 +58,8 @@
                 && displayContent.getIgnoreOrientationRequest();
         final boolean shouldApplyUserFullscreenOverride = mAppCompatOverrides
                 .getAppCompatAspectRatioOverrides().shouldApplyUserFullscreenOverride();
-        final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy
-                .getAppCompatCameraPolicy(mActivityRecord);
-        final boolean shouldCameraCompatControlOrientation = cameraPolicy != null
-                && cameraPolicy.shouldCameraCompatControlOrientation(mActivityRecord);
+        final boolean shouldCameraCompatControlOrientation =
+                AppCompatCameraPolicy.shouldCameraCompatControlOrientation(mActivityRecord);
         if (shouldApplyUserFullscreenOverride && isIgnoreOrientationRequestEnabled
                 // Do not override orientation to fullscreen for camera activities.
                 // Fixed-orientation activities are rarely tested in other orientations, and it
@@ -98,7 +96,7 @@
         if (displayContent != null
                 && mAppCompatOverrides.getAppCompatCameraOverrides()
                     .isOverrideOrientationOnlyForCameraEnabled()
-                && !displayContent.mAppCompatCameraPolicy
+                && !AppCompatCameraPolicy
                     .isActivityEligibleForOrientationOverride(mActivityRecord)) {
             return candidate;
         }
@@ -213,5 +211,4 @@
         }
         return false;
     }
-
 }
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 1073713..264c8be 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -146,7 +146,7 @@
                     "process bound by foreground uid");
         }
         // Allow if the caller has an activity in any foreground task.
-        if (checkConfiguration.checkVisibility && hasActivityInVisibleTask
+        if (checkConfiguration.checkOtherExemptions && hasActivityInVisibleTask
                 && appSwitchState != APP_SWITCH_DISALLOW) {
             return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ false,
                     "process has activity in foreground task");
diff --git a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
index 3b2f723..c8cb621 100644
--- a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
@@ -194,12 +194,8 @@
             return aspectRatioOverrides.getUserMinAspectRatio();
         }
 
-        final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
-                mActivityRecord);
-        final boolean shouldOverrideMinAspectRatioForCamera = cameraPolicy != null
-                && cameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord);
         if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
-                && !shouldOverrideMinAspectRatioForCamera) {
+                && !AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord)) {
             if (mActivityRecord.isUniversalResizeable()) {
                 return 0;
             }
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index b414a862..61b13a8 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -47,6 +47,7 @@
 import android.view.WindowInsetsAnimation;
 import android.view.WindowInsetsAnimation.Bounds;
 import android.view.WindowManager;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodManager;
 
@@ -412,6 +413,22 @@
                 state.addSource(imeSource);
                 return state;
             }
+        } else if (Flags.refactorInsetsController()
+                && (w.mMergedExcludeInsetsTypes & WindowInsets.Type.ime()) != 0) {
+            // In some cases (e.g. split screen from when the IME was requested and the animation
+            // actually starts) the insets should not be send, unless the flag is unset.
+            final InsetsSource originalImeSource = originalState.peekSource(ID_IME);
+            if (originalImeSource != null && originalImeSource.isVisible()) {
+                final InsetsState state = copyState
+                        ? new InsetsState(originalState)
+                        : originalState;
+                final InsetsSource imeSource = new InsetsSource(originalImeSource);
+                // Setting the height to zero, pretending we're in floating mode
+                imeSource.setFrame(0, 0, 0, 0);
+                imeSource.setVisibleFrame(imeSource.getFrame());
+                state.addSource(imeSource);
+                return state;
+            }
         }
         return originalState;
     }
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index ede587c..5dddf36 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -458,6 +458,12 @@
         mDisplayContent.notifyInsetsChanged(mDispatchInsetsChanged);
     }
 
+    void notifyInsetsChanged(ArraySet<WindowState> changedWindows) {
+        for (int i = changedWindows.size() - 1; i >= 0; i--) {
+            mDispatchInsetsChanged.accept(changedWindows.valueAt(i));
+        }
+    }
+
     /**
      * Checks if the control target has pending controls.
      *
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
index 8e32813..2428836 100644
--- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
+++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
@@ -156,7 +156,7 @@
 
     Listeners mRegisteredListeners = new Listeners();
 
-    private InputWindowHandle[] mLastWindowHandles;
+    private Pair<InputWindowHandle[], WindowInfosListener.DisplayInfo[]> mLastWindowHandles;
 
     private void startHandlerThreadIfNeeded() {
         synchronized (mHandlerThreadLock) {
@@ -222,10 +222,10 @@
             @Override
             public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
                     DisplayInfo[] displayInfos) {
-                mHandler.post(() -> computeTpl(windowHandles));
+                mHandler.post(() -> computeTpl(new Pair<>(windowHandles, displayInfos)));
             }
         };
-        mLastWindowHandles = mWindowInfosListener.register().first;
+        mLastWindowHandles = mWindowInfosListener.register();
     }
 
     private void unregisterWindowInfosListener() {
@@ -238,28 +238,52 @@
         mLastWindowHandles = null;
     }
 
-    private void computeTpl(InputWindowHandle[] windowHandles) {
+    private void computeTpl(
+            Pair<InputWindowHandle[], WindowInfosListener.DisplayInfo[]> windowHandles) {
         mLastWindowHandles = windowHandles;
-        if (mLastWindowHandles == null || mLastWindowHandles.length == 0
+        if (mLastWindowHandles == null || mLastWindowHandles.first.length == 0
                 || mRegisteredListeners.isEmpty()) {
             return;
         }
 
         Rect tmpRect = new Rect();
+        RectF tmpRectF = new RectF();
+        Rect tmpLogicalDisplaySize = new Rect();
         Matrix tmpInverseMatrix = new Matrix();
         float[] tmpMatrix = new float[9];
         Region coveredRegionsAbove = new Region();
         long currTimeMs = System.currentTimeMillis();
-        ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.length);
+        ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.first.length);
 
         ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates =
                 new ArrayMap<>();
-        for (var windowHandle : mLastWindowHandles) {
+        for (var windowHandle : mLastWindowHandles.first) {
             if (!windowHandle.canOccludePresentation) {
                 ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
                 continue;
             }
-            tmpRect.set(windowHandle.frame);
+            var displayFound = false;
+            tmpRectF.set(windowHandle.frame);
+            for (var displayHandle : mLastWindowHandles.second) {
+                if (displayHandle.mDisplayId == windowHandle.displayId) {
+                    // Transform the window frame into display logical space and then
+                    // crop by the logical display size
+                    displayHandle.mTransform.mapRect(tmpRectF);
+                    tmpRectF.round(tmpRect);
+                    tmpLogicalDisplaySize.set(0, 0, displayHandle.mLogicalSize.getWidth(),
+                            displayHandle.mLogicalSize.getHeight());
+                    tmpRect.intersect(tmpLogicalDisplaySize);
+                    displayFound = true;
+                    break;
+                }
+            }
+
+            if (!displayFound) {
+                ProtoLog.v(WM_DEBUG_TPL, "Skipping %s, no associated display %d", windowHandle.name,
+                        windowHandle.displayId);
+                continue;
+            }
+
             var listeners = mRegisteredListeners.get(windowHandle.getWindowToken());
             if (listeners != null) {
                 Region region = new Region();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index da64a5f..af57c84 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -32,6 +32,7 @@
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.os.UserHandle.USER_NULL;
 import static android.view.SurfaceControl.Transaction;
+import static android.view.WindowInsets.Type.InsetsType;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
@@ -173,6 +174,13 @@
      */
     protected SparseArray<InsetsSourceProvider> mInsetsSourceProviders = null;
 
+    /**
+     * The combined excluded insets types (combined mExcludeInsetsTypes and the
+     * mMergedExcludeInsetsTypes from its parent)
+     */
+    protected @InsetsType int mMergedExcludeInsetsTypes = 0;
+    private @InsetsType int mExcludeInsetsTypes = 0;
+
     @Nullable
     private ArrayMap<IBinder, DeathRecipient> mInsetsOwnerDeathRecipientMap;
 
@@ -555,6 +563,49 @@
         return mControllableInsetProvider;
     }
 
+    /**
+     * Sets the excludeInsetsTypes of this window and updates the mMergedExcludeInsetsTypes of
+     * all child nodes in the hierarchy.
+     *
+     * @param excludeInsetsTypes the excluded {@link InsetsType} that should be set on this
+     *                           WindowContainer
+     */
+    void setExcludeInsetsTypes(@InsetsType int excludeInsetsTypes) {
+        if (excludeInsetsTypes == mExcludeInsetsTypes) {
+            return;
+        }
+        mExcludeInsetsTypes = excludeInsetsTypes;
+        mergeExcludeInsetsTypesAndNotifyInsetsChanged(
+                mParent != null ? mParent.mMergedExcludeInsetsTypes : 0);
+    }
+
+    private void mergeExcludeInsetsTypesAndNotifyInsetsChanged(
+            @InsetsType int excludeInsetsTypesFromParent) {
+        final ArraySet<WindowState> changedWindows = new ArraySet<>();
+        updateMergedExcludeInsetsTypes(excludeInsetsTypesFromParent, changedWindows);
+        if (getDisplayContent() != null) {
+            getDisplayContent().getInsetsStateController().notifyInsetsChanged(changedWindows);
+        }
+    }
+
+    private void updateMergedExcludeInsetsTypes(
+            @InsetsType int excludeInsetsTypesFromParent, ArraySet<WindowState> changedWindows) {
+        final int newMergedExcludeInsetsTypes = mExcludeInsetsTypes | excludeInsetsTypesFromParent;
+        if (newMergedExcludeInsetsTypes == mMergedExcludeInsetsTypes) {
+            return;
+        }
+        mMergedExcludeInsetsTypes = newMergedExcludeInsetsTypes;
+
+        final WindowState win = asWindowState();
+        if (win != null) {
+            changedWindows.add(win);
+        }
+        // Apply to all children
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowContainer<?> child = mChildren.get(i);
+            child.updateMergedExcludeInsetsTypes(mMergedExcludeInsetsTypes, changedWindows);
+        }
+    }
 
     @Override
     final protected WindowContainer getParent() {
@@ -653,6 +704,7 @@
     void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
         super.onParentChanged(newParent, oldParent);
         if (mParent == null) {
+            mergeExcludeInsetsTypesAndNotifyInsetsChanged(0);
             return;
         }
 
@@ -667,6 +719,7 @@
             // new parent.
             reparentSurfaceControl(getSyncTransaction(), mParent.mSurfaceControl);
         }
+        mergeExcludeInsetsTypesAndNotifyInsetsChanged(mParent.mMergedExcludeInsetsTypes);
 
         // Either way we need to ask the parent to assign us a Z-order.
         mParent.assignChildLayers();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e1e4714..ebf645d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6810,30 +6810,36 @@
      * @param logLevel  Determines the amount of data to be written to the Protobuf.
      */
     void dumpDebugLocked(ProtoOutputStream proto, @WindowTracingLogLevel int logLevel) {
-        mPolicy.dumpDebug(proto, POLICY);
-        mRoot.dumpDebug(proto, ROOT_WINDOW_CONTAINER, logLevel);
-        final DisplayContent topFocusedDisplayContent = mRoot.getTopFocusedDisplayContent();
-        if (topFocusedDisplayContent.mCurrentFocus != null) {
-            topFocusedDisplayContent.mCurrentFocus.writeIdentifierToProto(proto, FOCUSED_WINDOW);
-        }
-        if (topFocusedDisplayContent.mFocusedApp != null) {
-            topFocusedDisplayContent.mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
-        }
-        final WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
-        if (imeWindow != null) {
-            imeWindow.writeIdentifierToProto(proto, INPUT_METHOD_WINDOW);
-        }
-        proto.write(DISPLAY_FROZEN, mDisplayFrozen);
-        proto.write(FOCUSED_DISPLAY_ID, topFocusedDisplayContent.getDisplayId());
-        proto.write(HARD_KEYBOARD_AVAILABLE, mHardKeyboardAvailable);
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "dumpDebugLocked");
+        try {
+            mPolicy.dumpDebug(proto, POLICY);
+            mRoot.dumpDebug(proto, ROOT_WINDOW_CONTAINER, logLevel);
+            final DisplayContent topFocusedDisplayContent = mRoot.getTopFocusedDisplayContent();
+            if (topFocusedDisplayContent.mCurrentFocus != null) {
+                topFocusedDisplayContent.mCurrentFocus
+                        .writeIdentifierToProto(proto, FOCUSED_WINDOW);
+            }
+            if (topFocusedDisplayContent.mFocusedApp != null) {
+                topFocusedDisplayContent.mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
+            }
+            final WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
+            if (imeWindow != null) {
+                imeWindow.writeIdentifierToProto(proto, INPUT_METHOD_WINDOW);
+            }
+            proto.write(DISPLAY_FROZEN, mDisplayFrozen);
+            proto.write(FOCUSED_DISPLAY_ID, topFocusedDisplayContent.getDisplayId());
+            proto.write(HARD_KEYBOARD_AVAILABLE, mHardKeyboardAvailable);
 
-        // This is always true for now since we still update the window frames at the server side.
-        // Once we move the window layout to the client side, this can be false when we are waiting
-        // for the frames.
-        proto.write(WINDOW_FRAMES_VALID, true);
+            // This is always true for now since we still update the window frames at the server
+            // side. Once we move the window layout to the client side, this can be false when we
+            // are waiting for the frames.
+            proto.write(WINDOW_FRAMES_VALID, true);
 
-        // Write the BackNavigationController's state into the protocol buffer
-        mAtmService.mBackNavigationController.dumpDebug(proto, BACK_NAVIGATION);
+            // Write the BackNavigationController's state into the protocol buffer
+            mAtmService.mBackNavigationController.dumpDebug(proto, BACK_NAVIGATION);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
     }
 
     private void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 09a2bf9..f8d0bc2 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -67,6 +67,7 @@
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
@@ -1449,6 +1450,16 @@
                 }
                 break;
             }
+            case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES: {
+                final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
+                if (container == null) {
+                    Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+                            + container);
+                    break;
+                }
+                container.setExcludeInsetsTypes(hop.getExcludeInsetsTypes());
+                break;
+            }
         }
         return effects;
     }
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index fe26726..6883437 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -167,12 +167,7 @@
 
             long token = os.start(WINDOW_MANAGER_SERVICE);
             synchronized (mGlobalLock) {
-                Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "dumpDebugLocked");
-                try {
-                    mService.dumpDebugLocked(os, logLevel);
-                } finally {
-                    Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
-                }
+                mService.dumpDebugLocked(os, logLevel);
             }
             os.end(token);
         } catch (Exception e) {
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index a07facf..776de2e 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -712,6 +712,12 @@
                         minOccurs="0" maxOccurs="unbounded">
                 <xs:annotation name="final"/>
             </xs:element>
+            <!-- The time after which the stylus is to be assumed to be not under use. This will
+             enable the logic of changing the brightness with ambient light changes -->
+            <xs:element name="idleStylusTimeoutMillis" type="xs:nonNegativeInteger"
+                minOccurs="0" maxOccurs="1">
+                <xs:annotation name="final"/>
+            </xs:element>
         </xs:sequence>
     </xs:complexType>
 
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 5309263..110a5a2 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -8,12 +8,14 @@
     method public final java.math.BigInteger getDarkeningLightDebounceIdleMillis();
     method public final java.math.BigInteger getDarkeningLightDebounceMillis();
     method public boolean getEnabled();
+    method public final java.math.BigInteger getIdleStylusTimeoutMillis();
     method public final java.util.List<com.android.server.display.config.LuxToBrightnessMapping> getLuxToBrightnessMapping();
     method public final void setBrighteningLightDebounceIdleMillis(java.math.BigInteger);
     method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
     method public final void setDarkeningLightDebounceIdleMillis(java.math.BigInteger);
     method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
     method public void setEnabled(boolean);
+    method public final void setIdleStylusTimeoutMillis(java.math.BigInteger);
   }
 
   public enum AutoBrightnessModeName {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 4e89b85..2be999f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8031,8 +8031,7 @@
                 "DevicePolicyManager.wipeDataWithReason() from %s, organization-owned? %s",
                 adminName, calledByProfileOwnerOnOrgOwnedDevice);
 
-        wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId,
-                calledOnParentInstance, factoryReset);
+        wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId, factoryReset);
     }
 
     private String getGenericWipeReason(
@@ -8188,17 +8187,16 @@
      *                     factory reset
      */
     private void wipeDataNoLock(@Nullable ComponentName admin, int flags, String internalReason,
-            String wipeReasonForUser, int userId, boolean calledOnParentInstance,
-            @Nullable Boolean factoryReset) {
+            String wipeReasonForUser, int userId, @Nullable Boolean factoryReset) {
         wtfIfInLock();
         final String adminPackage;
         if (admin != null) {
             adminPackage = admin.getPackageName();
         } else {
-            int callerId = mInjector.binderGetCallingUid();
-            String[] adminPackages = mInjector.getPackageManager().getPackagesForUid(callerId);
+            int callerUid = mInjector.binderGetCallingUid();
+            String[] adminPackages = mInjector.getPackageManager().getPackagesForUid(callerUid);
             Preconditions.checkState(adminPackages.length > 0,
-                    "Caller %s does not have any associated packages", callerId);
+                    "Caller %s does not have any associated packages", callerUid);
             adminPackage = adminPackages[0];
         }
         mInjector.binderWithCleanCallingIdentity(() -> {
@@ -8220,32 +8218,22 @@
                 throw new SecurityException("Cannot wipe data. " + restriction
                         + " restriction is set for user " + userId);
             }
-        });
 
-        boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
-        boolean isMainUser = userId == getMainUserId();
-        boolean wipeDevice;
-        if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
-                adminPackage,
-                userId)) {
-            // Legacy mode
-            wipeDevice = getHeadlessDeviceOwnerModeForDeviceOwner()
-                    == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER ? isMainUser : isSystemUser;
-        } else {
-            // Explicit behaviour
-            if (factoryReset) {
-                EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
-                        /*admin=*/ null,
-                        /*permission=*/ new String[]{MANAGE_DEVICE_POLICY_WIPE_DATA,
-                                MASTER_CLEAR},
-                        USES_POLICY_WIPE_DATA,
-                        adminPackage,
-                        factoryReset ? UserHandle.USER_ALL :
-                                getAffectedUser(calledOnParentInstance));
-                wipeDevice = true;
+            boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
+            boolean isMainUser = userId == getMainUserId();
+            boolean wipeDevice;
+            if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
+                    adminPackage,
+                    userId)) {
+                // Legacy mode
+                wipeDevice = getHeadlessDeviceOwnerModeForDeviceOwner()
+                        == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER ? isMainUser : isSystemUser;
             } else {
-                mInjector.binderWithCleanCallingIdentity(() -> {
-                    Preconditions.checkCallAuthorization(!isSystemUser,
+                // Explicit behaviour
+                if (factoryReset) {
+                    wipeDevice = true;
+                } else {
+                    Preconditions.checkState(!isSystemUser,
                             "User %s is a system user and cannot be removed", userId);
                     boolean isLastNonHeadlessUser = getUserInfo(userId).isFull()
                             && mUserManager.getAliveUsers().stream()
@@ -8253,13 +8241,11 @@
                             .noneMatch(UserInfo::isFull);
                     Preconditions.checkState(!isLastNonHeadlessUser,
                             "Removing user %s would leave the device without any active users. "
-                                    + "Consider factory resetting the device instead.",
-                            userId);
-                });
-                wipeDevice = false;
+                                    + "Consider factory resetting the device instead.", userId);
+                    wipeDevice = false;
+                }
             }
-        }
-        mInjector.binderWithCleanCallingIdentity(() -> {
+
             if (wipeDevice) {
                 forceWipeDeviceNoLock(
                         (flags & WIPE_EXTERNAL_STORAGE) != 0,
@@ -8600,7 +8586,6 @@
                         /* reason= */ "reportFailedPasswordAttempt()",
                         getFailedPasswordAttemptWipeMessage(),
                         userId,
-                        /* calledOnParentInstance= */ parent,
                         // factoryReset=null to enable U- behaviour
                         /* factoryReset= */ null);
             } catch (SecurityException e) {
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 7d5532f..5c4716d 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -57,7 +57,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
-import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.doReturn
@@ -384,10 +383,6 @@
                     android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)) {
                 PackageManager.PERMISSION_GRANTED
             }
-            whenever(this.checkPermission(
-                eq(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt())) {
-                PackageManager.PERMISSION_GRANTED
-            }
         }
         val mockSharedLibrariesImpl: SharedLibrariesImpl = mock {
             whenever(this.snapshot()) { this@mock }
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
index e33ca77..70a2d48 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
+++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
@@ -45,7 +45,7 @@
 
 import com.android.bedstead.harrier.BedsteadJUnit4;
 import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.multiuser.annotations.EnsureHasSecondaryUser;
 import com.android.bedstead.nene.users.UserReference;
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.TestUtils;
diff --git a/services/tests/appfunctions/Android.bp b/services/tests/appfunctions/Android.bp
index 9560ec9..c841643 100644
--- a/services/tests/appfunctions/Android.bp
+++ b/services/tests/appfunctions/Android.bp
@@ -36,6 +36,7 @@
         "androidx.test.core",
         "androidx.test.runner",
         "androidx.test.ext.truth",
+        "kotlin-test",
         "platform-test-annotations",
         "services.appfunctions",
         "servicestests-core-utils",
diff --git a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
index da3e94f..d326204 100644
--- a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
+++ b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
@@ -15,8 +15,12 @@
  */
 package android.app.appfunctions
 
+import android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DEFAULT
+import android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DISABLED
+import android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED
 import android.app.appsearch.AppSearchSchema
 import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -108,32 +112,43 @@
 
         assertThat(runtimeMetadata.packageName).isEqualTo("com.pkg")
         assertThat(runtimeMetadata.functionId).isEqualTo("funcId")
-        assertThat(runtimeMetadata.enabled).isNull()
+        assertThat(runtimeMetadata.enabled).isEqualTo(APP_FUNCTION_STATE_DEFAULT)
         assertThat(runtimeMetadata.appFunctionStaticMetadataQualifiedId)
             .isEqualTo("android\$apps-db/app_functions#com.pkg/funcId")
     }
 
     @Test
-    fun setEnabled_true() {
+    fun setEnabled_enabled() {
         val runtimeMetadata =
-            AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(true).build()
+            AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(APP_FUNCTION_STATE_ENABLED).build()
 
-        assertThat(runtimeMetadata.enabled).isTrue()
+        assertThat(runtimeMetadata.enabled).isEqualTo(APP_FUNCTION_STATE_ENABLED)
     }
 
     @Test
-    fun setEnabled_false() {
+    fun setEnabled_disabled() {
         val runtimeMetadata =
-            AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(false).build()
+            AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(
+                APP_FUNCTION_STATE_DISABLED).build()
 
-        assertThat(runtimeMetadata.enabled).isFalse()
+        assertThat(runtimeMetadata.enabled).isEqualTo(APP_FUNCTION_STATE_DISABLED)
     }
 
     @Test
-    fun setEnabled_null() {
+    fun setEnabled_default() {
         val runtimeMetadata =
-            AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(null).build()
+            AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(
+                APP_FUNCTION_STATE_DEFAULT).build()
 
-        assertThat(runtimeMetadata.enabled).isNull()
+        assertThat(runtimeMetadata.enabled).isEqualTo(APP_FUNCTION_STATE_DEFAULT)
+    }
+
+    @Test
+    fun setEnabled_illegalArgument() {
+        val runtimeMetadataBuilder =
+            AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId")
+        assertFailsWith<IllegalArgumentException>("Value of EnabledState is unsupported.") {
+            runtimeMetadataBuilder.setEnabled(-1)
+        }
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 8e1be9a..3976ea4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -157,6 +157,7 @@
                 .getIdleScreenRefreshRateTimeoutLuxThresholdPoint());
         assertNull(mDisplayDeviceConfig.getTempSensor().name);
         assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable());
+        assertEquals(0, mDisplayDeviceConfig.getIdleStylusTimeoutMillis());
     }
 
     @Test
@@ -253,6 +254,7 @@
                 .getLux().intValue());
         assertEquals(800, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(1)
                 .getTimeout().intValue());
+        assertEquals(1000, mDisplayDeviceConfig.getIdleStylusTimeoutMillis());
     }
 
     @Test
@@ -1479,6 +1481,7 @@
                 +               "</point>\n"
                 +           "</map>\n"
                 +       "</luxToBrightnessMapping>\n"
+                +       "<idleStylusTimeoutMillis>1000</idleStylusTimeoutMillis>\n"
                 +   "</autoBrightness>\n"
                 +  getPowerThrottlingConfig()
                 +   "<highBrightnessMode enabled=\"true\">\n"
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
index 50f3a88..5bcddc4 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
@@ -1,6 +1,10 @@
 package com.android.server.locksettings;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
 
 import android.platform.test.annotations.Presubmit;
 
@@ -56,4 +60,44 @@
         mService.initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
         assertEquals(Sets.newHashSet(0), mPasswordSlotManager.getUsedSlots());
     }
+
+    private int getNumUsedWeaverSlots() {
+        return mPasswordSlotManager.getUsedSlots().size();
+    }
+
+    @Test
+    public void testDisableWeaverOnUnsecuredUsers_false() {
+        final int userId = PRIMARY_USER_ID;
+        when(mResources.getBoolean(eq(
+                        com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers)))
+                .thenReturn(false);
+        assertEquals(0, getNumUsedWeaverSlots());
+        mService.initializeSyntheticPassword(userId);
+        assertEquals(1, getNumUsedWeaverSlots());
+        assertTrue(mService.setLockCredential(newPassword("password"), nonePassword(), userId));
+        assertEquals(1, getNumUsedWeaverSlots());
+        assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"), userId));
+        assertEquals(1, getNumUsedWeaverSlots());
+    }
+
+    @Test
+    public void testDisableWeaverOnUnsecuredUsers_true() {
+        final int userId = PRIMARY_USER_ID;
+        when(mResources.getBoolean(eq(
+                        com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers)))
+                .thenReturn(true);
+        assertEquals(0, getNumUsedWeaverSlots());
+        mService.initializeSyntheticPassword(userId);
+        assertEquals(0, getNumUsedWeaverSlots());
+        assertTrue(mService.setLockCredential(newPassword("password"), nonePassword(), userId));
+        assertEquals(1, getNumUsedWeaverSlots());
+        assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"), userId));
+        assertEquals(0, getNumUsedWeaverSlots());
+    }
+
+    @Test
+    public void testDisableWeaverOnUnsecuredUsers_defaultsToFalse() {
+        assertFalse(mResources.getBoolean(
+                    com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index ee63d5d..425bb15 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -33,6 +33,7 @@
 import static android.view.Display.INVALID_DISPLAY;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -51,11 +52,15 @@
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
+import android.Manifest;
 import android.annotation.SuppressLint;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions.LaunchCookie;
 import android.app.AppOpsManager;
+import android.app.Instrumentation;
 import android.app.KeyguardManager;
+import android.app.role.RoleManager;
+import android.companion.AssociationRequest;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -68,6 +73,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.test.TestLooper;
@@ -88,6 +94,7 @@
 import com.android.server.wm.WindowManagerInternal;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -98,6 +105,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -312,7 +320,6 @@
         assertThat(mService.getActiveProjectionInfo()).isNotNull();
     }
 
-    @SuppressLint("MissingPermission")
     @EnableFlags(android.companion.virtualdevice.flags
             .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
     @Test
@@ -335,6 +342,36 @@
         assertThat(mService.getActiveProjectionInfo()).isNotNull();
     }
 
+    @EnableFlags(android.companion.virtualdevice.flags
+            .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+    @Test
+    public void testCreateProjection_keyguardLocked_RoleHeld() {
+        runWithRole(AssociationRequest.DEVICE_PROFILE_APP_STREAMING, () -> {
+            try {
+                mAppInfo.privateFlags |= PRIVATE_FLAG_PRIVILEGED;
+                doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(),
+                        any(ApplicationInfoFlags.class), any(UserHandle.class));
+                MediaProjectionManagerService.MediaProjection projection =
+                        mService.createProjectionInternal(Process.myUid(),
+                                mContext.getPackageName(),
+                                TYPE_MIRRORING, /* isPermanentGrant= */ false, UserHandle.CURRENT);
+                doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+                doReturn(PackageManager.PERMISSION_DENIED).when(
+                        mPackageManager).checkPermission(
+                        RECORD_SENSITIVE_CONTENT, projection.packageName);
+
+                projection.start(mIMediaProjectionCallback);
+                projection.notifyVirtualDisplayCreated(10);
+
+                // The projection was started because it was allowed to capture the keyguard.
+                assertWithMessage("Failed to run projection")
+                        .that(mService.getActiveProjectionInfo()).isNotNull();
+            } catch (NameNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+
     @Test
     public void testCreateProjection_attemptReuse_noPriorProjectionGrant()
             throws NameNotFoundException {
@@ -1202,6 +1239,47 @@
         return mService.getProjectionInternal(UID, PACKAGE_NAME);
     }
 
+    /**
+     * Run the provided block giving the current context's package the provided role.
+     */
+    @SuppressWarnings("SameParameterValue")
+    private void runWithRole(String role, Runnable block) {
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        String packageName = mContext.getPackageName();
+        UserHandle user = instrumentation.getTargetContext().getUser();
+        RoleManager roleManager = Objects.requireNonNull(
+                mContext.getSystemService(RoleManager.class));
+        try {
+            CountDownLatch latch = new CountDownLatch(1);
+            instrumentation.getUiAutomation().adoptShellPermissionIdentity(
+                    Manifest.permission.MANAGE_ROLE_HOLDERS,
+                    Manifest.permission.BYPASS_ROLE_QUALIFICATION);
+
+            roleManager.setBypassingRoleQualification(true);
+            roleManager.addRoleHolderAsUser(role, packageName, /*  flags = */ 0, user,
+                    mContext.getMainExecutor(), success -> {
+                        if (success) {
+                            latch.countDown();
+                        } else {
+                            Assert.fail("Couldn't set role for test (failure) " + role);
+                        }
+                    });
+            assertWithMessage("Couldn't set role for test (timeout) : " + role)
+                    .that(latch.await(1, TimeUnit.SECONDS)).isTrue();
+            block.run();
+
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        } finally {
+            roleManager.removeRoleHolderAsUser(role, packageName, 0, user,
+                    mContext.getMainExecutor(), (aBool) -> {
+                    });
+            roleManager.setBypassingRoleQualification(false);
+            instrumentation.getUiAutomation()
+                    .dropShellPermissionIdentity();
+        }
+    }
+
     private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub {
         CountDownLatch mLatch = new CountDownLatch(1);
         @Override
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
index 18ca09b..bf0586c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
@@ -18,11 +18,21 @@
 
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING;
+
+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.assertNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.PendingIntent;
@@ -30,12 +40,16 @@
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.Adjustment;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 
 import com.android.server.UiServiceTestCase;
 
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.ArrayList;
@@ -43,6 +57,9 @@
 
 public class NotificationAdjustmentExtractorTest extends UiServiceTestCase {
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
     @Test
     public void testExtractsAdjustment() {
         NotificationAdjustmentExtractor extractor = new NotificationAdjustmentExtractor();
@@ -111,6 +128,44 @@
         assertEquals(snoozeCriteria, r.getSnoozeCriteria());
     }
 
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_FORCE_GROUPING})
+    public void testClassificationAdjustments_triggerRegrouping() {
+        GroupHelper groupHelper = mock(GroupHelper.class);
+        NotificationAdjustmentExtractor extractor = new NotificationAdjustmentExtractor();
+        extractor.setGroupHelper(groupHelper);
+
+        NotificationRecord r = generateRecord();
+
+        Bundle classificationAdj = new Bundle();
+        classificationAdj.putParcelable(Adjustment.KEY_TYPE, mock(NotificationChannel.class));
+        Adjustment adjustment = new Adjustment("pkg", r.getKey(), classificationAdj, "", 0);
+        r.addAdjustment(adjustment);
+
+        RankingReconsideration regroupingTask = extractor.process(r);
+        assertThat(regroupingTask).isNotNull();
+        regroupingTask.applyChangesLocked(r);
+        verify(groupHelper, times(1)).onChannelUpdated(r);
+    }
+
+    @Test
+    @DisableFlags({FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_FORCE_GROUPING})
+    public void testClassificationAdjustments_notTriggerRegrouping_flagsDisabled() {
+        GroupHelper groupHelper = mock(GroupHelper.class);
+        NotificationAdjustmentExtractor extractor = new NotificationAdjustmentExtractor();
+        extractor.setGroupHelper(groupHelper);
+
+        NotificationRecord r = generateRecord();
+
+        Bundle classificationAdj = new Bundle();
+        classificationAdj.putParcelable(Adjustment.KEY_TYPE, mock(NotificationChannel.class));
+        Adjustment adjustment = new Adjustment("pkg", r.getKey(), classificationAdj, "", 0);
+        r.addAdjustment(adjustment);
+
+        RankingReconsideration regroupingTask = extractor.process(r);
+        assertThat(regroupingTask).isNull();
+    }
+
     private NotificationRecord generateRecord() {
         NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
         final Notification.Builder builder = new Notification.Builder(getContext())
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 45cd571..03cad24 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -2637,6 +2637,63 @@
     }
 
     @Test
+    public void testBeepVolume_politeNotif_AvalancheStrategy_exempt_msgCategory() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        initAttentionHelper(flagResolver);
+
+        // Trigger avalanche trigger intent
+        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.putExtra("state", false);
+        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
+        // Create a conversation group with GROUP_ALERT_SUMMARY behavior
+        // Where the summary is not MessagingStyle
+        final String groupKey = "grup_name";
+        final String shortcutId = "shortcut";
+        NotificationRecord summary = getBeepyNotificationRecord(groupKey, GROUP_ALERT_SUMMARY);
+        summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+        summary.getNotification().category = Notification.CATEGORY_MESSAGE;
+        ShortcutInfo.Builder sb = new ShortcutInfo.Builder(getContext());
+        summary.setShortcutInfo(sb.setId(shortcutId).build());
+
+        // Should beep at 100% volume
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        verifyBeepVolume(1.0f);
+        assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+        Mockito.reset(mRingtonePlayer);
+
+        // Post child notifications with GROUP_ALERT_SUMMARY
+        NotificationRecord child = getConversationNotificationRecord(mId, false /* insistent */,
+                false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true,
+                true, false, groupKey, Notification.GROUP_ALERT_SUMMARY, false, mUser, mPkg,
+                shortcutId);
+
+        // Should not beep
+        mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertFalse(child.isInterruptive());
+        assertEquals(-1, child.getLastAudiblyAlertedMs());
+        Mockito.reset(mRingtonePlayer);
+
+        // 2nd update for summary should beep at 50% volume
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        verifyBeepVolume(0.5f);
+        assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+        Mockito.reset(mRingtonePlayer);
+
+        // 3rd update for summary should not beep
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertFalse(summary.isInterruptive());
+        assertEquals(-1, summary.getLastAudiblyAlertedMs());
+    }
+
+    @Test
     public void testBeepVolume_politeNotif_exemptEmergency() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
         mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 3c120e1..1349ee0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3206,7 +3206,6 @@
         // Send two cancelations.
         mBinderService.cancelNotificationWithTag(mPkg, mPkg, sbn.getTag(), sbn.getId(),
                 sbn.getUserId());
-        waitForIdle();
         mBinderService.cancelNotificationWithTag(mPkg, mPkg, sbn.getTag(), sbn.getId(),
                 sbn.getUserId());
         waitForIdle();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 9a6e818..5d4382a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -92,6 +92,7 @@
     @Mock ZenModeHelper mMockZenModeHelper;
     @Mock RankingConfig mConfig;
     @Mock Vibrator mVibrator;
+    @Mock GroupHelper mGroupHelper;
 
     private NotificationManager.Policy mTestNotificationPolicy;
     private Notification mNotiGroupGSortA;
@@ -157,7 +158,7 @@
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper,
                 mUsageStats, new String[] {ImportanceExtractor.class.getName()},
-                mock(IPlatformCompat.class));
+                mock(IPlatformCompat.class), mGroupHelper);
 
         mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setContentTitle("A")
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index b997f5d..a49f5a8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -26,8 +26,6 @@
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES;
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
 import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
-import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
 import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
 import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
@@ -57,7 +55,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.server.UiServiceTestCase;
 
@@ -188,49 +185,6 @@
     }
 
     @Test
-    public void testSuppressDNDInfo_yes_VisEffectsAllowed() {
-        NotificationRecord r = getNotificationRecord();
-        when(r.getSbn().getPackageName()).thenReturn("android");
-        when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
-        Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects()
-                - SUPPRESSED_EFFECT_STATUS_BAR, 0);
-
-        assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
-    }
-
-    @Test
-    public void testSuppressDNDInfo_yes_WrongId() {
-        NotificationRecord r = getNotificationRecord();
-        when(r.getSbn().getPackageName()).thenReturn("android");
-        when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ACCOUNT_CREDENTIAL_PERMISSION);
-        Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
-
-        assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
-    }
-
-    @Test
-    public void testSuppressDNDInfo_yes_WrongPackage() {
-        NotificationRecord r = getNotificationRecord();
-        when(r.getSbn().getPackageName()).thenReturn("android2");
-        when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
-        Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
-
-        assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
-    }
-
-    @Test
-    public void testSuppressDNDInfo_no() {
-        NotificationRecord r = getNotificationRecord();
-        when(r.getSbn().getPackageName()).thenReturn("android");
-        when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
-        Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
-
-        assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
-        assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_ALARMS, policy, r));
-        assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_NO_INTERRUPTIONS, policy, r));
-    }
-
-    @Test
     public void testSuppressAnything_yes_ZenModeOff() {
         NotificationRecord r = getNotificationRecord();
         when(r.getSbn().getPackageName()).thenReturn("bananas");
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index d4cba8d..294027b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -169,7 +169,6 @@
 
 import com.android.internal.R;
 import com.android.internal.config.sysui.TestableFlagResolver;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.os.AtomsProto;
@@ -747,54 +746,6 @@
     }
 
     @Test
-    public void testZenUpgradeNotification() {
-        /**
-         * Commit a485ec65b5ba947d69158ad90905abf3310655cf disabled DND status change
-         * notification on watches. So, assume that the device is not watch.
-         */
-        when(mContext.getPackageManager()).thenReturn(mPackageManager);
-        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(false);
-
-        // shows zen upgrade notification if stored settings says to shows,
-        // zen has not been updated, boot is completed
-        // and we're setting zen mode on
-        Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1);
-        Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
-        mZenModeHelper.mIsSystemServicesReady = true;
-        mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
-        mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
-        verify(mNotificationManager, times(1)).notify(eq(ZenModeHelper.TAG),
-                eq(SystemMessage.NOTE_ZEN_UPGRADE), any());
-        assertEquals(0, Settings.Secure.getInt(mContentResolver,
-                Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, -1));
-    }
-
-    @Test
-    public void testNoZenUpgradeNotification() {
-        // doesn't show upgrade notification if stored settings says don't show
-        Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
-        Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
-        mZenModeHelper.mIsSystemServicesReady = true;
-        mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
-        verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG),
-                eq(SystemMessage.NOTE_ZEN_UPGRADE), any());
-    }
-
-    @Test
-    public void testNoZenUpgradeNotificationZenUpdated() {
-        // doesn't show upgrade notification since zen was already updated
-        Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
-        Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 1);
-        mZenModeHelper.mIsSystemServicesReady = true;
-        mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
-        verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG),
-                eq(SystemMessage.NOTE_ZEN_UPGRADE), any());
-    }
-
-    @Test
     public void testZenSetInternalRinger_AllPriorityNotificationSoundsMuted() {
         AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class);
         mZenModeHelper.mAudioManager = mAudioManager;
@@ -3032,6 +2983,33 @@
     }
 
     @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+    public void updateAutomaticZenRule_withTypeBedtime_replacesDisabledSleeping() {
+        ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
+        sleepingRule.enabled = false;
+        sleepingRule.userModifiedFields = 0;
+        sleepingRule.name = "ZZZZZZZ...";
+        mZenModeHelper.mConfig.automaticRules.clear();
+        mZenModeHelper.mConfig.automaticRules.put(sleepingRule.id, sleepingRule);
+
+        AutomaticZenRule futureBedtime = new AutomaticZenRule.Builder("Bedtime (?)", CONDITION_ID)
+                .build();
+        String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(mPkg, futureBedtime,
+                ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.mConfig.automaticRules.keySet())
+                .containsExactly(sleepingRule.id, bedtimeRuleId);
+
+        AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime (!)", CONDITION_ID)
+                .setType(TYPE_BEDTIME)
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(bedtimeRuleId, bedtime, ORIGIN_APP, "reason",
+                CUSTOM_PKG_UID);
+
+        assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(bedtimeRuleId);
+    }
+
+    @Test
     @EnableFlags(FLAG_MODES_API)
     public void testSetManualZenMode() {
         setupZenConfig();
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 e83a4b2..7536f5f 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -47,7 +47,6 @@
 import android.hardware.vibrator.IVibratorManager;
 import android.os.CombinedVibration;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.PersistableBundle;
 import android.os.PowerManager;
 import android.os.Process;
@@ -119,7 +118,6 @@
     @Mock private PackageManagerInternal mPackageManagerInternalMock;
     @Mock private VibrationThread.VibratorManagerHooks mManagerHooks;
     @Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
-    @Mock private IBinder mVibrationToken;
     @Mock private VibrationConfig mVibrationConfigMock;
     @Mock private VibratorFrameworkStatsLogger mStatsLoggerMock;
 
@@ -668,7 +666,7 @@
         VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
         HalVibration vibration = createVibration(CombinedVibration.createParallel(
                 VibrationEffect.get(VibrationEffect.EFFECT_CLICK)));
-        vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback);
+        vibration.fillFallbacks(unused -> fallback);
         startThreadAndDispatcher(vibration);
         waitForCompletion();
 
@@ -848,7 +846,7 @@
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
                 .compose();
         HalVibration vibration = createVibration(CombinedVibration.createParallel(effect));
-        vibration.addFallback(VibrationEffect.EFFECT_TICK, fallback);
+        vibration.fillFallbacks(unused -> fallback);
         startThreadAndDispatcher(vibration);
         waitForCompletion();
 
@@ -954,7 +952,8 @@
         assertTrue(mThread.isRunningVibrationId(vibration.id));
         assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
 
-        mVibrationConductor.binderDied();
+        mVibrationConductor.notifyCancelled(
+                new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED), /* immediate= */ false);
         waitForCompletion();
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
@@ -1575,7 +1574,8 @@
                 TEST_TIMEOUT_MILLIS));
         assertTrue(mThread.isRunningVibrationId(vibration.id));
 
-        mVibrationConductor.binderDied();
+        mVibrationConductor.notifyCancelled(
+                new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED), /* immediate= */ false);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibration, Status.CANCELLED_BINDER_DIED);
@@ -1865,9 +1865,9 @@
         VibrationAttributes attrs = new VibrationAttributes.Builder()
                 .setUsage(usage)
                 .build();
-        HalVibration vib = new HalVibration(mVibrationToken,
-                CombinedVibration.createParallel(effect),
-                new CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
+        HalVibration vib = new HalVibration(
+                new CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"),
+                CombinedVibration.createParallel(effect));
         return startThreadAndDispatcher(vib, requestVibrationParamsFuture);
     }
 
@@ -1903,8 +1903,8 @@
     }
 
     private HalVibration createVibration(CombinedVibration effect) {
-        return new HalVibration(mVibrationToken, effect,
-                new CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
+        return new HalVibration(new CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason"),
+                effect);
     }
 
     private SparseArray<VibratorController> createVibratorControllers() {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
index cd057b6..1d1b4e2 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
@@ -115,6 +115,6 @@
     }
 
     private static VibrationStats.StatsInfo newEmptyStatsInfo() {
-        return new VibrationStats.StatsInfo(0, 0, 0, Status.FINISHED, new VibrationStats(), 0L);
+        return new VibrationStats.StatsInfo(0, 0, 0, Status.FINISHED, new VibrationStats());
     }
 }
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 538c3fc..b782162 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -1791,28 +1791,6 @@
     }
 
     @Test
-    public void performHapticFeedback_usesServiceAsToken() throws Exception {
-        VibratorManagerService service = createSystemReadyService();
-
-        HalVibration vibration =
-                performHapticFeedbackAndWaitUntilFinished(
-                        service, HapticFeedbackConstants.SCROLL_TICK, /* always= */ true);
-
-        assertTrue(vibration.callerToken == service);
-    }
-
-    @Test
-    public void performHapticFeedbackForInputDevice_usesServiceAsToken() throws Exception {
-        VibratorManagerService service = createSystemReadyService();
-
-        HalVibration vibration = performHapticFeedbackForInputDeviceAndWaitUntilFinished(
-                service, HapticFeedbackConstants.SCROLL_TICK, /* inputDeviceId= */ 0,
-                InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
-
-        assertTrue(vibration.callerToken == service);
-    }
-
-    @Test
     @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
     public void vibrate_vendorEffectsWithoutPermission_doesNotVibrate() throws Exception {
         // Deny permission to vibrate with vendor effects
@@ -2147,6 +2125,27 @@
     }
 
     @Test
+    public void cancelVibrate_externalVibration_cancelWithDifferentToken() {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        createSystemReadyService();
+
+        IBinder vibrationBinderToken = mock(IBinder.class);
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_ALARM_ATTRS,
+                mock(IExternalVibrationController.class), vibrationBinderToken);
+        ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+                externalVibration);
+
+        IBinder cancelBinderToken = mock(IBinder.class);
+        mService.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, cancelBinderToken);
+
+        assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+        assertEquals(Arrays.asList(false, true, false),
+                mVibratorProviders.get(1).getExternalControlStates());
+    }
+
+    @Test
     public void onExternalVibration_ignoreVibrationFromVirtualDevices() {
         mockVibrators(1);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 65736cb..c8a3559 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -179,17 +179,25 @@
                 .getAppCompatAspectRatioPolicy()).isLetterboxedForFixedOrientationAndAspectRatio();
     }
 
-    void enableTreatmentForTopActivity(boolean enabled) {
-        doReturn(enabled).when(mDisplayContent.mAppCompatCameraPolicy)
-                .isTreatmentEnabledForActivity(eq(mActivityStack.top()));
+    void enableFullscreenCameraCompatTreatmentForTopActivity(boolean enabled) {
+        if (mDisplayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
+            doReturn(enabled).when(
+                    mDisplayContent.mAppCompatCameraPolicy.mDisplayRotationCompatPolicy)
+                        .isTreatmentEnabledForActivity(eq(mActivityStack.top()));
+        }
     }
 
-    void setTopActivityCameraActive(boolean enabled) {
+    void setIsCameraRunningAndWindowingModeEligibleFullscreen(boolean enabled) {
         doReturn(enabled).when(getTopDisplayRotationCompatPolicy())
                 .isCameraRunningAndWindowingModeEligible(eq(mActivityStack.top()),
                         /* mustBeFullscreen= */ eq(true));
     }
 
+    void setIsCameraRunningAndWindowingModeEligibleFreeform(boolean enabled) {
+        doReturn(enabled).when(getTopCameraCompatFreeformPolicy())
+                .isCameraRunningAndWindowingModeEligible(eq(mActivityStack.top()));
+    }
+
     void setTopActivityEligibleForOrientationOverride(boolean enabled) {
         doReturn(enabled).when(getTopDisplayRotationCompatPolicy())
                 .isActivityEligibleForOrientationOverride(eq(mActivityStack.top()));
@@ -508,8 +516,13 @@
     }
 
     private DisplayRotationCompatPolicy getTopDisplayRotationCompatPolicy() {
-        return mActivityStack.top().mDisplayContent
-                .mAppCompatCameraPolicy.mDisplayRotationCompatPolicy;
+        return mActivityStack.top().mDisplayContent.mAppCompatCameraPolicy
+                .mDisplayRotationCompatPolicy;
+    }
+
+    private CameraCompatFreeformPolicy getTopCameraCompatFreeformPolicy() {
+        return mActivityStack.top().mDisplayContent.mAppCompatCameraPolicy
+                .mCameraCompatFreeformPolicy;
     }
 
     // We add the activity to the stack and spyOn() on its properties.
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
index 1e40aa0..b839113 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
@@ -282,7 +282,8 @@
             robot.activity().createActivityWithComponentInNewTaskAndDisplay();
             robot.checkFixedOrientationLetterboxAspectRatioForTopParent(/* expected */ 1.5f);
 
-            robot.activity().enableTreatmentForTopActivity(/* enabled */ true);
+            robot.activity().enableFullscreenCameraCompatTreatmentForTopActivity(
+                    /* enabled */ true);
             robot.checkAspectRatioForTopParentIsSplitScreenRatio(/* expected */ true);
         });
     }
@@ -308,6 +309,12 @@
         void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
             super.onPostDisplayContentCreation(displayContent);
             spyOn(displayContent.mAppCompatCameraPolicy);
+            if (displayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
+                spyOn(displayContent.mAppCompatCameraPolicy.mDisplayRotationCompatPolicy);
+            }
+            if (displayContent.mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()) {
+                spyOn(displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy);
+            }
         }
 
         @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
index 41102d6..9b9040b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
@@ -20,6 +20,8 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.AppCompatCameraPolicy.isTreatmentEnabledForActivity;
+import static com.android.server.wm.AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera;
 import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
 
 import static org.junit.Assert.assertEquals;
@@ -194,9 +196,10 @@
     @Test
     public void testIsCameraCompatTreatmentActive_whenTreatmentForTopActivityIsEnabled() {
         runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
             robot.applyOnActivity((a)-> {
-                a.createActivityWithComponent();
-                a.enableTreatmentForTopActivity(/* enabled */ true);
+                a.createActivityWithComponentInNewTaskAndDisplay();
+                a.enableFullscreenCameraCompatTreatmentForTopActivity(/* enabled */ true);
             });
 
             robot.checkIsCameraCompatTreatmentActiveForTopActivity(/* active */ true);
@@ -206,9 +209,10 @@
     @Test
     public void testIsCameraCompatTreatmentNotActive_whenTreatmentForTopActivityIsDisabled() {
         runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
             robot.applyOnActivity((a)-> {
                 a.createActivityWithComponent();
-                a.enableTreatmentForTopActivity(/* enabled */ false);
+                a.enableFullscreenCameraCompatTreatmentForTopActivity(/* enabled */ false);
             });
 
             robot.checkIsCameraCompatTreatmentActiveForTopActivity(/* active */ false);
@@ -220,9 +224,10 @@
     public void testShouldOverrideMinAspectRatioForCamera_whenCameraIsNotRunning() {
         runTestScenario((robot) -> {
             robot.applyOnActivity((a)-> {
+                robot.allowEnterDesktopMode(true);
                 robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
                 a.createActivityWithComponentInNewTaskAndDisplay();
-                a.setTopActivityCameraActive(/* active */ false);
+                a.setIsCameraRunningAndWindowingModeEligibleFullscreen(/* enabled */ false);
             });
 
             robot.checkShouldOverrideMinAspectRatioForCamera(/* active */ false);
@@ -234,9 +239,10 @@
     public void testShouldOverrideMinAspectRatioForCamera_whenCameraIsRunning_overrideDisabled() {
         runTestScenario((robot) -> {
             robot.applyOnActivity((a)-> {
+                robot.allowEnterDesktopMode(true);
                 robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
                 a.createActivityWithComponentInNewTaskAndDisplay();
-                a.setTopActivityCameraActive(/* active */ true);
+                a.setIsCameraRunningAndWindowingModeEligibleFullscreen(/* active */ true);
             });
 
             robot.checkShouldOverrideMinAspectRatioForCamera(/* active */ false);
@@ -245,12 +251,28 @@
 
     @Test
     @EnableCompatChanges(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA)
-    public void testShouldOverrideMinAspectRatioForCamera_whenCameraIsRunning_overrideEnabled() {
+    public void testShouldOverrideMinAspectRatioForCameraFullscr_cameraIsRunning_overrideEnabled() {
         runTestScenario((robot) -> {
             robot.applyOnActivity((a)-> {
                 robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
                 a.createActivityWithComponentInNewTaskAndDisplay();
-                a.setTopActivityCameraActive(/* active */ true);
+                a.setIsCameraRunningAndWindowingModeEligibleFullscreen(/* active */ true);
+            });
+
+            robot.checkShouldOverrideMinAspectRatioForCamera(/* active */ true);
+        });
+    }
+
+
+    @Test
+    @EnableCompatChanges(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA)
+    @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    public void testShouldOverrideMinAspectRatioForCameraFreeform_cameraRunning_overrideEnabled() {
+        runTestScenario((robot) -> {
+            robot.applyOnActivity((a)-> {
+                robot.allowEnterDesktopMode(true);
+                a.createActivityWithComponentInNewTaskAndDisplay();
+                a.setIsCameraRunningAndWindowingModeEligibleFreeform(/* active */ true);
             });
 
             robot.checkShouldOverrideMinAspectRatioForCamera(/* active */ true);
@@ -318,13 +340,11 @@
         }
 
         void checkIsCameraCompatTreatmentActiveForTopActivity(boolean active) {
-            assertEquals(getTopAppCompatCameraPolicy()
-                    .isTreatmentEnabledForActivity(activity().top()), active);
+            assertEquals(active, isTreatmentEnabledForActivity(activity().top()));
         }
 
         void checkShouldOverrideMinAspectRatioForCamera(boolean expected) {
-            assertEquals(getTopAppCompatCameraPolicy()
-                    .shouldOverrideMinAspectRatioForCamera(activity().top()), expected);
+            assertEquals(expected, shouldOverrideMinAspectRatioForCamera(activity().top()));
         }
 
         // TODO(b/350460645): Create Desktop Windowing Robot to reuse common functionalities.
@@ -332,9 +352,5 @@
             doReturn(isAllowed).when(() ->
                     DesktopModeHelper.canEnterDesktopMode(any()));
         }
-
-        private AppCompatCameraPolicy getTopAppCompatCameraPolicy() {
-            return activity().top().mDisplayContent.mAppCompatCameraPolicy;
-        }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
index 9057b6c..76101d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
@@ -37,14 +37,18 @@
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
 
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.annotation.NonNull;
@@ -321,7 +325,22 @@
             });
             robot.applyOnActivity((a) -> {
                 a.createActivityWithComponentInNewTaskAndDisplay();
-                a.setTopActivityCameraActive(false);
+                a.setIsCameraRunningAndWindowingModeEligibleFullscreen(false);
+            });
+
+            robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT,
+                    /* expected */ SCREEN_ORIENTATION_PORTRAIT);
+        });
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    public void testOverrideOrientationIfNeeded_fullscrOverrideFreeform_cameraActivity_unchanged() {
+        runTestScenario((robot) -> {
+            robot.applyOnActivity((a) -> {
+                robot.allowEnterDesktopMode(true);
+                a.createActivityWithComponentInNewTaskAndDisplay();
+                a.setIsCameraRunningAndWindowingModeEligibleFreeform(false);
             });
 
             robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT,
@@ -426,8 +445,8 @@
                 c.enablePolicyForIgnoringRequestedOrientation(true);
             });
             robot.applyOnActivity((a) -> {
-                a.createActivityWithComponentInNewTask();
-                a.enableTreatmentForTopActivity(true);
+                a.createActivityWithComponentInNewTaskAndDisplay();
+                a.enableFullscreenCameraCompatTreatmentForTopActivity(true);
             });
             robot.prepareRelaunchingAfterRequestedOrientationChanged(false);
 
@@ -591,5 +610,11 @@
         private AppCompatOrientationPolicy getTopAppCompatOrientationPolicy() {
             return activity().top().mAppCompatController.getOrientationPolicy();
         }
+
+        // TODO(b/350460645): Create Desktop Windowing Robot to reuse common functionalities.
+        void allowEnterDesktopMode(boolean isAllowed) {
+            doReturn(isAllowed).when(() ->
+                    DesktopModeHelper.canEnterDesktopMode(any()));
+        }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 401964c..1fa6578 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -23,6 +23,10 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.WindowInsets.Type.captionBar;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowInsets.Type.systemOverlays;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -36,6 +40,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -80,6 +85,8 @@
 import android.os.ShellCallback;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.ArraySet;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
 import android.view.InsetsFrameProvider;
@@ -103,6 +110,7 @@
 import java.io.FileDescriptor;
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.List;
 import java.util.NoSuchElementException;
 
 
@@ -967,7 +975,7 @@
         Rect insetsRect = new Rect(0, 200, 1080, 700);
         final int flags = FLAG_FORCE_CONSUMING;
         final InsetsFrameProvider provider =
-                new InsetsFrameProvider(owner, 1, WindowInsets.Type.captionBar())
+                new InsetsFrameProvider(owner, 1, captionBar())
                         .setArbitraryRectangle(insetsRect)
                         .setFlags(flags);
         task.addLocalInsetsFrameProvider(provider, owner);
@@ -1678,6 +1686,178 @@
         assertFalse("The source must be removed.", hasLocalSource(task, provider.getId()));
     }
 
+    @Test
+    public void testSetExcludeInsetsTypes_appliedOnNodeAndChildren() {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
+        final TestWindowContainer root = builder.setLayer(0).build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+        final TestWindowContainer child2 = root.addChildWindow();
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child12 = child1.addChildWindow();
+        final TestWindowContainer child21 = child2.addChildWindow();
+
+        assertEquals(0, root.mMergedExcludeInsetsTypes);
+        assertEquals(0, child1.mMergedExcludeInsetsTypes);
+        assertEquals(0, child11.mMergedExcludeInsetsTypes);
+        assertEquals(0, child12.mMergedExcludeInsetsTypes);
+        assertEquals(0, child2.mMergedExcludeInsetsTypes);
+        assertEquals(0, child21.mMergedExcludeInsetsTypes);
+
+        child1.setExcludeInsetsTypes(ime());
+        assertEquals(0, root.mMergedExcludeInsetsTypes);
+        assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+        assertEquals(ime(), child11.mMergedExcludeInsetsTypes);
+        assertEquals(ime(), child12.mMergedExcludeInsetsTypes);
+        assertEquals(0, child2.mMergedExcludeInsetsTypes);
+        assertEquals(0, child21.mMergedExcludeInsetsTypes);
+
+        child11.setExcludeInsetsTypes(navigationBars());
+        assertEquals(0, root.mMergedExcludeInsetsTypes);
+        assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+        assertEquals(ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+        assertEquals(ime(), child12.mMergedExcludeInsetsTypes);
+        assertEquals(0, child2.mMergedExcludeInsetsTypes);
+        assertEquals(0, child21.mMergedExcludeInsetsTypes);
+
+        // overwriting the same value has no change
+        for (int i = 0; i < 2; i++) {
+            root.setExcludeInsetsTypes(statusBars());
+            assertEquals(statusBars(), root.mMergedExcludeInsetsTypes);
+            assertEquals(statusBars() | ime(), child1.mMergedExcludeInsetsTypes);
+            assertEquals(statusBars() | ime() | navigationBars(),
+                    child11.mMergedExcludeInsetsTypes);
+            assertEquals(statusBars() | ime(), child12.mMergedExcludeInsetsTypes);
+            assertEquals(statusBars(), child2.mMergedExcludeInsetsTypes);
+            assertEquals(statusBars(), child21.mMergedExcludeInsetsTypes);
+        }
+
+        // set and reset type statusBars on child. Should have no effect because of parent
+        child2.setExcludeInsetsTypes(statusBars());
+        assertEquals(statusBars(), root.mMergedExcludeInsetsTypes);
+        assertEquals(statusBars() | ime(), child1.mMergedExcludeInsetsTypes);
+        assertEquals(statusBars() | ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+        assertEquals(statusBars() | ime(), child12.mMergedExcludeInsetsTypes);
+        assertEquals(statusBars(), child2.mMergedExcludeInsetsTypes);
+        assertEquals(statusBars(), child21.mMergedExcludeInsetsTypes);
+
+        // reset
+        child2.setExcludeInsetsTypes(0);
+        assertEquals(statusBars(), root.mMergedExcludeInsetsTypes);
+        assertEquals(statusBars() | ime(), child1.mMergedExcludeInsetsTypes);
+        assertEquals(statusBars() | ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+        assertEquals(statusBars() | ime(), child12.mMergedExcludeInsetsTypes);
+        assertEquals(statusBars(), child2.mMergedExcludeInsetsTypes);
+        assertEquals(statusBars(), child21.mMergedExcludeInsetsTypes);
+
+        // when parent has statusBars also removed, it should be cleared from all children in the
+        // hierarchy
+        root.setExcludeInsetsTypes(0);
+        assertEquals(0, root.mMergedExcludeInsetsTypes);
+        assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+        assertEquals(ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+        assertEquals(ime(), child12.mMergedExcludeInsetsTypes);
+        assertEquals(0, child2.mMergedExcludeInsetsTypes);
+        assertEquals(0, child21.mMergedExcludeInsetsTypes);
+
+        // change on node should have no effect on siblings
+        child12.setExcludeInsetsTypes(captionBar());
+        assertEquals(0, root.mMergedExcludeInsetsTypes);
+        assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+        assertEquals(ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+        assertEquals(captionBar() | ime(), child12.mMergedExcludeInsetsTypes);
+        assertEquals(0, child2.mMergedExcludeInsetsTypes);
+        assertEquals(0, child21.mMergedExcludeInsetsTypes);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+    public void testSetExcludeInsetsTypes_appliedAfterReparenting() {
+        final SurfaceControl mockSurfaceControl = mock(SurfaceControl.class);
+        final DisplayContent mockDisplayContent = mock(DisplayContent.class);
+        final var mockInsetsStateController = mock(InsetsStateController.class);
+        doReturn(mockInsetsStateController).when(mockDisplayContent).getInsetsStateController();
+
+        final WindowContainer root1 = createWindowContainerSpy(mockSurfaceControl,
+                mockDisplayContent);
+        final WindowContainer root2 = createWindowContainerSpy(mockSurfaceControl,
+                mockDisplayContent);
+        final WindowContainer child = createWindowContainerSpy(mockSurfaceControl,
+                mockDisplayContent);
+        doNothing().when(child).onConfigurationChanged(any());
+
+        root1.setExcludeInsetsTypes(ime());
+        root2.setExcludeInsetsTypes(captionBar());
+        assertEquals(ime(), root1.mMergedExcludeInsetsTypes);
+        assertEquals(captionBar(), root2.mMergedExcludeInsetsTypes);
+        assertEquals(0, child.mMergedExcludeInsetsTypes);
+        clearInvocations(mockInsetsStateController);
+
+        root1.addChild(child, 0);
+        assertEquals(ime(), child.mMergedExcludeInsetsTypes);
+        verify(mockInsetsStateController).notifyInsetsChanged(
+                new ArraySet<>(List.of(child.asWindowState())));
+        clearInvocations(mockInsetsStateController);
+
+        // Make sure that reparenting does not call notifyInsetsChanged twice
+        child.reparent(root2, 0);
+        assertEquals(captionBar(), child.mMergedExcludeInsetsTypes);
+        verify(mockInsetsStateController).notifyInsetsChanged(
+                new ArraySet<>(List.of(child.asWindowState())));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+    public void testSetExcludeInsetsTypes_notifyInsetsAfterChange() {
+        final var mockDisplayContent = mock(DisplayContent.class);
+        final var mockInsetsStateController = mock(InsetsStateController.class);
+        doReturn(mockInsetsStateController).when(mockDisplayContent).getInsetsStateController();
+
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
+        final WindowState mockRootWs = mock(WindowState.class);
+        final TestWindowContainer root = builder.setLayer(0).setAsWindowState(mockRootWs).build();
+        root.mDisplayContent = mockDisplayContent;
+        verify(mockInsetsStateController, never()).notifyInsetsChanged(any());
+
+        root.setExcludeInsetsTypes(ime());
+        assertEquals(ime(), root.mMergedExcludeInsetsTypes);
+        verify(mockInsetsStateController).notifyInsetsChanged(
+                new ArraySet<>(List.of(root.asWindowState())));
+        clearInvocations(mockInsetsStateController);
+
+        // adding a child (while parent has set excludedInsetsTypes) should trigger
+        // notifyInsetsChanged
+        final WindowState mockChildWs = mock(WindowState.class);
+        final TestWindowContainer child1 = builder.setLayer(0).setAsWindowState(
+                mockChildWs).build();
+        child1.mDisplayContent = mockDisplayContent;
+        root.addChildWindow(child1);
+        // TestWindowContainer overrides onParentChanged and therefore doesn't call into
+        // mergeExcludeInsetsTypesAndNotifyInsetsChanged. This is checked in another test
+        assertTrue(child1.mOnParentChangedCalled);
+        child1.setExcludeInsetsTypes(ime());
+        assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+        verify(mockInsetsStateController).notifyInsetsChanged(
+                new ArraySet<>(List.of(child1.asWindowState())));
+        clearInvocations(mockInsetsStateController);
+
+        // not changing excludedInsetsTypes should not trigger notifyInsetsChanged again
+        root.setExcludeInsetsTypes(ime());
+        assertEquals(ime(), root.mMergedExcludeInsetsTypes);
+        assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+        verify(mockInsetsStateController, never()).notifyInsetsChanged(any());
+    }
+
+    private WindowContainer<?> createWindowContainerSpy(SurfaceControl mockSurfaceControl,
+            DisplayContent mockDisplayContent) {
+        final WindowContainer<?> wc = spy(new WindowContainer<>(mWm));
+        final WindowState mocWs = mock(WindowState.class);
+        doReturn(mocWs).when(wc).asWindowState();
+        wc.mSurfaceControl = mockSurfaceControl;
+        wc.mDisplayContent = mockDisplayContent;
+        return wc;
+    }
+
     private static boolean hasLocalSource(WindowContainer container, int sourceId) {
         if (container.mLocalInsetsSources == null) {
             return false;
@@ -1693,6 +1873,7 @@
         private boolean mFillsParent;
         private boolean mWaitForTransitStart;
         private Integer mOrientation;
+        private WindowState mWindowState;
 
         private boolean mOnParentChangedCalled;
         private boolean mOnDescendantOverrideCalled;
@@ -1714,7 +1895,7 @@
         };
 
         TestWindowContainer(WindowManagerService wm, int layer, boolean isAnimating,
-                boolean isVisible, boolean waitTransitStart, Integer orientation) {
+                boolean isVisible, boolean waitTransitStart, Integer orientation, WindowState ws) {
             super(wm);
 
             mLayer = layer;
@@ -1723,6 +1904,7 @@
             mFillsParent = true;
             mOrientation = orientation;
             mWaitForTransitStart = waitTransitStart;
+            mWindowState = ws;
             spyOn(mSurfaceAnimator);
             doReturn(mIsAnimating).when(mSurfaceAnimator).isAnimating();
             doReturn(ANIMATION_TYPE_APP_TRANSITION).when(mSurfaceAnimator).getAnimationType();
@@ -1790,6 +1972,11 @@
         boolean isWaitingForTransitionStart() {
             return mWaitForTransitStart;
         }
+
+        @Override
+        WindowState asWindowState() {
+            return mWindowState;
+        }
     }
 
     private static class TestWindowContainerBuilder {
@@ -1799,6 +1986,7 @@
         private boolean mIsVisible;
         private boolean mIsWaitTransitStart;
         private Integer mOrientation;
+        private WindowState mWindowState;
 
         TestWindowContainerBuilder(WindowManagerService wm) {
             mWm = wm;
@@ -1806,6 +1994,7 @@
             mIsAnimating = false;
             mIsVisible = false;
             mOrientation = null;
+            mWindowState = null;
         }
 
         TestWindowContainerBuilder setLayer(int layer) {
@@ -1828,6 +2017,11 @@
             return this;
         }
 
+        TestWindowContainerBuilder setAsWindowState(WindowState ws) {
+            mWindowState = ws;
+            return this;
+        }
+
         TestWindowContainerBuilder setWaitForTransitionStart(boolean waitTransitStart) {
             mIsWaitTransitStart = waitTransitStart;
             return this;
@@ -1835,7 +2029,7 @@
 
         TestWindowContainer build() {
             return new TestWindowContainer(mWm, mLayer, mIsAnimating, mIsVisible,
-                    mIsWaitTransitStart, mOrientation);
+                    mIsWaitTransitStart, mOrientation, mWindowState);
         }
     }
 
diff --git a/services/usb/java/com/UsbDataSignalDisableRequesters.java b/services/usb/java/com/UsbDataSignalDisableRequesters.java
new file mode 100644
index 0000000..d4d6492
--- /dev/null
+++ b/services/usb/java/com/UsbDataSignalDisableRequesters.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.util.ArraySet;
+
+/**
+ * A helper class to store and manage the request for disabling USB port data signaling.
+ *
+ * External requesters are identified by UIDs.
+ * Internal requesters are identified by a reason code enumerated in UsbManagerInternal.
+ *
+ * @hide
+ */
+public final class UsbDataSignalDisableRequesters {
+    final ArraySet<Integer> mExternalUids = new ArraySet<>();
+    final ArraySet<Integer> mInternalReasons  = new ArraySet<>();
+
+    public boolean isEmpty() {
+        return mExternalUids.isEmpty() && mInternalReasons.isEmpty();
+    }
+}
\ No newline at end of file
diff --git a/services/usb/java/com/android/server/usb/UsbManagerInternal.java b/services/usb/java/com/android/server/usb/UsbManagerInternal.java
new file mode 100644
index 0000000..c97df6b
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbManagerInternal.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.usb.IUsbOperationInternal;
+import android.hardware.usb.UsbPort;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * UsbManagerInternal provides internal APIs for the UsbService to
+ * reduce IPC overhead costs and support internal USB data signal stakers.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class UsbManagerInternal {
+
+  public static final int OS_USB_DISABLE_REASON_AAPM = 0;
+
+  @Retention(RetentionPolicy.SOURCE)
+  @IntDef(value = {OS_USB_DISABLE_REASON_AAPM})
+  public @interface OsUsbDisableReason {
+  }
+
+  public abstract boolean enableUsbData(String portId, boolean enable,
+      int operationId, IUsbOperationInternal callback, @OsUsbDisableReason int disableReason);
+
+  public abstract UsbPort[] getPorts();
+
+}
\ No newline at end of file
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 9470c0a..ba9dff6 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -46,6 +46,7 @@
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
+
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Looper;
@@ -69,6 +70,7 @@
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.FgThread;
+import com.android.server.LocalServices;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 
@@ -165,8 +167,10 @@
     private final Object mLock = new Object();
 
     // Key: USB port id
-    // Value: A set of UIDs of requesters who request disabling usb data
-    private final ArrayMap<String, ArraySet<Integer>> mUsbDisableRequesters = new ArrayMap<>();
+    // Value: UsbDataSignalDisableRequesters: UIDs of requesters who request
+    // disabling usb data and disable request reasons by local service callers
+    private final ArrayMap<String, UsbDataSignalDisableRequesters>
+        mUsbDisableRequesters = new ArrayMap<>();
 
     /**
      * @return the {@link UsbUserSettingsManager} for the given userId
@@ -221,6 +225,9 @@
         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
         mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, null);
+        if(android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal()) {
+            LocalServices.addService(UsbManagerInternal.class, new UsbManagerInternalImpl());
+        }
     }
 
     // Ideally we should use the injector pattern so we wouldn't need this constructor  for test
@@ -236,6 +243,10 @@
         mUserManager = userManager;
         mSettingsManager = usbSettingsManager;
         mPermissionManager = new UsbPermissionManager(context, this);
+
+        if(android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal()) {
+            LocalServices.addService(UsbManagerInternal.class, new UsbManagerInternalImpl());
+        }
     }
 
     /**
@@ -903,15 +914,21 @@
     @Override
     public boolean enableUsbData(String portId, boolean enable, int operationId,
                                  IUsbOperationInternal callback) {
-        return enableUsbDataInternal(portId, enable, operationId, callback, Binder.getCallingUid());
+        return enableUsbDataInternal(portId, enable, operationId, callback,
+            Binder.getCallingUid(), false);
     }
 
     /**
-     *  Internal function abstracted for testing with callerUid
+     *  Manages the enablement of USB data. Requester field could mean two things:
+     *  1. UID of the app that requested USB data to be disabled if caller is external.
+     *  2. Enumberated disable request reason if the caller is internal.
+     *
+     *  For internal requests, isInternalRequest should be set to true. Since
+     * internal requests all share the same UID, the request managed separately.
      */
     @VisibleForTesting
     boolean enableUsbDataInternal(String portId, boolean enable, int operationId,
-            IUsbOperationInternal callback, int callerUid) {
+            IUsbOperationInternal callback, int requester, boolean isInternalRequest) {
         Objects.requireNonNull(portId, "enableUsbData: portId must not be null. opId:"
                 + operationId);
         Objects.requireNonNull(callback, "enableUsbData: callback must not be null. opId:"
@@ -919,7 +936,7 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
         if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
-            if (!shouldUpdateUsbSignaling(portId, enable, callerUid)) {
+            if (!shouldUpdateUsbSignaling(portId, enable, requester, isInternalRequest)) {
                 try {
                     callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
                 } catch (RemoteException e) {
@@ -949,25 +966,42 @@
     }
 
     /**
+     * Function to determine if USB data signaling state should be updated.
+     * Depending on if request is internal, input requester should be UID or enumerated disable
+     * reason.
+     *
      * If enable = true, exclude UID from update list.
      * If enable = false, include UID in update list.
      * Return false if enable = true and the list is empty (no updates).
      * Return true otherwise (let downstream decide on updates).
      */
-    private boolean shouldUpdateUsbSignaling(String portId, boolean enable, int uid) {
+    private boolean shouldUpdateUsbSignaling(String portId, boolean enable,
+        int requester, boolean isInternalRequest) {
+        if(isInternalRequest &&
+               !android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal())
+          return false;
         synchronized (mUsbDisableRequesters) {
             if (!mUsbDisableRequesters.containsKey(portId)) {
-                mUsbDisableRequesters.put(portId, new ArraySet<>());
+                mUsbDisableRequesters.put(portId, new UsbDataSignalDisableRequesters());
             }
-
-            ArraySet<Integer> uidsOfDisableRequesters = mUsbDisableRequesters.get(portId);
+            UsbDataSignalDisableRequesters disableRequests =
+                mUsbDisableRequesters.get(portId);
 
             if (enable) {
-                uidsOfDisableRequesters.remove(uid);
-                // re-enable USB port (return true) if there are no other disable requesters
-                return uidsOfDisableRequesters.isEmpty();
+                if(isInternalRequest) {
+                    disableRequests.mInternalReasons.remove(requester);
+                } else {
+                    disableRequests.mExternalUids.remove(requester);
+                }
+                // re-enable USB port (return true) if there are no other
+                // disable requesters
+                return disableRequests.isEmpty();
             } else {
-                uidsOfDisableRequesters.add(uid);
+                if(isInternalRequest) {
+                    disableRequests.mInternalReasons.add(requester);
+                } else {
+                    disableRequests.mExternalUids.add(requester);
+                }
             }
         }
         return true;
@@ -976,7 +1010,8 @@
     @Override
     public void enableUsbDataWhileDocked(String portId, int operationId,
                                          IUsbOperationInternal callback) {
-        enableUsbDataWhileDockedInternal(portId, operationId, callback, Binder.getCallingUid());
+        enableUsbDataWhileDockedInternal(portId, operationId, callback,
+            Binder.getCallingUid(), false);
     }
 
     /**
@@ -984,7 +1019,7 @@
      */
     @VisibleForTesting
      void enableUsbDataWhileDockedInternal(String portId, int operationId,
-            IUsbOperationInternal callback, int callerUid) {
+            IUsbOperationInternal callback, int callerUid, boolean isInternalRequest) {
         Objects.requireNonNull(portId, "enableUsbDataWhileDocked: portId must not be null. opId:"
                 + operationId);
         Objects.requireNonNull(callback,
@@ -993,7 +1028,7 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
         if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
-            if (!shouldUpdateUsbSignaling(portId, true, callerUid)) {
+            if (!shouldUpdateUsbSignaling(portId, true, callerUid, isInternalRequest)) {
                 try {
                     callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
                 } catch (RemoteException e) {
@@ -1455,10 +1490,11 @@
         public void onUidRemoved(int uid) {
             synchronized (mUsbDisableRequesters) {
                 for (String portId : mUsbDisableRequesters.keySet()) {
-                    ArraySet<Integer> disabledUid = mUsbDisableRequesters.get(portId);
-                    if (disabledUid != null) {
-                        disabledUid.remove(uid);
-                        if (disabledUid.isEmpty()) {
+                    UsbDataSignalDisableRequesters disableRequesters =
+                        mUsbDisableRequesters.get(portId);
+                    if (disableRequesters != null) {
+                        disableRequesters.mExternalUids.remove(uid);
+                        if (disableRequesters.isEmpty()) {
                             enableUsbData(portId, true, PACKAGE_MONITOR_OPERATION_ID,
                                     new IUsbOperationInternal.Default());
                         }
@@ -1496,4 +1532,19 @@
             }
         }
     }
+
+    private class UsbManagerInternalImpl extends UsbManagerInternal {
+        @Override
+        public boolean enableUsbData(String portId, boolean enable,
+                int operationId, IUsbOperationInternal callback,
+            @OsUsbDisableReason int disableReason) {
+            return enableUsbDataInternal(portId, enable, operationId, callback,
+                disableReason, true);
+        }
+
+        @Override
+        public UsbPort[] getPorts() {
+            return mPortManager.getPorts();
+        }
+    }
 }
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index ff9cba2..f001232 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -33,6 +33,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.util.ArrayList;
@@ -185,11 +186,7 @@
                 if (hasPrivileges) {
                     // Only update enabled state for the app on /system. Once it has been
                     // updated we shouldn't touch it.
-                    if (!isUpdatedSystemApp(ai) && enabledSetting
-                            == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
-                            || enabledSetting
-                            == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
-                            || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
+                    if (shouldUpdateEnabledState(ai, enabledSetting)) {
                         Log.i(TAG, "Update state (" + packageName + "): ENABLED for user "
                                 + userId);
                         context.createContextAsUser(UserHandle.of(userId), 0)
@@ -330,6 +327,21 @@
         }
     }
 
+    private static boolean shouldUpdateEnabledState(ApplicationInfo appInfo, int enabledSetting) {
+        if (Flags.cleanupCarrierAppUpdateEnabledStateLogic()) {
+            return !isUpdatedSystemApp(appInfo)
+                    && (enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+                            || enabledSetting
+                                    == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+                            || (appInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0);
+        } else {
+            return !isUpdatedSystemApp(appInfo)
+                            && enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+                    || enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+                    || (appInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0;
+        }
+    }
+
     /**
      * Returns the list of "default" carrier apps.
      *
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index bd5c759..49ca6f3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -249,6 +249,13 @@
     public static final String KEY_PROVISION_SATELLITE_TOKENS = "provision_satellite";
 
     /**
+     * Bundle key to get the response from
+     * {@link #deprovisionSatellite(List, Executor, OutcomeReceiver)}.
+     * @hide
+     */
+    public static final String KEY_DEPROVISION_SATELLITE_TOKENS = "deprovision_satellite";
+
+    /**
      * The request was successfully processed.
      */
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -2791,6 +2798,61 @@
         }
     }
 
+    /**
+     * Deliver the list of deprovisioned satellite subscriber infos.
+     *
+     * @param list The list of deprovisioned satellite subscriber infos.
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback object to which the result will be delivered.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public void deprovisionSatellite(@NonNull List<SatelliteSubscriberInfo> list,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                ResultReceiver receiver = new ResultReceiver(null) {
+                    @Override
+                    protected void onReceiveResult(int resultCode, Bundle resultData) {
+                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                            if (resultData.containsKey(KEY_DEPROVISION_SATELLITE_TOKENS)) {
+                                boolean isUpdated =
+                                        resultData.getBoolean(KEY_DEPROVISION_SATELLITE_TOKENS);
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onResult(isUpdated)));
+                            } else {
+                                loge("KEY_DEPROVISION_SATELLITE_TOKENS does not exist.");
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onError(new SatelliteException(
+                                                SATELLITE_RESULT_REQUEST_FAILED))));
+                            }
+                        } else {
+                            executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                    callback.onError(new SatelliteException(resultCode))));
+                        }
+                    }
+                };
+                telephony.deprovisionSatellite(list, receiver);
+            } else {
+                loge("deprovisionSatellite() invalid telephony");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+            }
+        } catch (RemoteException ex) {
+            loge("deprovisionSatellite() RemoteException: " + ex);
+            executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                    new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+        }
+    }
+
     @Nullable
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 3161d17..61f0146 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3444,4 +3444,15 @@
      */
     boolean overrideCarrierRoamingNtnEligibilityChanged(
             in boolean status, in boolean resetRequired);
+
+    /**
+     * Deliver the list of deprovisioned satellite subscriber infos.
+     *
+     * @param list The list of deprovisioned satellite subscriber infos.
+     * @param result The result receiver that returns whether deliver success or fail.
+     * @hide
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void deprovisionSatellite(in List<SatelliteSubscriberInfo> list, in ResultReceiver result);
 }
diff --git a/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java
index e3a129f..d03ad5c 100644
--- a/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java
+++ b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java
@@ -61,8 +61,13 @@
     @Test
     public void canRead() {
         ApplicationSharedMemory instance = ApplicationSharedMemory.getInstance();
-        instance.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis();
-        // Don't actually care about the value of the above.
+        try {
+            instance.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis();
+            // Don't actually care about the value of the above.
+        } catch (java.time.DateTimeException e) {
+            // This exception is okay during testing.  It means there was no time source, which
+            // could be because of network problems or a feature being flagged off.
+        }
     }
 
     /** Application processes should not have mutable access. */
diff --git a/tests/Internal/src/com/android/internal/os/OWNERS b/tests/Internal/src/com/android/internal/os/OWNERS
new file mode 100644
index 0000000..64ffa46
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/os/OWNERS
@@ -0,0 +1,2 @@
+# ApplicationSharedMemory
+per-file *ApplicationSharedMemory* = file:/PERFORMANCE_OWNERS
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
index 56845ae..51d57f0 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
@@ -18,6 +18,10 @@
 
 import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -31,12 +35,15 @@
 import android.content.Context;
 import android.hardware.usb.IUsbOperationInternal;
 import android.hardware.usb.flags.Flags;
+import android.hardware.usb.UsbPort;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.LocalServices;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -71,26 +78,38 @@
 
     private static final int TEST_SECOND_CALLER_ID = 2000;
 
+    private static final int TEST_INTERNAL_REQUESTER_REASON_1 = 100;
+
+    private static final int TEST_INTERNAL_REQUESTER_REASON_2 = 200;
+
     private UsbService mUsbService;
 
+    private UsbManagerInternal mUsbManagerInternal;
+
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Before
     public void setUp() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING);
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING_INTERNAL);
+        LocalServices.removeAllServicesForTest();
         MockitoAnnotations.initMocks(this);
 
-        when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID), anyBoolean(), eq(TEST_TRANSACTION_ID),
-                eq(mCallback), any())).thenReturn(true);
+        when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID), anyBoolean(),
+                 eq(TEST_TRANSACTION_ID), eq(mCallback), any())).thenReturn(true);
 
         mUsbService = new UsbService(mContext, mUsbPortManager, mUsbAlsaManager,
                 mUserManager, mUsbSettingsManager);
+        mUsbManagerInternal = LocalServices.getService(UsbManagerInternal.class);
+        assertWithMessage("LocalServices.getService(UsbManagerInternal.class)")
+            .that(mUsbManagerInternal).isNotNull();
     }
 
-    private void assertToggleUsbSuccessfully(int uid, boolean enable) {
+    private void assertToggleUsbSuccessfully(int requester, boolean enable,
+        boolean isInternalRequest) {
         assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable,
-                TEST_TRANSACTION_ID, mCallback, uid));
+                TEST_TRANSACTION_ID, mCallback, requester, isInternalRequest));
 
         verify(mUsbPortManager).enableUsbData(TEST_PORT_ID,
                 enable, TEST_TRANSACTION_ID, mCallback, null);
@@ -100,9 +119,10 @@
         clearInvocations(mCallback);
     }
 
-    private void assertToggleUsbFailed(int uid, boolean enable) throws Exception {
+    private void assertToggleUsbFailed(int requester, boolean enable,
+        boolean isInternalRequest) throws Exception {
         assertFalse(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable,
-                TEST_TRANSACTION_ID, mCallback, uid));
+                TEST_TRANSACTION_ID, mCallback, requester, isInternalRequest));
 
         verifyZeroInteractions(mUsbPortManager);
         verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
@@ -116,15 +136,16 @@
      */
     @Test
     public void disableUsb_successfullyDisable() {
-        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
     }
 
     /**
-     * Verify enableUsbData successfully enables USB port without error given no other stakers
+     * Verify enableUsbData successfully enables USB port without error given
+     * no other stakers
      */
     @Test
     public void enableUsbWhenNoOtherStakers_successfullyEnable() {
-        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true);
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true, false);
     }
 
     /**
@@ -132,47 +153,132 @@
      */
     @Test
     public void enableUsbPortWithOtherStakers_failsToEnable() throws Exception {
-        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
 
-        assertToggleUsbFailed(TEST_SECOND_CALLER_ID, true);
+        assertToggleUsbFailed(TEST_SECOND_CALLER_ID, true, false);
     }
 
     /**
-     * Verify enableUsbData successfully enables USB port when the last staker is removed
+     * Verify enableUsbData successfully enables USB port when the last staker
+     * is removed
      */
     @Test
     public void enableUsbByTheOnlyStaker_successfullyEnable() {
-        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
 
-        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true);
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true, false);
     }
 
     /**
-     * Verify enableUsbDataWhileDockedInternal does not enable USB port if other stakers are present
+     * Verify enableUsbDataWhileDockedInternal does not enable USB port if other
+     * stakers are present
      */
     @Test
     public void enableUsbWhileDockedWhenThereAreOtherStakers_failsToEnable()
             throws RemoteException {
-        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
 
         mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
-                mCallback, TEST_SECOND_CALLER_ID);
+                mCallback, TEST_SECOND_CALLER_ID, false);
 
         verifyZeroInteractions(mUsbPortManager);
         verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
     }
 
     /**
-     * Verify enableUsbDataWhileDockedInternal does enable USB port if other stakers are
-     * not present
+     * Verify enableUsbDataWhileDockedInternal does enable USB port if other
+     * stakers are not present
      */
     @Test
     public void enableUsbWhileDockedWhenThereAreNoStakers_SuccessfullyEnable() {
         mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
-                mCallback, TEST_SECOND_CALLER_ID);
+                mCallback, TEST_SECOND_CALLER_ID, false);
 
         verify(mUsbPortManager).enableUsbDataWhileDocked(TEST_PORT_ID, TEST_TRANSACTION_ID,
                         mCallback, null);
         verifyZeroInteractions(mCallback);
     }
+
+    /**
+     * Verify enableUsbData successfully enables USB port without error given no
+     * other stakers for internal requests
+     */
+    @Test
+    public void enableUsbWhenNoOtherStakers_forInternalRequest_successfullyEnable() {
+        assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, true, true);
+    }
+
+    /**
+     * Verify enableUsbData does not enable USB port if other internal stakers
+     * are present for internal requests
+     */
+    @Test
+    public void enableUsbPortWithOtherInternalStakers_forInternalRequest_failsToEnable()
+        throws Exception {
+        assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, true);
+
+        assertToggleUsbFailed(TEST_INTERNAL_REQUESTER_REASON_2, true, true);
+    }
+
+    /**
+     * Verify enableUsbData does not enable USB port if other external stakers
+     * are present for internal requests
+     */
+    @Test
+    public void enableUsbPortWithOtherExternalStakers_forInternalRequest_failsToEnable()
+        throws Exception {
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
+
+        assertToggleUsbFailed(TEST_INTERNAL_REQUESTER_REASON_2, true, true);
+    }
+
+    /**
+     * Verify enableUsbData does not enable USB port if other internal stakers
+     * are present for external requests
+     */
+    @Test
+    public void enableUsbPortWithOtherInternalStakers_forExternalRequest_failsToEnable()
+        throws Exception {
+        assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, true);
+
+        assertToggleUsbFailed(TEST_FIRST_CALLER_ID, true, false);
+    }
+
+    /**
+     * Verify enableUsbData successfully enables USB port when the last staker
+     * is removed for internal requests
+     */
+    @Test
+    public void enableUsbByTheOnlyStaker_forInternalRequest_successfullyEnable() {
+        assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, false);
+
+        assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, true, false);
+    }
+
+    /**
+     * Verify USB Manager internal calls mPortManager to get UsbPorts
+     */
+    @Test
+    public void usbManagerInternal_getPorts_callsPortManager() {
+        when(mUsbPortManager.getPorts()).thenReturn(new UsbPort[] {});
+
+        UsbPort[] ports = mUsbManagerInternal.getPorts();
+
+        verify(mUsbPortManager).getPorts();
+        assertEquals(ports.length, 0);
+    }
+
+    @Test
+    public void usbManagerInternal_enableUsbData_successfullyEnable() {
+        boolean desiredEnableState = true;
+
+        assertTrue(mUsbManagerInternal.enableUsbData(TEST_PORT_ID, desiredEnableState,
+        TEST_TRANSACTION_ID, mCallback, TEST_INTERNAL_REQUESTER_REASON_1));
+
+        verify(mUsbPortManager).enableUsbData(TEST_PORT_ID,
+                desiredEnableState, TEST_TRANSACTION_ID, mCallback, null);
+        verifyZeroInteractions(mCallback);
+        clearInvocations(mUsbPortManager);
+        clearInvocations(mCallback);
+    }
 }
diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp
index 1af8d6f..b2e48bd 100644
--- a/tools/aapt/StringPool.cpp
+++ b/tools/aapt/StringPool.cpp
@@ -40,7 +40,7 @@
 void printStringPool(const ResStringPool* pool)
 {
     if (pool->getError() == NO_INIT) {
-        printf("String pool is unitialized.\n");
+        printf("String pool is uninitialized.\n");
         return;
     } else if (pool->getError() != NO_ERROR) {
         printf("String pool is corrupt/invalid.\n");
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 064b461..2527dcd 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -445,7 +445,7 @@
   using namespace android;
 
   if (pool->getError() == NO_INIT) {
-    printer->Print("String pool is unitialized.\n");
+    printer->Print("String pool is uninitialized.\n");
     return;
   } else if (pool->getError() != NO_ERROR) {
     printer->Print("String pool is corrupt/invalid.\n");
diff --git a/tools/processors/property_cache/Android.bp b/tools/processors/property_cache/Android.bp
new file mode 100644
index 0000000..81fab7a
--- /dev/null
+++ b/tools/processors/property_cache/Android.bp
@@ -0,0 +1,57 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+    default_team: "trendy_team_framework_android_multiuser",
+}
+
+java_library_host {
+    name: "libcached-property-annotation-processor",
+    srcs: [
+        ":framework-annotations",
+        "src/**/*.java",
+    ],
+    static_libs: [
+        "codegen-version-info",
+        "android.multiuser.flags-aconfig-java-host",
+        "guava",
+    ],
+    use_tools_jar: true,
+}
+
+java_plugin {
+    name: "cached-property-annotation-processor",
+    processor_class: "android.processor.property_cache.CachedPropertyProcessor",
+    static_libs: ["libcached-property-annotation-processor"],
+}
+
+java_aconfig_library {
+    name: "android.multiuser.flags-aconfig-java-host",
+    aconfig_declarations: "android.multiuser.flags-aconfig",
+    host_supported: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+java_test_host {
+    name: "cached-property-annotation-processor-test",
+    srcs: ["test/java/**/*.java"],
+    java_resources: [":CachedPropertyAnnotationJavaTestSource"],
+    static_libs: [
+        "compile-testing-prebuilt",
+        "truth",
+        "junit",
+        "guava",
+        "libcached-property-annotation-processor",
+    ],
+    test_suites: ["general-tests"],
+}
+
+filegroup {
+    name: "CachedPropertyAnnotationJavaTestSource",
+    srcs: ["test/resources/*.java"],
+    path: "test/resources/",
+    visibility: ["//visibility:private"],
+}
diff --git a/tools/processors/property_cache/TEST_MAPPING b/tools/processors/property_cache/TEST_MAPPING
new file mode 100644
index 0000000..7177abc
--- /dev/null
+++ b/tools/processors/property_cache/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "cached-property-annotation-processor-test"
+    }
+  ]
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/CacheConfig.java b/tools/processors/property_cache/src/java/android/processor/property_cache/CacheConfig.java
new file mode 100644
index 0000000..c665c84
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/CacheConfig.java
@@ -0,0 +1,138 @@
+/*
+ * 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.processor.property_cache;
+
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
+
+import com.google.common.base.CaseFormat;
+
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+
+public class CacheConfig {
+    private final CacheModifiers mModifiers;
+    private final int mMaxSize;
+    private final String mModuleName;
+    private final String mApiName;
+    private final String mClassName;
+    private final String mQualifiedName;
+    private String mPropertyName;
+    private String mMethodName;
+    private int mNumberOfParams = 0;
+    private String mInputType = Constants.JAVA_LANG_VOID;
+    private String mResultType;
+
+    public CacheConfig(TypeElement classElement, ExecutableElement method) {
+        CachedPropertyDefaults classAnnotation = classElement.getAnnotation(
+                CachedPropertyDefaults.class);
+        CachedProperty methodAnnotation = method.getAnnotation(CachedProperty.class);
+
+        mModuleName = methodAnnotation.module().isEmpty() ? classAnnotation.module()
+                : methodAnnotation.module();
+        mClassName = classElement.getSimpleName().toString();
+        mQualifiedName = classElement.getQualifiedName().toString();
+        mModifiers = new CacheModifiers(methodAnnotation.modsFlagOnOrNone());
+        mMethodName = method.getSimpleName().toString();
+        mPropertyName = getPropertyName(mMethodName);
+        mApiName = methodAnnotation.api().isEmpty() ? getUniqueApiName(mClassName, mPropertyName)
+                : methodAnnotation.api();
+        mMaxSize = methodAnnotation.max() == -1 ? classAnnotation.max() : methodAnnotation.max();
+        mNumberOfParams = method.getParameters().size();
+        if (mNumberOfParams > 0) {
+            mInputType = primitiveTypeToObjectEquivalent(
+                method.getParameters().get(0).asType().toString());
+        }
+        mResultType = primitiveTypeToObjectEquivalent(method.getReturnType().toString());
+    }
+
+    public CacheModifiers getModifiers() {
+        return mModifiers;
+    }
+
+    public int getMaxSize() {
+        return mMaxSize;
+    }
+
+    public String getApiName() {
+        return mApiName;
+    }
+
+    public String getClassName() {
+        return mClassName;
+    }
+
+    public String getQualifiedName() {
+        return mQualifiedName;
+    }
+
+    public String getModuleName() {
+        return mModuleName;
+    }
+
+    public String getMethodName() {
+        return mMethodName;
+    }
+
+    public String getPropertyName() {
+        return mPropertyName;
+    }
+
+    public String getPropertyVariable() {
+        return (mModifiers.isStatic() ? "s" : "m") + mPropertyName;
+    }
+
+    private String getPropertyName(String methodName) {
+        if (methodName.startsWith("get")) {
+            return methodName.substring(3);
+        } else if (methodName.startsWith("is")) {
+            return methodName.substring(2);
+        } else {
+            return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, methodName);
+        }
+    }
+
+    public int getNumberOfParams() {
+        return mNumberOfParams;
+    }
+
+    public String getInputType() {
+        return mInputType;
+    }
+
+    public String getResultType() {
+        return mResultType;
+    }
+
+    /**
+     * This method returns the unique api name for a given class and property name.
+     * Property name is retrieved from the method name.
+     * Both names are combined and converted to lower snake case.
+     *
+     * @param className    The name of the class that contains the property.
+     * @param propertyName The name of the property.
+     * @return The registration name for the property.
+     */
+    private String getUniqueApiName(String className, String propertyName) {
+        return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, className + propertyName);
+    }
+
+    private String primitiveTypeToObjectEquivalent(String simpleType) {
+        // checking against primitive types
+        return Constants.PRIMITIVE_TYPE_MAP.getOrDefault(simpleType, simpleType);
+    }
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/CacheModifiers.java b/tools/processors/property_cache/src/java/android/processor/property_cache/CacheModifiers.java
new file mode 100644
index 0000000..fda9b2c
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/CacheModifiers.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache;
+
+import com.android.internal.annotations.CacheModifier;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class CacheModifiers {
+    private final boolean mIsStatic;
+    private static final String STATIC_MODIFIER_STRING = "static ";
+
+    CacheModifiers(CacheModifier[] modifierArray) {
+        final List<CacheModifier> modifiers = Arrays.asList(modifierArray);
+        mIsStatic = modifiers.contains(CacheModifier.STATIC);
+    }
+
+    public boolean isStatic() {
+        return mIsStatic;
+    }
+
+    public String getStaticModifier() {
+        return mIsStatic ? STATIC_MODIFIER_STRING : Constants.EMPTY_STRING;
+    }
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java b/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java
new file mode 100644
index 0000000..0361012
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java
@@ -0,0 +1,95 @@
+/*
+ * 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.processor.property_cache;
+
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.ElementFilter;
+import javax.tools.JavaFileObject;
+
+public class CachedPropertyProcessor extends AbstractProcessor {
+
+    IpcDataCacheComposer mIpcDataCacheComposer =
+            new IpcDataCacheComposer();
+
+    @Override
+    public Set<String> getSupportedAnnotationTypes() {
+        return new HashSet<String>(
+                ImmutableSet.of(CachedPropertyDefaults.class.getCanonicalName()));
+    }
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+        for (Element element : roundEnv.getElementsAnnotatedWith(CachedPropertyDefaults.class)) {
+            try {
+                generateCachedClass((TypeElement) element, processingEnv.getFiler());
+            } catch (IOException e) {
+                e.printStackTrace();
+                return false;
+            }
+        }
+        return false;
+    }
+
+    private void generateCachedClass(TypeElement classElement, Filer filer) throws IOException {
+        String packageName =
+                processingEnv
+                        .getElementUtils()
+                        .getPackageOf(classElement)
+                        .getQualifiedName()
+                        .toString();
+        String className = classElement.getSimpleName().toString() + "Cache";
+        JavaFileObject jfo = filer.createSourceFile(packageName + "." + className);
+        Writer writer = jfo.openWriter();
+        writer.write("package " + packageName + ";\n\n");
+        writer.write("import android.os.IpcDataCache;\n");
+        writer.write("\n    /** \n    * This class is auto-generated \n    * @hide \n    **/");
+        writer.write("\npublic class " + className + " {\n");
+
+        List<ExecutableElement> methods =
+                ElementFilter.methodsIn(classElement.getEnclosedElements());
+        String initCache = String.format(Constants.METHOD_COMMENT,
+                " - initialise all caches for class " + className)
+                + "\npublic static void initCache() {";
+        for (ExecutableElement method : methods) {
+            if (method.getAnnotation(CachedProperty.class) != null) {
+                mIpcDataCacheComposer.generatePropertyCache(writer, classElement, method);
+                initCache += "\n    " + mIpcDataCacheComposer.generateInvalidatePropertyCall();
+            }
+        }
+        initCache += "\n}";
+        writer.write(initCache);
+        writer.write("\n}");
+        writer.write("\n");
+        writer.close();
+    }
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/Constants.java b/tools/processors/property_cache/src/java/android/processor/property_cache/Constants.java
new file mode 100644
index 0000000..03961bc
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/Constants.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache;
+
+import com.google.common.collect.ImmutableMap;
+
+public final class Constants {
+    public static final String EMPTY_STRING = "";
+    public static final String JAVA_LANG_VOID = "java.lang.Void";
+    public static final ImmutableMap<String, String> PRIMITIVE_TYPE_MAP =
+            ImmutableMap.of(
+                    "int", "java.lang.Integer",
+                    "boolean", "java.lang.Boolean",
+                    "long", "java.lang.Long",
+                    "float", "java.lang.Float",
+                    "double", "java.lang.Double",
+                    "byte", "java.lang.Byte",
+                    "short", "java.lang.Short",
+                    "char", "java.lang.Character");
+
+    public static final String METHOD_COMMENT = "\n    /**"
+            + "\n    * This method is auto-generated%s"
+            + "\n    * "
+            + "\n    * @hide"
+            + "\n    */";
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/IpcDataCacheComposer.java b/tools/processors/property_cache/src/java/android/processor/property_cache/IpcDataCacheComposer.java
new file mode 100644
index 0000000..8526a04
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/IpcDataCacheComposer.java
@@ -0,0 +1,152 @@
+/*
+ * 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.processor.property_cache;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+
+public class IpcDataCacheComposer {
+
+    private static final String PROPERTY_DEFINITION_LINE = "private %s%s %s;\n";
+    private static final String METHOD_NAME_LINE = "\npublic %s%s %s(%s%s%s\n) {\n";
+    private static final String RETURN_IF_NOT_NULL_LINE =
+            "if (%s != null) {\n   return %s.%s;\n  }";
+
+    private CacheConfig mCacheConfig;
+
+    /**
+     * Generates code for property cache.
+     *
+     * @param writer       writer to write code to.
+     * @param classElement class element to generate code for.
+     * @param method       method element to generate code for.
+     * @throws IOException if writer throws IOException.
+     */
+    public void generatePropertyCache(Writer writer, TypeElement classElement,
+            ExecutableElement method) throws IOException {
+
+        mCacheConfig = new CacheConfig(classElement, method);
+
+        ParamComposer inputParam = new ParamComposer(null, null);
+        ParamComposer binderParam = new ParamComposer(
+                String.format("IpcDataCache.RemoteCall<%s, %s>", mCacheConfig.getInputType(),
+                        mCacheConfig.getResultType()), "binderCall");
+
+        ParamComposer bypassParam = new ParamComposer(null, null); // empty if method have no params
+        String queryCall = "query(null)";
+        if (mCacheConfig.getNumberOfParams() > 0) {
+            bypassParam = new ParamComposer(
+                    String.format("IpcDataCache.BypassCall<%s> ", mCacheConfig.getInputType()),
+                    "bypassPredicate");
+            inputParam = new ParamComposer(mCacheConfig.getInputType(), "query");
+            queryCall = "query(query)";
+        }
+        String propertyClass =
+                "IpcDataCache<" + mCacheConfig.getInputType() + ", " + mCacheConfig.getResultType()
+                        + ">";
+        String invalidateName = "invalidate" + mCacheConfig.getPropertyName();
+        String lockObject = mCacheConfig.getPropertyVariable() + "Lock";
+        writer.write("private " + mCacheConfig.getModifiers().getStaticModifier() + "final Object "
+                + lockObject + " = new Object();\n");
+        writer.write(String.format(PROPERTY_DEFINITION_LINE,
+                mCacheConfig.getModifiers().getStaticModifier(), propertyClass,
+                mCacheConfig.getPropertyVariable()));
+
+        writer.write(propertyInvalidatedCacheMethod(binderParam, bypassParam, inputParam, queryCall,
+                lockObject));
+
+        // If binder param is not empty then generate getter without binder param to be called
+        if (!bypassParam.getParam().isEmpty()) {
+            writer.write(propertyInvalidatedCacheMethod(binderParam, new ParamComposer(null, null),
+                    inputParam, queryCall, lockObject));
+        }
+        writer.write(String.format(Constants.METHOD_COMMENT,
+                "- invalidate cache for {@link  " + mCacheConfig.getQualifiedName() + "#"
+                        + mCacheConfig.getMethodName() + "}"));
+        writer.write("\n public static final void " + invalidateName + "() {");
+        writer.write(
+                "\n     IpcDataCache.invalidateCache(\"" + mCacheConfig.getModuleName() + "\", \""
+                        + mCacheConfig.getApiName() + "\");");
+        writer.write("\n }");
+        writer.write("\n");
+        writer.write("\n");
+    }
+
+    /**
+     * Generates code to call cache invalidation.
+     *
+     * @return code string calling cache invalidation.
+     */
+    public String generateInvalidatePropertyCall() {
+        String invalidateName = "invalidate" + mCacheConfig.getPropertyName();
+        return mCacheConfig.getClassName() + "Cache." + invalidateName + "();";
+    }
+
+    /**
+     * Generates code for getter that returns cached value or calls binder and caches result.
+     *
+     * @param binderParam parameter for binder call.
+     * @param bypassParam parameter for bypass predicate.
+     * @param inputParam  parameter for input value.
+     * @param queryCall   cache query call syntax.
+     * @param lockObject  object to synchronize on.
+     * @return String with code for method.
+     */
+    private String propertyInvalidatedCacheMethod(ParamComposer binderParam,
+            ParamComposer bypassParam, ParamComposer inputParam, String queryCall,
+            String lockObject) {
+        String result = "\n";
+        CacheModifiers modifiers = mCacheConfig.getModifiers();
+        String paramsComments = binderParam.getParamComment(
+                "lambda for remote call" + " {@link  " + mCacheConfig.getQualifiedName() + "#"
+                        + mCacheConfig.getMethodName() + " }") + bypassParam.getParamComment(
+                "lambda to bypass remote call") + inputParam.getParamComment(
+                "parameter to call remote lambda");
+        result += String.format(Constants.METHOD_COMMENT, paramsComments);
+        result += String.format(METHOD_NAME_LINE, modifiers.getStaticModifier(),
+                mCacheConfig.getResultType(), mCacheConfig.getMethodName(),
+                binderParam.getParam(), bypassParam.getNextParam(),
+                inputParam.getNextParam());
+        result += String.format(RETURN_IF_NOT_NULL_LINE, mCacheConfig.getPropertyVariable(),
+                mCacheConfig.getPropertyVariable(), queryCall);
+        result += "\n  synchronized (" + lockObject + " ) {";
+        result += "\n    if (" + mCacheConfig.getPropertyVariable() + " == null) {";
+        result += "\n      " + mCacheConfig.getPropertyVariable() + " = new IpcDataCache" + "("
+                + generateCreateIpcConfig() + ", " + binderParam.getName()
+                + bypassParam.getNextName() + ");\n";
+        result += "\n   }";
+        result += "\n  }";
+        result += "\n  return " + mCacheConfig.getPropertyVariable() + "." + queryCall + ";";
+        result += "\n }";
+        result += "\n";
+        return result;
+    }
+
+    /**
+     * Generates code for new IpcDataCache.Config object for given configuration.
+     *
+     * @return String with code for new IpcDataCache.Config object.
+     */
+    public String generateCreateIpcConfig() {
+        return "new IpcDataCache.Config(" + mCacheConfig.getMaxSize() + ", " + "\""
+                + mCacheConfig.getModuleName() + "\"" + ", " + "\"" + mCacheConfig.getApiName()
+                + "\"" + ", " + "\"" + mCacheConfig.getPropertyName() + "\"" + ")";
+    }
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/ParamComposer.java b/tools/processors/property_cache/src/java/android/processor/property_cache/ParamComposer.java
new file mode 100644
index 0000000..307443a
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/ParamComposer.java
@@ -0,0 +1,103 @@
+/*
+ * 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.processor.property_cache;
+
+public class ParamComposer {
+    private String mType;
+    private String mName;
+
+    /** Creates ParamComposer with given type and name.
+     *
+     * @param type type of parameter.
+     * @param name name of parameter.
+     */
+    public ParamComposer(String type, String name) {
+        mType = type;
+        mName = name;
+    }
+
+    /** Returns name of parameter.
+     *
+     * @return name of parameter.
+     */
+    public String getName() {
+        if (mName != null) {
+            return mName;
+        }
+        return Constants.EMPTY_STRING;
+    }
+
+    /** Returns name of parameter for next parameter followed by comma.
+     *
+     * @return name of parameter for next parameter if exists, empty string otherwise.
+     */
+    public String getNextName() {
+        if (!getName().isEmpty()) {
+            return ", " + getName();
+        }
+        return Constants.EMPTY_STRING;
+    }
+
+    /**
+     * Returns type of parameter.
+     *
+     * @return type of parameter.
+     */
+    public String getType() {
+        if (mType != null) {
+            return mType;
+        }
+        return Constants.EMPTY_STRING;
+    }
+
+    /**
+     * Returns type and name of parameter.
+     *
+     * @return type and name of parameter if exists, empty string otherwise.
+     */
+    public String getParam() {
+        if (!getType().isEmpty() && !getName().isEmpty()) {
+            return getType() + " " + getName();
+        }
+        return Constants.EMPTY_STRING;
+    }
+
+    /**
+     * Returns type and name of parameter for next parameter followed by comma.
+     *
+     * @return type and name of parameter for next parameter if exists, empty string otherwise.
+     */
+    public String getNextParam() {
+        if (!getType().isEmpty() && !getName().isEmpty()) {
+            return ", " + getParam();
+        }
+        return Constants.EMPTY_STRING;
+    }
+
+    /**
+     * Returns comment for parameter.
+     *
+     * @param description of parameter.
+     * @return comment for parameter if exists, empty string otherwise.
+     */
+    public String getParamComment(String description) {
+        if (!getType().isEmpty() && !getName().isEmpty()) {
+            return "\n    * @param " + getName() + " - " + description;
+        }
+        return Constants.EMPTY_STRING;
+    }
+}
diff --git a/tools/processors/property_cache/test/java/android/processor/property_cache/CachedPropertyProcessorTest.java b/tools/processors/property_cache/test/java/android/processor/property_cache/CachedPropertyProcessorTest.java
new file mode 100644
index 0000000..1e23c78
--- /dev/null
+++ b/tools/processors/property_cache/test/java/android/processor/property_cache/CachedPropertyProcessorTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache.test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+
+import android.processor.property_cache.CachedPropertyProcessor;
+
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.Compiler;
+import com.google.testing.compile.JavaFileObjects;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import javax.tools.JavaFileObject;
+import javax.tools.StandardLocation;
+
+/** Tests the {@link CachedPropertyProcessor}. */
+@RunWith(JUnit4.class)
+public class CachedPropertyProcessorTest {
+    private final Compiler mCompiler =
+            Compiler.javac().withProcessors(new CachedPropertyProcessor());
+
+    @Test
+    public void testDefaultValues() {
+        JavaFileObject expectedJava = JavaFileObjects.forResource("DefaultCache.java");
+
+        Compilation compilation = mCompiler.compile(JavaFileObjects.forResource("Default.java"));
+        assertThat(compilation).succeeded();
+        assertThat(compilation)
+                .generatedFile(StandardLocation.SOURCE_OUTPUT,
+                        "android/processor/property_cache/test/DefaultCache.java")
+                .hasSourceEquivalentTo(expectedJava);
+    }
+
+    @Test
+    public void testCustomValues() {
+        JavaFileObject expectedJava = JavaFileObjects.forResource("CustomCache.java");
+
+        Compilation compilation = mCompiler.compile(JavaFileObjects.forResource("Custom.java"));
+        assertThat(compilation).succeeded();
+        assertThat(compilation)
+                .generatedFile(StandardLocation.SOURCE_OUTPUT,
+                        "android/processor/property_cache/test/CustomCache.java")
+                .hasSourceEquivalentTo(expectedJava);
+    }
+}
diff --git a/tools/processors/property_cache/test/java/android/processor/property_cache/shadows/IpcDataCache.java b/tools/processors/property_cache/test/java/android/processor/property_cache/shadows/IpcDataCache.java
new file mode 100644
index 0000000..e5ef48c
--- /dev/null
+++ b/tools/processors/property_cache/test/java/android/processor/property_cache/shadows/IpcDataCache.java
@@ -0,0 +1,134 @@
+/*
+ * 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.os;
+
+// Mocked class for generation compilation tests purposes only.
+public class IpcDataCache<Input, Output> {
+    public static class Config {
+        public Config(int max, String module, String api, String name) {
+        }
+    }
+
+    /** Shadow method for generated code compilation tests purposes only.
+     *
+     * @param query - shadow parameter from IpcDataCache in Frameworks.
+     * @return null
+     */
+    public Output query(Input query) {
+        return null;
+    }
+
+    /** Shadow method for generated code compilation tests purposes only.
+     *
+     * @param key - shadow parameter from IpcDataCache in Frameworks;
+     */
+    public static void invalidateCache(String key) {
+    }
+
+    /** Shadow method for generated code compilation tests purposes only.
+     *
+     * @param query - shadow parameter from IpcDataCache in Frameworks;
+     * @return null
+     */
+    public Output recompute(Input query) {
+        return null;
+    }
+
+    /** Shadow method for generated code compilation tests purposes only.
+     *
+     * @param query - parameter equivalent to IpcDataCache in android framework.
+     * @param query - shadow parameter from IpcDataCache in Frameworks;
+     * @return false
+     */
+    public boolean bypass(Input query) {
+        return false;
+    }
+
+    /** Shadow method for generated code compilation tests purposes only.
+     *
+     * @param module - parameter equivalent to IpcDataCache in android framework.
+     * @param key - parameter equivalent to IpcDataCache in android framework.
+     * @return module + key sttring
+     */
+    public static String createPropertyName(String module, String key) {
+        return module + key;
+    }
+
+    public abstract static class QueryHandler<Input, Output> {
+        /** Shadow method for generated code compilation tests purposes only.
+         *
+         * @param query - parameter equivalent to IpcDataCache.QueryHandler in android framework.
+         * @return expected value
+         */
+        public abstract Output apply(Input query);
+        /** Shadow method for generated code compilation tests purposes only.
+         *
+         * @param query - parameter equivalent to IpcDataCache.QueryHandler in android framework.
+         */
+        public boolean shouldBypassCache(Input query) {
+            return false;
+        }
+    }
+
+    public interface RemoteCall<Input, Output> {
+        /** Shadow method for generated code compilation tests purposes only.
+         *
+         * @param query - parameter equivalent to IpcDataCache.RemoteCall in android framework.
+         */
+        Output apply(Input query);
+    }
+
+    public interface BypassCall<Input> {
+        /** Shadow method for generated code compilation tests purposes only.
+         *
+         * @param query - parameter equivalent to IpcDataCache.BypassCall in android framework.
+         */
+        boolean apply(Input query);
+    }
+
+    public IpcDataCache(
+            int maxEntries,
+            String module,
+            String api,
+            String cacheName,
+            QueryHandler<Input, Output> computer) {
+    }
+
+    public IpcDataCache(Config config, QueryHandler<Input, Output> computer) {
+    }
+
+    public IpcDataCache(Config config, RemoteCall<Input, Output> computer) {
+    }
+
+    public IpcDataCache(Config config, RemoteCall<Input, Output> computer,
+            BypassCall<Input> bypassCall) {
+    }
+
+    /** Shadow method for generated code compilation tests purposes only.*/
+    public void invalidateCache() {
+    }
+
+
+    /** Shadow method for generated code compilation tests purposes only.
+     *
+     * @param module - shadow parameter from IpcDataCache in Frameworks.
+     * @param api - shadow parameter from IpcDataCache in Frameworks.
+     */
+    public static void invalidateCache(String module, String api) {
+    }
+
+}
diff --git a/tools/processors/property_cache/test/resources/Custom.java b/tools/processors/property_cache/test/resources/Custom.java
new file mode 100644
index 0000000..05024da
--- /dev/null
+++ b/tools/processors/property_cache/test/resources/Custom.java
@@ -0,0 +1,127 @@
+/*
+ * 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.processor.property_cache.test;
+
+import com.android.internal.annotations.CacheModifier;
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
+
+import java.util.Date;
+
+@CachedPropertyDefaults(max = 4, module = "bluetooth")
+public class Custom {
+    BirthdayManagerService mService = new BirthdayManagerService();
+    Object mCache = new CustomCache();
+
+    public Custom() {
+        CustomCache.initCache();
+    }
+
+    /**
+     * Testing custom class values to generate static IpcDataCache
+     *
+     * @param userId - user Id
+     * @return birthday date of given user Id
+     */
+    @CachedProperty()
+    public Date getBirthday(int userId) {
+        return CustomCache.getBirthday(mService::getBirthday, userId);
+    }
+
+    /**
+     * Testing custom class values to generate static IpcDataCache
+     *
+     * @param userId - user Id
+     * @return number of days till birthday of given user Id
+     */
+    @CachedProperty(modsFlagOnOrNone = {CacheModifier.STATIC})
+    public int getDaysTillBirthday(int userId) {
+        return CustomCache.getDaysTillBirthday(mService::getDaysTillBirthday, userId);
+    }
+
+    /**
+     * Testing custom class values to generate non-static IpcDataCache
+     *
+     * @param userId - user Id
+     * @return number of days since birthday of given user Id
+     */
+    @CachedProperty(modsFlagOnOrNone = {})
+    public int getDaysSinceBirthday(int userId) {
+        return ((CustomCache) mCache).getDaysSinceBirthday(mService::getDaysSinceBirthday, userId);
+    }
+
+    /**
+     * Testing custom class values to generate static IpcDataCache with max capasity of 1
+     *
+     * @return number of days till birthay of current user
+     */
+    @CachedProperty(modsFlagOnOrNone = {CacheModifier.STATIC}, max = 1)
+    public int getDaysTillMyBirthday() {
+        return CustomCache.getDaysTillMyBirthday((Void) -> mService.getDaysTillMyBirthday());
+    }
+
+    /**
+     * Testing custom class values to generate static IpcDataCache with max capasity of 1 and custom
+     * api
+     *
+     * @return number of days since birthay of current user
+     */
+    @CachedProperty(modsFlagOnOrNone = {}, max = 1, api = "my_unique_key")
+    public int getDaysSinceMyBirthday() {
+        return ((CustomCache) mCache).getDaysSinceMyBirthday(
+                (Void) -> mService.getDaysSinceMyBirthday());
+    }
+
+    /**
+     * Testing custom class values to generate static IpcDataCache with custom module name
+     *
+     * @return birthday wishes of given user Id
+     */
+    @CachedProperty(module = "telephony")
+    public String getBirthdayWishesFromUser(int userId) {
+        return CustomCache.getBirthdayWishesFromUser(mService::getBirthdayWishesFromUser,
+                userId);
+    }
+
+    class BirthdayManagerService {
+        int mDaysTillBirthday = 182;
+
+        public Date getBirthday(int userId) {
+            return new Date(2024, 6, 1 + userId);
+        }
+
+        public int getDaysTillBirthday(int userId) {
+            return mDaysTillBirthday + userId;
+        }
+
+        public int getDaysSinceBirthday(int userId) {
+            return 365 - getDaysTillBirthday(userId);
+        }
+
+        public int getDaysTillMyBirthday() {
+            return 0;
+        }
+
+        public int getDaysSinceMyBirthday() {
+            return 365;
+        }
+
+        public String getBirthdayWishesFromUser(int userId) {
+            return "Happy Birthday!\n- " + userId;
+        }
+    }
+}
diff --git a/tools/processors/property_cache/test/resources/CustomCache.java b/tools/processors/property_cache/test/resources/CustomCache.java
new file mode 100644
index 0000000..326467f
--- /dev/null
+++ b/tools/processors/property_cache/test/resources/CustomCache.java
@@ -0,0 +1,384 @@
+/*
+ * 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.processor.property_cache.test;
+
+import android.os.IpcDataCache;
+
+/**
+ * This class is auto-generated
+ *
+ * @hide
+ **/
+public class CustomCache {
+    private static final Object sBirthdayLock = new Object();
+    private static IpcDataCache<java.lang.Integer, java.util.Date> sBirthday;
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall      - lambda for remote call
+     *                        {@link  android.processor.property_cache.test.Custom#getBirthday }
+     * @param bypassPredicate - lambda to bypass remote call
+     * @param query           - parameter to call remote lambda
+     * @hide
+     */
+    public static java.util.Date getBirthday(
+            IpcDataCache.RemoteCall<java.lang.Integer, java.util.Date> binderCall,
+            IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query) {
+        if (sBirthday != null) {
+            return sBirthday.query(query);
+        }
+        synchronized (sBirthdayLock) {
+            if (sBirthday == null) {
+                sBirthday = new IpcDataCache(
+                        new IpcDataCache.Config(4, "bluetooth", "custom_birthday", "Birthday"),
+                        binderCall, bypassPredicate);
+
+            }
+        }
+        return sBirthday.query(query);
+    }
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall - lambda for remote call
+     *                   {@link  android.processor.property_cache.test.Custom#getBirthday }
+     * @param query      - parameter to call remote lambda
+     * @hide
+     */
+    public static java.util.Date getBirthday(
+            IpcDataCache.RemoteCall<java.lang.Integer, java.util.Date> binderCall,
+            java.lang.Integer query) {
+        if (sBirthday != null) {
+            return sBirthday.query(query);
+        }
+        synchronized (sBirthdayLock) {
+            if (sBirthday == null) {
+                sBirthday = new IpcDataCache(
+                        new IpcDataCache.Config(4, "bluetooth", "custom_birthday", "Birthday"),
+                        binderCall);
+            }
+        }
+        return sBirthday.query(query);
+    }
+
+    /**
+     * This method is auto-generated- invalidate cache for
+     * {@link  android.processor.property_cache.test.Custom#getBirthday}
+     *
+     * @hide
+     */
+    public static final void invalidateBirthday() {
+        IpcDataCache.invalidateCache("bluetooth", "custom_birthday");
+    }
+
+    private static final Object sDaysTillBirthdayLock = new Object();
+    private static IpcDataCache<java.lang.Integer, java.lang.Integer> sDaysTillBirthday;
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall      - lambda for remote call
+     *                        {@link
+     *                        android.processor.property_cache.test.Custom#getDaysTillBirthday }
+     * @param bypassPredicate - lambda to bypass remote call
+     * @param query           - parameter to call remote lambda
+     * @hide
+     */
+    public static java.lang.Integer getDaysTillBirthday(
+            IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+            IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query) {
+        if (sDaysTillBirthday != null) {
+            return sDaysTillBirthday.query(query);
+        }
+        synchronized (sDaysTillBirthdayLock) {
+            if (sDaysTillBirthday == null) {
+                sDaysTillBirthday = new IpcDataCache(
+                        new IpcDataCache.Config(4, "bluetooth", "custom_days_till_birthday",
+                                "DaysTillBirthday"), binderCall, bypassPredicate);
+
+            }
+        }
+        return sDaysTillBirthday.query(query);
+    }
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall - lambda for remote call
+     *                   {@link  android.processor.property_cache.test.Custom#getDaysTillBirthday }
+     * @param query      - parameter to call remote lambda
+     * @hide
+     */
+    public static java.lang.Integer getDaysTillBirthday(
+            IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+            java.lang.Integer query) {
+        if (sDaysTillBirthday != null) {
+            return sDaysTillBirthday.query(query);
+        }
+        synchronized (sDaysTillBirthdayLock) {
+            if (sDaysTillBirthday == null) {
+                sDaysTillBirthday = new IpcDataCache(
+                        new IpcDataCache.Config(4, "bluetooth", "custom_days_till_birthday",
+                                "DaysTillBirthday"), binderCall);
+
+            }
+        }
+        return sDaysTillBirthday.query(query);
+    }
+
+    /**
+     * This method is auto-generated- invalidate cache for
+     * {@link  android.processor.property_cache.test.Custom#getDaysTillBirthday}
+     *
+     * @hide
+     */
+    public static final void invalidateDaysTillBirthday() {
+        IpcDataCache.invalidateCache("bluetooth", "custom_days_till_birthday");
+    }
+
+    private final Object mDaysSinceBirthdayLock = new Object();
+    private IpcDataCache<java.lang.Integer, java.lang.Integer> mDaysSinceBirthday;
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall      - lambda for remote call
+     *                        {@link
+     *                        android.processor.property_cache.test.Custom#getDaysSinceBirthday }
+     * @param bypassPredicate - lambda to bypass remote call
+     * @param query           - parameter to call remote lambda
+     * @hide
+     */
+    public java.lang.Integer getDaysSinceBirthday(
+            IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+            IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query) {
+        if (mDaysSinceBirthday != null) {
+            return mDaysSinceBirthday.query(query);
+        }
+        synchronized (mDaysSinceBirthdayLock) {
+            if (mDaysSinceBirthday == null) {
+                mDaysSinceBirthday = new IpcDataCache(
+                        new IpcDataCache.Config(4, "bluetooth", "custom_days_since_birthday",
+                                "DaysSinceBirthday"), binderCall, bypassPredicate);
+
+            }
+        }
+        return mDaysSinceBirthday.query(query);
+    }
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall - lambda for remote call
+     *                   {@link  android.processor.property_cache.test.Custom#getDaysSinceBirthday
+     *                   }
+     * @param query      - parameter to call remote lambda
+     * @hide
+     */
+    public java.lang.Integer getDaysSinceBirthday(
+            IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+            java.lang.Integer query) {
+        if (mDaysSinceBirthday != null) {
+            return mDaysSinceBirthday.query(query);
+        }
+        synchronized (mDaysSinceBirthdayLock) {
+            if (mDaysSinceBirthday == null) {
+                mDaysSinceBirthday = new IpcDataCache(
+                        new IpcDataCache.Config(4, "bluetooth", "custom_days_since_birthday",
+                                "DaysSinceBirthday"), binderCall);
+
+            }
+        }
+        return mDaysSinceBirthday.query(query);
+    }
+
+    /**
+     * This method is auto-generated- invalidate cache for
+     * {@link  android.processor.property_cache.test.Custom#getDaysSinceBirthday}
+     *
+     * @hide
+     */
+    public static final void invalidateDaysSinceBirthday() {
+        IpcDataCache.invalidateCache("bluetooth", "custom_days_since_birthday");
+    }
+
+    private static final Object sDaysTillMyBirthdayLock = new Object();
+    private static IpcDataCache<java.lang.Void, java.lang.Integer> sDaysTillMyBirthday;
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall - lambda for remote call
+     *                   {@link  android.processor.property_cache.test.Custom#getDaysTillMyBirthday
+     *                   }
+     * @hide
+     */
+    public static java.lang.Integer getDaysTillMyBirthday(
+            IpcDataCache.RemoteCall<java.lang.Void, java.lang.Integer> binderCall) {
+        if (sDaysTillMyBirthday != null) {
+            return sDaysTillMyBirthday.query(null);
+        }
+        synchronized (sDaysTillMyBirthdayLock) {
+            if (sDaysTillMyBirthday == null) {
+                sDaysTillMyBirthday = new IpcDataCache(
+                        new IpcDataCache.Config(1, "bluetooth", "custom_days_till_my_birthday",
+                                "DaysTillMyBirthday"), binderCall);
+
+            }
+        }
+        return sDaysTillMyBirthday.query(null);
+    }
+
+    /**
+     * This method is auto-generated- invalidate cache for
+     * {@link  android.processor.property_cache.test.Custom#getDaysTillMyBirthday}
+     *
+     * @hide
+     */
+    public static final void invalidateDaysTillMyBirthday() {
+        IpcDataCache.invalidateCache("bluetooth", "custom_days_till_my_birthday");
+    }
+
+    private final Object mDaysSinceMyBirthdayLock = new Object();
+    private IpcDataCache<java.lang.Void, java.lang.Integer> mDaysSinceMyBirthday;
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall - lambda for remote call
+     *                   {@link  android.processor.property_cache.test.Custom#getDaysSinceMyBirthday
+     *                   }
+     * @hide
+     */
+    public java.lang.Integer getDaysSinceMyBirthday(
+            IpcDataCache.RemoteCall<java.lang.Void, java.lang.Integer> binderCall) {
+        if (mDaysSinceMyBirthday != null) {
+            return mDaysSinceMyBirthday.query(null);
+        }
+        synchronized (mDaysSinceMyBirthdayLock) {
+            if (mDaysSinceMyBirthday == null) {
+                mDaysSinceMyBirthday = new IpcDataCache(
+                        new IpcDataCache.Config(1, "bluetooth", "my_unique_key",
+                                "DaysSinceMyBirthday"), binderCall);
+
+            }
+        }
+        return mDaysSinceMyBirthday.query(null);
+    }
+
+    /**
+     * This method is auto-generated- invalidate cache for
+     * {@link  android.processor.property_cache.test.Custom#getDaysSinceMyBirthday}
+     *
+     * @hide
+     */
+    public static final void invalidateDaysSinceMyBirthday() {
+        IpcDataCache.invalidateCache("bluetooth", "my_unique_key");
+    }
+
+    private static final Object sBirthdayWishesFromUserLock = new Object();
+    private static IpcDataCache<java.lang.Integer, java.lang.String> sBirthdayWishesFromUser;
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall      - lambda for remote call
+     *                        {@link
+     *                        android.processor.property_cache.test.Custom#getBirthdayWishesFromUser
+     *                        }
+     * @param bypassPredicate - lambda to bypass remote call
+     * @param query           - parameter to call remote lambda
+     * @hide
+     */
+    public static java.lang.String getBirthdayWishesFromUser(
+            IpcDataCache.RemoteCall<java.lang.Integer, java.lang.String> binderCall,
+            IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query) {
+        if (sBirthdayWishesFromUser != null) {
+            return sBirthdayWishesFromUser.query(query);
+        }
+        synchronized (sBirthdayWishesFromUserLock) {
+            if (sBirthdayWishesFromUser == null) {
+                sBirthdayWishesFromUser = new IpcDataCache(
+                        new IpcDataCache.Config(4, "telephony", "custom_birthday_wishes_from_user",
+                                "BirthdayWishesFromUser"), binderCall, bypassPredicate);
+
+            }
+        }
+        return sBirthdayWishesFromUser.query(query);
+    }
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall - lambda for remote call
+     *                   {@link
+     *                   android.processor.property_cache.test.Custom#getBirthdayWishesFromUser }
+     * @param query      - parameter to call remote lambda
+     * @hide
+     */
+    public static java.lang.String getBirthdayWishesFromUser(
+            IpcDataCache.RemoteCall<java.lang.Integer, java.lang.String> binderCall,
+            java.lang.Integer query) {
+        if (sBirthdayWishesFromUser != null) {
+            return sBirthdayWishesFromUser.query(query);
+        }
+        synchronized (sBirthdayWishesFromUserLock) {
+            if (sBirthdayWishesFromUser == null) {
+                sBirthdayWishesFromUser = new IpcDataCache(
+                        new IpcDataCache.Config(4, "telephony", "custom_birthday_wishes_from_user",
+                                "BirthdayWishesFromUser"), binderCall);
+
+            }
+        }
+        return sBirthdayWishesFromUser.query(query);
+    }
+
+    /**
+     * This method is auto-generated- invalidate cache for
+     * {@link  android.processor.property_cache.test.Custom#getBirthdayWishesFromUser}
+     *
+     * @hide
+     */
+    public static final void invalidateBirthdayWishesFromUser() {
+        IpcDataCache.invalidateCache("telephony", "custom_birthday_wishes_from_user");
+    }
+
+
+    /**
+     * This method is auto-generated - initialise all caches for class CustomCache
+     *
+     * @hide
+     */
+    public static void initCache() {
+        CustomCache.invalidateBirthday();
+        CustomCache.invalidateDaysTillBirthday();
+        CustomCache.invalidateDaysSinceBirthday();
+        CustomCache.invalidateDaysTillMyBirthday();
+        CustomCache.invalidateDaysSinceMyBirthday();
+        CustomCache.invalidateBirthdayWishesFromUser();
+    }
+}
diff --git a/tools/processors/property_cache/test/resources/Default.java b/tools/processors/property_cache/test/resources/Default.java
new file mode 100644
index 0000000..d2449aa
--- /dev/null
+++ b/tools/processors/property_cache/test/resources/Default.java
@@ -0,0 +1,125 @@
+/*
+ * 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.processor.property_cache.test;
+
+import com.android.internal.annotations.CacheModifier;
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
+
+import java.util.Date;
+
+@CachedPropertyDefaults()
+public class Default {
+    BirthdayManagerService mService = new BirthdayManagerService();
+    Object mCache = new DefaultCache();
+
+    /** Testing default class values to generate static IpcDataCache
+     *
+     * @param userId - user Id
+     * @return birthday date of given user Id
+     */
+    @CachedProperty()
+    public Date getBirthday(int userId) {
+        return DefaultCache.getBirthday(mService::getBirthday, userId);
+    }
+
+    /** Testing default class values to generate static IpcDataCache
+     *
+     * @param userId - user Id
+     * @return number of days till birthday of given user Id
+     */
+    @CachedProperty(modsFlagOnOrNone = {CacheModifier.STATIC})
+    public int getDaysTillBirthday(int userId) {
+        return DefaultCache.getDaysTillBirthday(mService::getDaysTillBirthday, userId);
+    }
+
+    /** Testing generate non-static IpcDataCache
+     *
+     * @param userId - user Id
+     * @return number of days since birthday of given user Id
+     */
+    @CachedProperty(modsFlagOnOrNone = {})
+    public int getDaysSinceBirthday(int userId) {
+        return ((DefaultCache) mCache).getDaysSinceBirthday(mService::getDaysSinceBirthday, userId);
+    }
+
+    /** Testing default class values to generate static IpcDataCache with max capacity of 1
+     *
+     * @return number of days till birthay of current user
+     */
+    @CachedProperty(
+            modsFlagOnOrNone = {CacheModifier.STATIC},
+            max = 1)
+    public int getDaysTillMyBirthday() {
+        return DefaultCache.getDaysTillMyBirthday((Void) -> mService.getDaysTillMyBirthday());
+    }
+
+    /** Testing default class values to generate static IpcDataCache with max capacity of 1 and
+    custom api
+     *
+     * @return number of days since birthay of current user
+     */
+    @CachedProperty(
+            modsFlagOnOrNone = {},
+            max = 1,
+            api = "my_unique_key")
+    public int getDaysSinceMyBirthday() {
+        return ((DefaultCache) mCache).getDaysSinceMyBirthday(
+                (Void) -> mService.getDaysSinceMyBirthday());
+    }
+
+    /** Testing default class values to generate static IpcDataCache with custom module name
+     *
+     * @return birthday wishes of given user Id
+     */
+    @CachedProperty(module = "telephony")
+    public String getBirthdayWishesFromUser(int userId) {
+        return DefaultCache.getBirthdayWishesFromUser(mService::getBirthdayWishesFromUser,
+                userId);
+    }
+
+    class BirthdayManagerService {
+
+        BirthdayManagerService() {
+            DefaultCache.initCache();
+        }
+
+        public Date getBirthday(int userId) {
+            return new Date();
+        }
+
+        public int getDaysTillBirthday(int userId) {
+            return 0;
+        }
+
+        public int getDaysSinceBirthday(int userId) {
+            return 0;
+        }
+
+        public int getDaysTillMyBirthday() {
+            return 0;
+        }
+
+        public int getDaysSinceMyBirthday() {
+            return 0;
+        }
+
+        public String getBirthdayWishesFromUser(int userId) {
+            return "Happy Birthday!\n- " + userId;
+        }
+    }
+}
diff --git a/tools/processors/property_cache/test/resources/DefaultCache.java b/tools/processors/property_cache/test/resources/DefaultCache.java
new file mode 100644
index 0000000..9531118
--- /dev/null
+++ b/tools/processors/property_cache/test/resources/DefaultCache.java
@@ -0,0 +1,402 @@
+/*
+ * 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.processor.property_cache.test;
+
+import android.os.IpcDataCache;
+
+/**
+ * This class is auto-generated
+ *
+ * @hide
+ **/
+public class DefaultCache {
+    private static final Object sBirthdayLock = new Object();
+    private static IpcDataCache<java.lang.Integer, java.util.Date> sBirthday;
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall      - lambda for remote call
+     *                        {@link  android.processor.property_cache.test.Default#getBirthday }
+     * @param bypassPredicate - lambda to bypass remote call
+     * @param query           - parameter to call remote lambda
+     * @hide
+     */
+    public static java.util.Date getBirthday(
+            IpcDataCache.RemoteCall<java.lang.Integer, java.util.Date> binderCall,
+            IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query
+    ) {
+        if (sBirthday != null) {
+            return sBirthday.query(query);
+        }
+        synchronized (sBirthdayLock) {
+            if (sBirthday == null) {
+                sBirthday = new IpcDataCache(
+                        new IpcDataCache.Config(32, "system_server",
+                                "default_birthday", "Birthday"),
+                        binderCall, bypassPredicate);
+
+            }
+        }
+        return sBirthday.query(query);
+    }
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall - lambda for remote call
+     *                   {@link  android.processor.property_cache.test.Default#getBirthday }
+     * @param query      - parameter to call remote lambda
+     * @hide
+     */
+    public static java.util.Date getBirthday(
+            IpcDataCache.RemoteCall<java.lang.Integer, java.util.Date> binderCall,
+            java.lang.Integer query
+    ) {
+        if (sBirthday != null) {
+            return sBirthday.query(query);
+        }
+        synchronized (sBirthdayLock) {
+            if (sBirthday == null) {
+                sBirthday = new IpcDataCache(
+                        new IpcDataCache.Config(32, "system_server",
+                                "default_birthday", "Birthday"),
+                        binderCall);
+
+            }
+        }
+        return sBirthday.query(query);
+    }
+
+    /**
+     * This method is auto-generated- invalidate cache for
+     * {@link  android.processor.property_cache.test.Default#getBirthday}
+     *
+     * @hide
+     */
+    public static final void invalidateBirthday() {
+        IpcDataCache.invalidateCache("system_server", "default_birthday");
+    }
+
+    private static final Object sDaysTillBirthdayLock = new Object();
+    private static IpcDataCache<java.lang.Integer, java.lang.Integer> sDaysTillBirthday;
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall      - lambda for remote call
+     *                        {@link
+     *                        android.processor.property_cache.test.Default#getDaysTillBirthday }
+     * @param bypassPredicate - lambda to bypass remote call
+     * @param query           - parameter to call remote lambda
+     * @hide
+     */
+    public static java.lang.Integer getDaysTillBirthday(
+            IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+            IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query
+    ) {
+        if (sDaysTillBirthday != null) {
+            return sDaysTillBirthday.query(query);
+        }
+        synchronized (sDaysTillBirthdayLock) {
+            if (sDaysTillBirthday == null) {
+                sDaysTillBirthday = new IpcDataCache(
+                        new IpcDataCache.Config(32, "system_server", "default_days_till_birthday",
+                                "DaysTillBirthday"), binderCall, bypassPredicate);
+
+            }
+        }
+        return sDaysTillBirthday.query(query);
+    }
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall - lambda for remote call
+     *                   {@link  android.processor.property_cache.test.Default#getDaysTillBirthday
+     *                   }
+     * @param query      - parameter to call remote lambda
+     * @hide
+     */
+    public static java.lang.Integer getDaysTillBirthday(
+            IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+            java.lang.Integer query
+    ) {
+        if (sDaysTillBirthday != null) {
+            return sDaysTillBirthday.query(query);
+        }
+        synchronized (sDaysTillBirthdayLock) {
+            if (sDaysTillBirthday == null) {
+                sDaysTillBirthday = new IpcDataCache(
+                        new IpcDataCache.Config(32, "system_server", "default_days_till_birthday",
+                                "DaysTillBirthday"), binderCall);
+
+            }
+        }
+        return sDaysTillBirthday.query(query);
+    }
+
+    /**
+     * This method is auto-generated- invalidate cache for
+     * {@link  android.processor.property_cache.test.Default#getDaysTillBirthday}
+     *
+     * @hide
+     */
+    public static final void invalidateDaysTillBirthday() {
+        IpcDataCache.invalidateCache("system_server", "default_days_till_birthday");
+    }
+
+    private final Object mDaysSinceBirthdayLock = new Object();
+    private IpcDataCache<java.lang.Integer, java.lang.Integer> mDaysSinceBirthday;
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall      - lambda for remote call
+     *                        {@link
+     *                        android.processor.property_cache.test.Default#getDaysSinceBirthday }
+     * @param bypassPredicate - lambda to bypass remote call
+     * @param query           - parameter to call remote lambda
+     * @hide
+     */
+    public java.lang.Integer getDaysSinceBirthday(
+            IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+            IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query
+    ) {
+        if (mDaysSinceBirthday != null) {
+            return mDaysSinceBirthday.query(query);
+        }
+        synchronized (mDaysSinceBirthdayLock) {
+            if (mDaysSinceBirthday == null) {
+                mDaysSinceBirthday = new IpcDataCache(
+                        new IpcDataCache.Config(32, "system_server", "default_days_since_birthday",
+                                "DaysSinceBirthday"), binderCall, bypassPredicate);
+
+            }
+        }
+        return mDaysSinceBirthday.query(query);
+    }
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall - lambda for remote call
+     *                   {@link  android.processor.property_cache.test.Default#getDaysSinceBirthday
+     *                   }
+     * @param query      - parameter to call remote lambda
+     * @hide
+     */
+    public java.lang.Integer getDaysSinceBirthday(
+            IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+            java.lang.Integer query
+    ) {
+        if (mDaysSinceBirthday != null) {
+            return mDaysSinceBirthday.query(query);
+        }
+        synchronized (mDaysSinceBirthdayLock) {
+            if (mDaysSinceBirthday == null) {
+                mDaysSinceBirthday = new IpcDataCache(
+                        new IpcDataCache.Config(32, "system_server", "default_days_since_birthday",
+                                "DaysSinceBirthday"), binderCall);
+
+            }
+        }
+        return mDaysSinceBirthday.query(query);
+    }
+
+    /**
+     * This method is auto-generated- invalidate cache for
+     * {@link  android.processor.property_cache.test.Default#getDaysSinceBirthday}
+     *
+     * @hide
+     */
+    public static final void invalidateDaysSinceBirthday() {
+        IpcDataCache.invalidateCache("system_server", "default_days_since_birthday");
+    }
+
+    private static final Object sDaysTillMyBirthdayLock = new Object();
+    private static IpcDataCache<java.lang.Void, java.lang.Integer> sDaysTillMyBirthday;
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall - lambda for remote call
+     *                   {@link  android.processor.property_cache.test.Default#getDaysTillMyBirthday
+     *                   }
+     * @hide
+     */
+    public static java.lang.Integer getDaysTillMyBirthday(
+            IpcDataCache.RemoteCall<java.lang.Void, java.lang.Integer> binderCall
+    ) {
+        if (sDaysTillMyBirthday != null) {
+            return sDaysTillMyBirthday.query(null);
+        }
+        synchronized (sDaysTillMyBirthdayLock) {
+            if (sDaysTillMyBirthday == null) {
+                sDaysTillMyBirthday = new IpcDataCache(
+                        new IpcDataCache.Config(1, "system_server", "default_days_till_my_birthday",
+                                "DaysTillMyBirthday"), binderCall);
+
+            }
+        }
+        return sDaysTillMyBirthday.query(null);
+    }
+
+    /**
+     * This method is auto-generated- invalidate cache for
+     * {@link  android.processor.property_cache.test.Default#getDaysTillMyBirthday}
+     *
+     * @hide
+     */
+    public static final void invalidateDaysTillMyBirthday() {
+        IpcDataCache.invalidateCache("system_server", "default_days_till_my_birthday");
+    }
+
+    private final Object mDaysSinceMyBirthdayLock = new Object();
+    private IpcDataCache<java.lang.Void, java.lang.Integer> mDaysSinceMyBirthday;
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall - lambda for remote call
+     *                   {@link
+     *                   android.processor.property_cache.test.Default#getDaysSinceMyBirthday }
+     * @hide
+     */
+    public java.lang.Integer getDaysSinceMyBirthday(
+            IpcDataCache.RemoteCall<java.lang.Void, java.lang.Integer> binderCall
+    ) {
+        if (mDaysSinceMyBirthday != null) {
+            return mDaysSinceMyBirthday.query(null);
+        }
+        synchronized (mDaysSinceMyBirthdayLock) {
+            if (mDaysSinceMyBirthday == null) {
+                mDaysSinceMyBirthday = new IpcDataCache(
+                        new IpcDataCache.Config(1, "system_server", "my_unique_key",
+                                "DaysSinceMyBirthday"), binderCall);
+
+            }
+        }
+        return mDaysSinceMyBirthday.query(null);
+    }
+
+    /**
+     * This method is auto-generated- invalidate cache for
+     * {@link  android.processor.property_cache.test.Default#getDaysSinceMyBirthday}
+     *
+     * @hide
+     */
+    public static final void invalidateDaysSinceMyBirthday() {
+        IpcDataCache.invalidateCache("system_server", "my_unique_key");
+    }
+
+    private static final Object sBirthdayWishesFromUserLock = new Object();
+    private static IpcDataCache<java.lang.Integer, java.lang.String> sBirthdayWishesFromUser;
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall      - lambda for remote call
+     *                        {@link
+     *
+     *                       android.processor.property_cache.test.Default#getBirthdayWishesFromUser
+     *                        }
+     * @param bypassPredicate - lambda to bypass remote call
+     * @param query           - parameter to call remote lambda
+     * @hide
+     */
+    public static java.lang.String getBirthdayWishesFromUser(
+            IpcDataCache.RemoteCall<java.lang.Integer, java.lang.String> binderCall,
+            IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query
+    ) {
+        if (sBirthdayWishesFromUser != null) {
+            return sBirthdayWishesFromUser.query(query);
+        }
+        synchronized (sBirthdayWishesFromUserLock) {
+            if (sBirthdayWishesFromUser == null) {
+                sBirthdayWishesFromUser = new IpcDataCache(
+                        new IpcDataCache.Config(32, "telephony",
+                                "default_birthday_wishes_from_user",
+                                "BirthdayWishesFromUser"), binderCall, bypassPredicate);
+
+            }
+        }
+        return sBirthdayWishesFromUser.query(query);
+    }
+
+
+    /**
+     * This method is auto-generated
+     *
+     * @param binderCall - lambda for remote call
+     *                   {@link
+     *                   android.processor.property_cache.test.Default#getBirthdayWishesFromUser }
+     * @param query      - parameter to call remote lambda
+     * @hide
+     */
+    public static java.lang.String getBirthdayWishesFromUser(
+            IpcDataCache.RemoteCall<java.lang.Integer, java.lang.String> binderCall,
+            java.lang.Integer query
+    ) {
+        if (sBirthdayWishesFromUser != null) {
+            return sBirthdayWishesFromUser.query(query);
+        }
+        synchronized (sBirthdayWishesFromUserLock) {
+            if (sBirthdayWishesFromUser == null) {
+                sBirthdayWishesFromUser = new IpcDataCache(
+                        new IpcDataCache.Config(32, "telephony",
+                                "default_birthday_wishes_from_user",
+                                "BirthdayWishesFromUser"), binderCall);
+
+            }
+        }
+        return sBirthdayWishesFromUser.query(query);
+    }
+
+    /**
+     * This method is auto-generated- invalidate cache for
+     * {@link  android.processor.property_cache.test.Default#getBirthdayWishesFromUser}
+     *
+     * @hide
+     */
+    public static final void invalidateBirthdayWishesFromUser() {
+        IpcDataCache.invalidateCache("telephony", "default_birthday_wishes_from_user");
+    }
+
+
+    /**
+     * This method is auto-generated - initialise all caches for class DefaultCache
+     *
+     * @hide
+     */
+    public static void initCache() {
+        DefaultCache.invalidateBirthday();
+        DefaultCache.invalidateDaysTillBirthday();
+        DefaultCache.invalidateDaysSinceBirthday();
+        DefaultCache.invalidateDaysTillMyBirthday();
+        DefaultCache.invalidateDaysSinceMyBirthday();
+        DefaultCache.invalidateBirthdayWishesFromUser();
+    }
+}