Merge "Have zygote mount appcompat system properties" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 2457e70..372ae6d 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -53,6 +53,7 @@
     ":android.credentials.flags-aconfig-java{.generated_srcjars}",
     ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
     ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
+    ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
     ":aconfig_midi_flags_java_lib{.generated_srcjars}",
     ":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
     ":com.android.net.flags-aconfig-java{.generated_srcjars}",
@@ -379,6 +380,19 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Media TV
+aconfig_declarations {
+    name: "android.media.tv.flags-aconfig",
+    package: "android.media.tv.flags",
+    srcs: ["media/java/android/media/tv/flags/media_tv.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.media.tv.flags-aconfig-java",
+    aconfig_declarations: "android.media.tv.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Media Audio
 java_aconfig_library {
     name: "com.android.media.audio.flags-aconfig-java",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 1287cb4..23b36e2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1820,7 +1820,11 @@
                     jobStatus.getEstimatedNetworkUploadBytes(),
                     jobStatus.getWorkCount(),
                     ActivityManager.processStateAmToProto(mUidProcStates.get(jobStatus.getUid())),
-                    jobStatus.getNamespaceHash());
+                    jobStatus.getNamespaceHash(),
+                    /* system_measured_source_download_bytes */0,
+                    /* system_measured_source_upload_bytes */ 0,
+                    /* system_measured_calling_download_bytes */0,
+                    /* system_measured_calling_upload_bytes */ 0);
 
             // If the job is immediately ready to run, then we can just immediately
             // put it in the pending list and try to schedule it.  This is especially
@@ -2257,7 +2261,11 @@
                     cancelled.getEstimatedNetworkUploadBytes(),
                     cancelled.getWorkCount(),
                     ActivityManager.processStateAmToProto(mUidProcStates.get(cancelled.getUid())),
-                    cancelled.getNamespaceHash());
+                    cancelled.getNamespaceHash(),
+                    /* system_measured_source_download_bytes */ 0,
+                    /* system_measured_source_upload_bytes */ 0,
+                    /* system_measured_calling_download_bytes */0,
+                    /* system_measured_calling_upload_bytes */ 0);
         }
         // If this is a replacement, bring in the new version of the job
         if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 79653f0..2d49cfb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -45,6 +45,7 @@
 import android.content.PermissionChecker;
 import android.content.ServiceConnection;
 import android.net.Network;
+import android.net.TrafficStats;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -241,6 +242,14 @@
     private int mDeathMarkInternalStopReason;
     private String mDeathMarkDebugReason;
 
+    private long mInitialDownloadedBytesFromSource;
+
+    private long mInitialUploadedBytesFromSource;
+
+    private long mInitialDownloadedBytesFromCalling;
+
+    private long mInitialUploadedBytesFromCalling;
+
     // Debugging: reason this job was last stopped.
     public String mStoppedReason;
 
@@ -472,6 +481,14 @@
             }
             mJobPackageTracker.noteActive(job);
             final int sourceUid = job.getSourceUid();
+
+            // Measure UID baseline traffic for deltas
+            mInitialDownloadedBytesFromSource = TrafficStats.getUidRxBytes(sourceUid);
+            mInitialUploadedBytesFromSource = TrafficStats.getUidTxBytes(sourceUid);
+
+            mInitialDownloadedBytesFromCalling = TrafficStats.getUidRxBytes(job.getUid());
+            mInitialUploadedBytesFromCalling = TrafficStats.getUidTxBytes(job.getUid());
+
             FrameworkStatsLog.write(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
                     job.isProxyJob() ? new int[]{sourceUid, job.getUid()} : new int[]{sourceUid},
                     // Given that the source tag is set by the calling app, it should be connected
@@ -517,7 +534,11 @@
                     job.getEstimatedNetworkUploadBytes(),
                     job.getWorkCount(),
                     ActivityManager.processStateAmToProto(mService.getUidProcState(job.getUid())),
-                    job.getNamespaceHash());
+                    job.getNamespaceHash(),
+                    /* system_measured_source_download_bytes */ 0,
+                    /* system_measured_source_upload_bytes */ 0,
+                    /* system_measured_calling_download_bytes */ 0,
+                    /* system_measured_calling_upload_bytes */ 0);
             sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
             final String sourcePackage = job.getSourcePackageName();
             if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1586,7 +1607,15 @@
                 completedJob.getWorkCount(),
                 ActivityManager
                         .processStateAmToProto(mService.getUidProcState(completedJob.getUid())),
-                completedJob.getNamespaceHash());
+                completedJob.getNamespaceHash(),
+                TrafficStats.getUidRxBytes(completedJob.getSourceUid())
+                        - mInitialDownloadedBytesFromSource,
+                TrafficStats.getUidTxBytes(completedJob.getSourceUid())
+                        - mInitialUploadedBytesFromSource,
+                TrafficStats.getUidRxBytes(completedJob.getUid())
+                        - mInitialDownloadedBytesFromCalling,
+                TrafficStats.getUidTxBytes(completedJob.getUid())
+                        - mInitialUploadedBytesFromCalling);
         if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
             Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
                     getId());
diff --git a/core/api/current.txt b/core/api/current.txt
index 9962469..7e3da22 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -42599,6 +42599,7 @@
     field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telecom.extra.CALL_DISCONNECT_CAUSE";
     field public static final String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecom.extra.CALL_DISCONNECT_MESSAGE";
     field public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
+    field @FlaggedApi("com.android.server.telecom.flags.add_call_uri_for_missed_calls") public static final String EXTRA_CALL_LOG_URI = "android.telecom.extra.CALL_LOG_URI";
     field public static final String EXTRA_CALL_NETWORK_TYPE = "android.telecom.extra.CALL_NETWORK_TYPE";
     field public static final String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT";
     field public static final String EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME = "android.telecom.extra.CHANGE_DEFAULT_DIALER_PACKAGE_NAME";
@@ -45537,6 +45538,7 @@
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; // 0x2
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT = 9; // 0x9
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED = 6; // 0x6
+    field @FlaggedApi("com.android.internal.telephony.flags.slicing_additional_error_codes") public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16; // 0x10
     field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2
     field public static final int SET_OPPORTUNISTIC_SUB_NO_OPPORTUNISTIC_SUB_AVAILABLE = 3; // 0x3
     field public static final int SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION = 4; // 0x4
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5674aec..183b925 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -627,6 +627,7 @@
     field public static final String OPSTR_BIND_ACCESSIBILITY_SERVICE = "android:bind_accessibility_service";
     field public static final String OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android:capture_consentless_bugreport_on_userdebug_build";
     field public static final String OPSTR_CHANGE_WIFI_STATE = "android:change_wifi_state";
+    field @FlaggedApi("android.view.contentprotection.flags.create_accessibility_overlay_app_op_enabled") public static final String OPSTR_CREATE_ACCESSIBILITY_OVERLAY = "android:create_accessibility_overlay";
     field public static final String OPSTR_ESTABLISH_VPN_MANAGER = "android:establish_vpn_manager";
     field public static final String OPSTR_ESTABLISH_VPN_SERVICE = "android:establish_vpn_service";
     field public static final String OPSTR_GET_ACCOUNTS = "android:get_accounts";
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 7e84ceb..b03bd59 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -16,10 +16,13 @@
 
 package android.app;
 
+import static android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED;
+
 import static java.lang.Long.max;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -1495,9 +1498,17 @@
     public static final int OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
             AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
 
+    /**
+     * Creation of an overlay using accessibility services
+     *
+     * @hide
+     */
+    public static final int OP_CREATE_ACCESSIBILITY_OVERLAY =
+            AppProtoEnums.APP_OP_CREATE_ACCESSIBILITY_OVERLAY;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 138;
+    public static final int _NUM_OP = 139;
 
     /**
      * All app ops represented as strings.
@@ -1641,7 +1652,8 @@
             OPSTR_CAMERA_SANDBOXED,
             OPSTR_RECORD_AUDIO_SANDBOXED,
             OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
-            OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA
+            OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
+            OPSTR_CREATE_ACCESSIBILITY_OVERLAY,
     })
     public @interface AppOpString {}
 
@@ -2270,6 +2282,16 @@
     public static final String OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
             "android:RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";
 
+    /**
+     * Creation of an overlay using accessibility services
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED)
+    public static final String OPSTR_CREATE_ACCESSIBILITY_OVERLAY =
+            "android:create_accessibility_overlay";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2819,7 +2841,11 @@
                 OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
                 "RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA")
                 .setPermission(Manifest.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA)
-                .setDefaultMode(AppOpsManager.MODE_DEFAULT).build()
+                .setDefaultMode(AppOpsManager.MODE_DEFAULT).build(),
+        new AppOpInfo.Builder(OP_CREATE_ACCESSIBILITY_OVERLAY,
+                OPSTR_CREATE_ACCESSIBILITY_OVERLAY,
+                "CREATE_ACCESSIBILITY_OVERLAY")
+                .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 9cf54e3..cc56a1c 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -36,6 +36,7 @@
 per-file GameState* = file:/GAME_MANAGER_OWNERS
 per-file IGameManager* = file:/GAME_MANAGER_OWNERS
 per-file IGameMode* = file:/GAME_MANAGER_OWNERS
+per-file BackgroundStartPrivileges.java = file:/BAL_OWNERS
 
 # ActivityThread
 per-file ActivityThread.java = file:/services/core/java/com/android/server/am/OWNERS
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 3abe1d9..c6012bb 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -562,13 +562,6 @@
         });
     }
 
-    private boolean isPostingTaskToBackground(@Nullable RemoteViews views) {
-        return Looper.myLooper() == Looper.getMainLooper()
-                && RemoteViews.isAdapterConversionEnabled()
-                && (mHasPostedLegacyLists = mHasPostedLegacyLists
-                        || (views != null && views.hasLegacyLists()));
-    }
-
     /**
      * Set the RemoteViews to use for the specified appWidgetIds.
      * <p>
@@ -593,16 +586,25 @@
             return;
         }
 
-        if (isPostingTaskToBackground(views)) {
-            createUpdateExecutorIfNull().execute(() -> {
-                try {
-                    mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error updating app widget views in background", e);
-                }
-            });
+        final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled()
+                && (mHasPostedLegacyLists = mHasPostedLegacyLists
+                        || (views != null && views.hasLegacyLists()));
 
-            return;
+        if (isConvertingAdapter) {
+            views.collectAllIntents();
+
+            if (Looper.getMainLooper() == Looper.myLooper()) {
+                RemoteViews viewsCopy = new RemoteViews(views);
+                createUpdateExecutorIfNull().execute(() -> {
+                    try {
+                        mService.updateAppWidgetIds(mPackageName, appWidgetIds, viewsCopy);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error updating app widget views in background", e);
+                    }
+                });
+
+                return;
+            }
         }
 
         try {
@@ -714,16 +716,25 @@
             return;
         }
 
-        if (isPostingTaskToBackground(views)) {
-            createUpdateExecutorIfNull().execute(() -> {
-                try {
-                    mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error partially updating app widget views in background", e);
-                }
-            });
+        final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled()
+                && (mHasPostedLegacyLists = mHasPostedLegacyLists
+                        || (views != null && views.hasLegacyLists()));
 
-            return;
+        if (isConvertingAdapter) {
+            views.collectAllIntents();
+
+            if (Looper.getMainLooper() == Looper.myLooper()) {
+                RemoteViews viewsCopy = new RemoteViews(views);
+                createUpdateExecutorIfNull().execute(() -> {
+                    try {
+                        mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, viewsCopy);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error partially updating app widget views in background", e);
+                    }
+                });
+
+                return;
+            }
         }
 
         try {
@@ -782,16 +793,26 @@
             return;
         }
 
-        if (isPostingTaskToBackground(views)) {
-            createUpdateExecutorIfNull().execute(() -> {
-                try {
-                    mService.updateAppWidgetProvider(provider, views);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error updating app widget view using provider in background", e);
-                }
-            });
+        final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled()
+                && (mHasPostedLegacyLists = mHasPostedLegacyLists
+                        || (views != null && views.hasLegacyLists()));
 
-            return;
+        if (isConvertingAdapter) {
+            views.collectAllIntents();
+
+            if (Looper.getMainLooper() == Looper.myLooper()) {
+                RemoteViews viewsCopy = new RemoteViews(views);
+                createUpdateExecutorIfNull().execute(() -> {
+                    try {
+                        mService.updateAppWidgetProvider(provider, viewsCopy);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error updating app widget view using provider in background",
+                                e);
+                    }
+                });
+
+                return;
+            }
         }
 
         try {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index ffc4805..ea54c91 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1164,6 +1164,11 @@
      * numbers.  Applications can <strong>dial</strong> emergency numbers using
      * {@link #ACTION_DIAL}, however.
      *
+     * <p>Note: This Intent can only be used to dial call forwarding MMI codes if the application
+     * using this intent is set as the default or system dialer. The system will treat any other
+     * application using this Intent for the purpose of dialing call forwarding MMI codes as if the
+     * {@link #ACTION_DIAL} Intent was used instead.
+     *
      * <p>Note: An app filling the {@link android.app.role.RoleManager#ROLE_DIALER} role should use
      * {@link android.telecom.TelecomManager#placeCall(Uri, Bundle)} to place calls rather than
      * relying on this intent.
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 524afe9..ad3ccc4 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -437,7 +437,14 @@
      * Returns {@code true} if the calling application provides a CredentialProviderService that is
      * enabled for the current user, or {@code false} otherwise. CredentialProviderServices are
      * enabled on a per-service basis so the individual component name of the service should be
-     * passed in here.
+     * passed in here. <strong>Usage of this API is discouraged as it is not fully functional, and
+     * may throw a NullPointerException on certain devices and/or API versions.</strong>
+     *
+     * @throws IllegalArgumentException if the componentName package does not match the calling
+     * package name this call will throw an exception
+     *
+     * @throws NullPointerException Usage of this API is discouraged as it is not fully
+     * functional, and may throw a NullPointerException on certain devices and/or API versions
      *
      * @param componentName the component name to check is enabled
      */
diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index 52be8f6..127d4a7 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -337,13 +337,13 @@
     @VisibleForTesting
     public final boolean matchesForCallStackDebugging(@Nullable String name, @NonNull String call) {
         final boolean matchCall = !sCallStackDebuggingMatchCall.isEmpty();
-        if (matchCall && !call.toLowerCase().contains(sCallStackDebuggingMatchCall)) {
+        if (matchCall && !sCallStackDebuggingMatchCall.contains(call.toLowerCase())) {
             // Skip if target call doesn't match requested caller
             return false;
         }
         final boolean matchName = !sCallStackDebuggingMatchName.isEmpty();
         if (matchName && (name == null
-                || !name.toLowerCase().contains(sCallStackDebuggingMatchName))) {
+                || !sCallStackDebuggingMatchName.contains(name.toLowerCase()))) {
             // Skip if target surface doesn't match requested surface
             return false;
         }
diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
index f6ee061..f3dc33c 100644
--- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
+++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
@@ -20,3 +20,10 @@
     description: "If true, content protection setting ui is displayed in Settings > Privacy & Security > More security & privacy."
     bug: "305792348"
 }
+
+flag {
+    name: "create_accessibility_overlay_app_op_enabled"
+    namespace: "content_protection"
+    description: "If true, an appop is logged on creation of accessibility overlays."
+    bug: "289081465"
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 3078801..403b403 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -85,6 +85,7 @@
 import android.util.LongArray;
 import android.util.Pair;
 import android.util.SizeF;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.TypedValue;
 import android.util.TypedValue.ComplexDimensionUnit;
@@ -367,6 +368,11 @@
     @UnsupportedAppUsage
     private BitmapCache mBitmapCache = new BitmapCache();
 
+    /**
+     * Maps Intent ID to RemoteCollectionItems to avoid duplicate items
+     */
+    private RemoteCollectionCache mCollectionCache = new RemoteCollectionCache();
+
     /** Cache of ApplicationInfos used by collection items. */
     private ApplicationInfoCache mApplicationInfoCache = new ApplicationInfoCache();
 
@@ -784,9 +790,12 @@
                 if (action instanceof SetRemoteCollectionItemListAdapterAction itemsAction
                         && itemsAction.mViewId == viewId
                         && itemsAction.mServiceIntent != null) {
-                    mActions.set(i,
-                            new SetRemoteCollectionItemListAdapterAction(itemsAction.mViewId,
-                                    itemsAction.mServiceIntent));
+                    SetRemoteCollectionItemListAdapterAction newCollectionAction =
+                            new SetRemoteCollectionItemListAdapterAction(
+                                    itemsAction.mViewId, itemsAction.mServiceIntent);
+                    newCollectionAction.mIntentId = itemsAction.mIntentId;
+                    newCollectionAction.mIsReplacedIntoAction = true;
+                    mActions.set(i, newCollectionAction);
                     isActionReplaced = true;
                 } else if (action instanceof SetRemoteViewsAdapterIntent intentAction
                         && intentAction.mViewId == viewId) {
@@ -1048,6 +1057,8 @@
         @NonNull
         private CompletableFuture<RemoteCollectionItems> mItemsFuture;
         final Intent mServiceIntent;
+        int mIntentId = -1;
+        boolean mIsReplacedIntoAction = false;
 
         SetRemoteCollectionItemListAdapterAction(@IdRes int id,
                 @NonNull RemoteCollectionItems items) {
@@ -1108,38 +1119,36 @@
 
         SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
             mViewId = parcel.readInt();
-            mItemsFuture = CompletableFuture.completedFuture(
-                    new RemoteCollectionItems(parcel, getHierarchyRootData()));
+            mIntentId = parcel.readInt();
+            mItemsFuture = CompletableFuture.completedFuture(mIntentId != -1
+                    ? null
+                    : new RemoteCollectionItems(parcel, getHierarchyRootData()));
             mServiceIntent = parcel.readTypedObject(Intent.CREATOR);
         }
 
         @Override
         public void setHierarchyRootData(HierarchyRootData rootData) {
-            mItemsFuture = mItemsFuture
-                    .thenApply(rc -> {
-                        rc.setHierarchyRootData(rootData);
-                        return rc;
-                    });
-        }
-
-        private static RemoteCollectionItems getCollectionItemsFromFuture(
-                CompletableFuture<RemoteCollectionItems> itemsFuture) {
-            RemoteCollectionItems items;
-            try {
-                items = itemsFuture.get();
-            } catch (Exception e) {
-                Log.e(LOG_TAG, "Error getting collection items from future", e);
-                items = new RemoteCollectionItems.Builder().build();
+            if (mIntentId == -1) {
+                mItemsFuture = mItemsFuture
+                        .thenApply(rc -> {
+                            rc.setHierarchyRootData(rootData);
+                            return rc;
+                        });
+                return;
             }
 
-            return items;
+            // Set the root data for items in the cache instead
+            mCollectionCache.setHierarchyDataForId(mIntentId, rootData);
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(mViewId);
-            RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
-            items.writeToParcel(dest, flags, /* attached= */ true);
+            dest.writeInt(mIntentId);
+            if (mIntentId == -1) {
+                RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+                items.writeToParcel(dest, flags, /* attached= */ true);
+            }
             dest.writeTypedObject(mServiceIntent, flags);
         }
 
@@ -1149,7 +1158,9 @@
             View target = root.findViewById(mViewId);
             if (target == null) return;
 
-            RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+            RemoteCollectionItems items = mIntentId == -1
+                    ? getCollectionItemsFromFuture(mItemsFuture)
+                    : mCollectionCache.getItemsForId(mIntentId);
 
             // Ensure that we are applying to an AppWidget root
             if (!(rootParent instanceof AppWidgetHostView)) {
@@ -1210,6 +1221,153 @@
         }
     }
 
+    private static RemoteCollectionItems getCollectionItemsFromFuture(
+            CompletableFuture<RemoteCollectionItems> itemsFuture) {
+        RemoteCollectionItems items;
+        try {
+            items = itemsFuture.get();
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Error getting collection items from future", e);
+            items = new RemoteCollectionItems.Builder().build();
+        }
+
+        return items;
+    }
+
+    /**
+     * @hide
+     */
+    public void collectAllIntents() {
+        mCollectionCache.collectAllIntentsNoComplete(this);
+    }
+
+    private class RemoteCollectionCache {
+        private SparseArray<String> mIdToUriMapping = new SparseArray<>();
+        private HashMap<String, RemoteCollectionItems> mUriToCollectionMapping = new HashMap<>();
+
+        // We don't put this into the parcel
+        private HashMap<String, CompletableFuture<RemoteCollectionItems>> mTempUriToFutureMapping =
+                new HashMap<>();
+
+        RemoteCollectionCache() { }
+
+        RemoteCollectionCache(RemoteCollectionCache src) {
+            boolean isWaitingCache = src.mTempUriToFutureMapping.size() != 0;
+            for (int i = 0; i < src.mIdToUriMapping.size(); i++) {
+                String uri = src.mIdToUriMapping.valueAt(i);
+                mIdToUriMapping.put(src.mIdToUriMapping.keyAt(i), uri);
+                if (isWaitingCache) {
+                    mTempUriToFutureMapping.put(uri, src.mTempUriToFutureMapping.get(uri));
+                } else {
+                    mUriToCollectionMapping.put(uri, src.mUriToCollectionMapping.get(uri));
+                }
+            }
+        }
+
+        RemoteCollectionCache(Parcel in) {
+            int cacheSize = in.readInt();
+            HierarchyRootData currentRootData = new HierarchyRootData(mBitmapCache,
+                    this,
+                    mApplicationInfoCache,
+                    mClassCookies);
+            for (int i = 0; i < cacheSize; i++) {
+                int intentId = in.readInt();
+                String intentUri = in.readString8();
+                RemoteCollectionItems items = new RemoteCollectionItems(in, currentRootData);
+                mIdToUriMapping.put(intentId, intentUri);
+                mUriToCollectionMapping.put(intentUri, items);
+            }
+        }
+
+        void setHierarchyDataForId(int intentId, HierarchyRootData data) {
+            String uri = mIdToUriMapping.get(intentId);
+            if (mTempUriToFutureMapping.get(uri) != null) {
+                CompletableFuture<RemoteCollectionItems> itemsFuture =
+                        mTempUriToFutureMapping.get(uri);
+                mTempUriToFutureMapping.put(uri, itemsFuture.thenApply(rc -> {
+                    rc.setHierarchyRootData(data);
+                    return rc;
+                }));
+
+                return;
+            }
+
+            RemoteCollectionItems items = mUriToCollectionMapping.get(uri);
+            items.setHierarchyRootData(data);
+        }
+
+        RemoteCollectionItems getItemsForId(int intentId) {
+            String uri = mIdToUriMapping.get(intentId);
+            return mUriToCollectionMapping.get(uri);
+        }
+
+        void collectAllIntentsNoComplete(@NonNull RemoteViews inViews) {
+            if (inViews.hasSizedRemoteViews()) {
+                for (RemoteViews remoteViews : inViews.mSizedRemoteViews) {
+                    remoteViews.collectAllIntents();
+                }
+            } else if (inViews.hasLandscapeAndPortraitLayouts()) {
+                inViews.mLandscape.collectAllIntents();
+                inViews.mPortrait.collectAllIntents();
+            } else if (inViews.mActions != null) {
+                for (Action action : inViews.mActions) {
+                    if (action instanceof SetRemoteCollectionItemListAdapterAction rca) {
+                        // Deal with the case where the intent is replaced into the action list
+                        if (rca.mIntentId != -1 && !rca.mIsReplacedIntoAction) {
+                            continue;
+                        }
+
+                        if (rca.mIntentId != -1 && rca.mIsReplacedIntoAction) {
+                            String uri = mIdToUriMapping.get(rca.mIntentId);
+                            mTempUriToFutureMapping.put(uri, rca.mItemsFuture);
+                            rca.mItemsFuture = CompletableFuture.completedFuture(null);
+                            continue;
+                        }
+
+                        // Differentiate between the normal collection actions and the ones with
+                        // intents.
+                        if (rca.mServiceIntent != null) {
+                            String uri = rca.mServiceIntent.toUri(0);
+                            int index = mIdToUriMapping.indexOfValue(uri);
+                            if (index == -1) {
+                                int newIntentId = mIdToUriMapping.size();
+                                rca.mIntentId = newIntentId;
+                                mIdToUriMapping.put(newIntentId, uri);
+                                // mUriToIntentMapping.put(uri, mServiceIntent);
+                                mTempUriToFutureMapping.put(uri, rca.mItemsFuture);
+                            } else {
+                                rca.mIntentId = mIdToUriMapping.keyAt(index);
+                            }
+                            rca.mItemsFuture = CompletableFuture.completedFuture(null);
+                        } else {
+                            RemoteCollectionItems items = getCollectionItemsFromFuture(
+                                    rca.mItemsFuture);
+                            for (RemoteViews views : items.mViews) {
+                                views.collectAllIntents();
+                            }
+                        }
+                    } else if (action instanceof ViewGroupActionAdd vgaa
+                            && vgaa.mNestedViews != null) {
+                        vgaa.mNestedViews.collectAllIntents();
+                    }
+                }
+            }
+        }
+
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(mIdToUriMapping.size());
+            for (int i = 0; i < mIdToUriMapping.size(); i++) {
+                out.writeInt(mIdToUriMapping.keyAt(i));
+                String intentUri = mIdToUriMapping.valueAt(i);
+                out.writeString8(intentUri);
+                RemoteCollectionItems items = mTempUriToFutureMapping.get(intentUri) != null
+                        ? getCollectionItemsFromFuture(mTempUriToFutureMapping.get(intentUri))
+                        : mUriToCollectionMapping.get(intentUri);
+                items.writeToParcel(out, flags, true);
+            }
+        }
+    }
+
     private class SetRemoteViewsAdapterIntent extends Action {
         Intent mIntent;
         boolean mIsAsync = false;
@@ -3850,9 +4008,12 @@
     private void initializeFrom(@NonNull RemoteViews src, @Nullable RemoteViews hierarchyRoot) {
         if (hierarchyRoot == null) {
             mBitmapCache = src.mBitmapCache;
+            // We need to create a new instance because we don't reconstruct collection cache
+            mCollectionCache = new RemoteCollectionCache(src.mCollectionCache);
             mApplicationInfoCache = src.mApplicationInfoCache;
         } else {
             mBitmapCache = hierarchyRoot.mBitmapCache;
+            mCollectionCache = hierarchyRoot.mCollectionCache;
             mApplicationInfoCache = hierarchyRoot.mApplicationInfoCache;
         }
         if (hierarchyRoot == null || src.mIsRoot) {
@@ -3926,6 +4087,7 @@
             mBitmapCache = new BitmapCache(parcel);
             // Store the class cookies such that they are available when we clone this RemoteView.
             mClassCookies = parcel.copyClassCookies();
+            mCollectionCache = new RemoteCollectionCache(parcel);
         } else {
             configureAsChild(rootData);
         }
@@ -4087,6 +4249,7 @@
     private void configureAsChild(@NonNull HierarchyRootData rootData) {
         mIsRoot = false;
         mBitmapCache = rootData.mBitmapCache;
+        mCollectionCache = rootData.mRemoteCollectionCache;
         mApplicationInfoCache = rootData.mApplicationInfoCache;
         mClassCookies = rootData.mClassCookies;
         configureDescendantsAsChildren();
@@ -6357,6 +6520,7 @@
             // is shared by all children.
             if (mIsRoot) {
                 mBitmapCache.writeBitmapsToParcel(dest, flags);
+                mCollectionCache.writeToParcel(dest, flags);
             }
             mApplication.writeToParcel(dest, flags);
             if (mIsRoot || mIdealSize == null) {
@@ -6373,6 +6537,7 @@
             dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS);
             if (mIsRoot) {
                 mBitmapCache.writeBitmapsToParcel(dest, flags);
+                mCollectionCache.writeToParcel(dest, flags);
             }
             dest.writeInt(mSizedRemoteViews.size());
             for (RemoteViews view : mSizedRemoteViews) {
@@ -6384,6 +6549,7 @@
             // is shared by all children.
             if (mIsRoot) {
                 mBitmapCache.writeBitmapsToParcel(dest, flags);
+                mCollectionCache.writeToParcel(dest, flags);
             }
             mLandscape.writeToParcel(dest, flags);
             // Both RemoteViews already share the same package and user
@@ -7262,19 +7428,23 @@
     }
 
     private HierarchyRootData getHierarchyRootData() {
-        return new HierarchyRootData(mBitmapCache, mApplicationInfoCache, mClassCookies);
+        return new HierarchyRootData(mBitmapCache, mCollectionCache,
+                mApplicationInfoCache, mClassCookies);
     }
 
     private static final class HierarchyRootData {
         final BitmapCache mBitmapCache;
+        final RemoteCollectionCache mRemoteCollectionCache;
         final ApplicationInfoCache mApplicationInfoCache;
         final Map<Class, Object> mClassCookies;
 
         HierarchyRootData(
                 BitmapCache bitmapCache,
+                RemoteCollectionCache remoteCollectionCache,
                 ApplicationInfoCache applicationInfoCache,
                 Map<Class, Object> classCookies) {
             mBitmapCache = bitmapCache;
+            mRemoteCollectionCache = remoteCollectionCache;
             mApplicationInfoCache = applicationInfoCache;
             mClassCookies = classCookies;
         }
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 7b7e341..4706dfd 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -19,6 +19,7 @@
 import android.os.IBinder;
 import android.view.RemoteAnimationDefinition;
 import android.window.ITaskFragmentOrganizer;
+import android.window.RemoteTransition;
 import android.window.WindowContainerTransaction;
 
 /** @hide */
@@ -65,7 +66,10 @@
 
     /**
      * Requests the server to apply the given {@link WindowContainerTransaction}.
+     *
+     * {@link RemoteTransition} can only be used by a system organizer and
+     * {@code shouldApplyIndependently} must be {@code true}. See {@link registerOrganizer}.
      */
     void applyTransaction(in WindowContainerTransaction wct, int transitionType,
-        boolean shouldApplyIndependently);
+        boolean shouldApplyIndependently, in RemoteTransition remoteTransition);
 }
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index a6c9cec..5c113f8 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -275,7 +275,31 @@
         }
         wct.setTaskFragmentOrganizer(mInterface);
         try {
-            getController().applyTransaction(wct, transitionType, shouldApplyIndependently);
+            getController().applyTransaction(
+                    wct, transitionType, shouldApplyIndependently, null /* remoteTransition */);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Applies a transaction with a {@link RemoteTransition}. Only a system organizer is allowed to
+     * use {@link RemoteTransition}. See {@link TaskFragmentOrganizer#registerOrganizer(boolean)}.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG)
+    public void applySystemTransaction(@NonNull WindowContainerTransaction wct,
+            @TaskFragmentTransitionType int transitionType,
+            @Nullable RemoteTransition remoteTransition) {
+        if (wct.isEmpty()) {
+            return;
+        }
+        wct.setTaskFragmentOrganizer(mInterface);
+        try {
+            getController().applyTransaction(
+                    wct, transitionType, remoteTransition != null /* shouldApplyIndependently */,
+                    remoteTransition);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/window/flags/OWNERS b/core/java/android/window/flags/OWNERS
new file mode 100644
index 0000000..fa81ee3
--- /dev/null
+++ b/core/java/android/window/flags/OWNERS
@@ -0,0 +1 @@
+per-file responsible_apis.aconfig = file:/BAL_OWNERS
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java
index 21d1dbb..5d213ca 100644
--- a/core/tests/coretests/src/android/os/PowerManagerTest.java
+++ b/core/tests/coretests/src/android/os/PowerManagerTest.java
@@ -19,11 +19,17 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.os.Flags;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.test.AndroidTestCase;
 
 import androidx.test.InstrumentationRegistry;
@@ -31,6 +37,7 @@
 import androidx.test.uiautomator.UiDevice;
 
 import org.junit.After;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -64,6 +71,10 @@
         System.loadLibrary("powermanagertest_jni");
     }
 
+    // Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect.
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     /**
      * Setup any common data for the upcoming tests.
      */
@@ -454,4 +465,27 @@
         parcelBatterySaverPolicyConfigToNativeAndVerify(bs2);
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_BATTERY_SAVER_SUPPORTED_CHECK_API)
+    public void testBatterySaverSupported_isSupported() throws RemoteException {
+        IPowerManager powerManager = mock(IPowerManager.class);
+        PowerManager pm = new PowerManager(mContext, powerManager,
+                mock(IThermalService.class),
+                Handler.createAsync(Looper.getMainLooper()));
+        when(powerManager.isBatterySaverSupported()).thenReturn(true);
+
+        assertTrue(pm.isBatterySaverSupported());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_BATTERY_SAVER_SUPPORTED_CHECK_API)
+    public void testBatterySaverSupported_isNotSupported() throws RemoteException {
+        IPowerManager powerManager = mock(IPowerManager.class);
+        PowerManager pm = new PowerManager(mContext, powerManager,
+                mock(IThermalService.class),
+                Handler.createAsync(Looper.getMainLooper()));
+        when(powerManager.isBatterySaverSupported()).thenReturn(false);
+
+        assertFalse(pm.isBatterySaverSupported());
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index 24479d7..a596cef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -353,7 +353,6 @@
         closingLeft += mapRange(interpolatedProgress, deltaXMin, deltaXMax);
 
         // Move the window along the Y axis.
-        final float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
         final float closingTop = (height - closingHeight) * 0.5f;
         targetRect.set(
                 closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 9cd318f..723a4a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -40,7 +40,6 @@
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
-import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
@@ -422,11 +421,6 @@
                 continue;
             }
 
-            // The back gesture has animated this change before transition happen, so here we don't
-            // play the animation again.
-            if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
-                continue;
-            }
             // Don't animate anything that isn't independent.
             if (!TransitionInfo.isIndependent(change, info)) continue;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index b1fc16d..030f601 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -44,7 +44,7 @@
     private IBinder mTransition = null;
 
     /** The remote to delegate animation to */
-    private final RemoteTransition mRemote;
+    private RemoteTransition mRemote;
 
     public OneShotRemoteHandler(@NonNull ShellExecutor mainExecutor,
             @NonNull RemoteTransition remote) {
@@ -83,6 +83,8 @@
                 mMainExecutor.execute(() -> {
                     finishCallback.onTransitionFinished(wct);
                 });
+                Log.d("b/302551868", "OneShotRemoteHandler#start remote anim null");
+                mRemote = null;
             }
         };
         Transitions.setRunningRemoteTransitionDelegate(mRemote.getAppThread());
@@ -105,6 +107,8 @@
                 mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
             }
             finishCallback.onTransitionFinished(null /* wct */);
+            Log.d("b/302551868", "OneShotRemoteHandler#exception remote anim null");
+            mRemote = null;
         }
         return true;
     }
@@ -123,6 +127,8 @@
                 // so just assume the worst-case and clear the local transaction.
                 t.clear();
                 mMainExecutor.execute(() -> finishCallback.onTransitionFinished(wct));
+                Log.d("b/302551868", "OneShotRemoteHandler#merge remote anim null");
+                mRemote = null;
             }
         };
         try {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index baa9aca..c34a14e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -28,6 +28,7 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.fixScale;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -743,6 +744,11 @@
             if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
                 allOccluded = false;
             }
+            // The change has already animated by back gesture, don't need to play transition
+            // animation on it.
+            if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+                info.getChanges().remove(i);
+            }
         }
         // There does not need animation when:
         // A. Transfer starting window. Apply transfer starting window directly if there is no other
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index a4890ed..ad963dd 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -19,6 +19,8 @@
 #include "DeferredLayerUpdater.h"
 #include "hwui/Paint.h"
 
+#include <hwui/MinikinSkia.h>
+#include <hwui/Typeface.h>
 #include <minikin/Layout.h>
 #include <pipeline/skia/SkiaOpenGLPipeline.h>
 #include <pipeline/skia/SkiaVulkanPipeline.h>
@@ -179,5 +181,13 @@
     return outlineInLocalCoord;
 }
 
+SkFont TestUtils::defaultFont() {
+    const std::shared_ptr<minikin::MinikinFont>& minikinFont =
+      Typeface::resolveDefault(nullptr)->fFontCollection->getFamilyAt(0)->getFont(0)->baseTypeface();
+    SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(minikinFont.get())->GetSkTypeface();
+    LOG_ALWAYS_FATAL_IF(skTypeface == nullptr);
+    return SkFont(sk_ref_sp(skTypeface));
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index ffc664c..0ede902 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -30,6 +30,7 @@
 
 #include <SkBitmap.h>
 #include <SkColor.h>
+#include <SkFont.h>
 #include <SkImageInfo.h>
 #include <SkRefCnt.h>
 
@@ -353,6 +354,8 @@
 
     static CallCounts& countsForFunctor(int functor) { return sMockFunctorCounts[functor]; }
 
+    static SkFont defaultFont();
+
 private:
     static std::unordered_map<int, CallCounts> sMockFunctorCounts;
 
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
index 4a5d946..97d4c82 100644
--- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -57,7 +57,7 @@
                 128 * 3;
         paint.setColor(bgDark ? Color::White : Color::Grey_700);
 
-        SkFont font;
+        SkFont font = TestUtils::defaultFont();
         font.setSize(size / 2);
         char charToShow = 'A' + (rand() % 26);
         const SkPoint pos = {SkIntToScalar(size / 2),
diff --git a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
index bb95490..159541c 100644
--- a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
@@ -102,7 +102,7 @@
                 128 * 3;
         paint.setColor(bgDark ? Color::White : Color::Grey_700);
 
-        SkFont font;
+        SkFont font = TestUtils::defaultFont();
         font.setSize(size / 2);
         char charToShow = 'A' + (rand() % 26);
         const SkPoint pos = {SkIntToScalar(size / 2),
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
new file mode 100644
index 0000000..a73d1ff
--- /dev/null
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -0,0 +1,8 @@
+package: "android.media.tv.flags"
+
+flag {
+    name: "broadcast_visibility_types"
+    namespace: "media_tv"
+    description: "Constants for standardizing broadcast visibility types."
+    bug: "222402395"
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 1088ace..4992ef1 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -290,7 +290,14 @@
                         broadcastIntent,
                         PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
 
-                session.commit(pendingIntent.getIntentSender());
+                try {
+                    session.commit(pendingIntent.getIntentSender());
+                } catch (Exception e) {
+                    Log.e(LOG_TAG, "Cannot install package: ", e);
+                    launchFailure(PackageInstaller.STATUS_FAILURE,
+                        PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
+                    return;
+                }
                 mCancelButton.setEnabled(false);
                 setFinishOnTouchOutside(false);
             } else {
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java
deleted file mode 100644
index 117b48f..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.settingslib.inputmethod;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.text.TextUtils;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
-
-import androidx.preference.Preference;
-import androidx.preference.PreferenceCategory;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceScreen;
-import androidx.preference.TwoStatePreference;
-
-import com.android.settingslib.R;
-
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-public class InputMethodAndSubtypeEnablerManager implements Preference.OnPreferenceChangeListener {
-
-    private final PreferenceFragment mFragment;
-
-    private boolean mHaveHardKeyboard;
-    private final HashMap<String, List<Preference>> mInputMethodAndSubtypePrefsMap =
-            new HashMap<>();
-    private final HashMap<String, TwoStatePreference> mAutoSelectionPrefsMap = new HashMap<>();
-    private InputMethodManager mImm;
-    // TODO: Change mInputMethodInfoList to Map
-    private List<InputMethodInfo> mInputMethodInfoList;
-    private final Collator mCollator = Collator.getInstance();
-
-    public InputMethodAndSubtypeEnablerManager(PreferenceFragment fragment) {
-        mFragment = fragment;
-        mImm = fragment.getContext().getSystemService(InputMethodManager.class);
-
-        mInputMethodInfoList = mImm.getInputMethodList();
-    }
-
-    public void init(PreferenceFragment fragment, String targetImi, PreferenceScreen root) {
-        final Configuration config = fragment.getResources().getConfiguration();
-        mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY);
-
-        for (final InputMethodInfo imi : mInputMethodInfoList) {
-            // Add subtype preferences of this IME when it is specified or no IME is specified.
-            if (imi.getId().equals(targetImi) || TextUtils.isEmpty(targetImi)) {
-                addInputMethodSubtypePreferences(fragment, imi, root);
-            }
-        }
-    }
-
-    public void refresh(Context context, PreferenceFragment fragment) {
-        // Refresh internal states in mInputMethodSettingValues to keep the latest
-        // "InputMethodInfo"s and "InputMethodSubtype"s
-        InputMethodSettingValuesWrapper
-                .getInstance(context).refreshAllInputMethodAndSubtypes();
-        InputMethodAndSubtypeUtil.loadInputMethodSubtypeList(fragment, context.getContentResolver(),
-                mInputMethodInfoList, mInputMethodAndSubtypePrefsMap);
-        updateAutoSelectionPreferences();
-    }
-
-    public void save(Context context, PreferenceFragment fragment) {
-        InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(fragment, context.getContentResolver(),
-                mInputMethodInfoList, mHaveHardKeyboard);
-    }
-
-    @Override
-    public boolean onPreferenceChange(final Preference pref, final Object newValue) {
-        if (!(newValue instanceof Boolean)) {
-            return true; // Invoke default behavior.
-        }
-        final boolean isChecking = (Boolean) newValue;
-        for (final String imiId : mAutoSelectionPrefsMap.keySet()) {
-            // An auto select subtype preference is changing.
-            if (mAutoSelectionPrefsMap.get(imiId) == pref) {
-                final TwoStatePreference autoSelectionPref = (TwoStatePreference) pref;
-                autoSelectionPref.setChecked(isChecking);
-                // Enable or disable subtypes depending on the auto selection preference.
-                setAutoSelectionSubtypesEnabled(imiId, autoSelectionPref.isChecked());
-                return false;
-            }
-        }
-        // A subtype preference is changing.
-        if (pref instanceof InputMethodSubtypePreference) {
-            final InputMethodSubtypePreference subtypePref = (InputMethodSubtypePreference) pref;
-            subtypePref.setChecked(isChecking);
-            if (!subtypePref.isChecked()) {
-                // It takes care of the case where no subtypes are explicitly enabled then the auto
-                // selection preference is going to be checked.
-                updateAutoSelectionPreferences();
-            }
-            return false;
-        }
-        return true; // Invoke default behavior.
-    }
-
-    private void addInputMethodSubtypePreferences(PreferenceFragment fragment, InputMethodInfo imi,
-            final PreferenceScreen root) {
-        Context prefContext = fragment.getPreferenceManager().getContext();
-
-        final int subtypeCount = imi.getSubtypeCount();
-        if (subtypeCount <= 1) {
-            return;
-        }
-        final String imiId = imi.getId();
-        final PreferenceCategory keyboardSettingsCategory =
-                new PreferenceCategory(prefContext);
-        root.addPreference(keyboardSettingsCategory);
-        final PackageManager pm = prefContext.getPackageManager();
-        final CharSequence label = imi.loadLabel(pm);
-
-        keyboardSettingsCategory.setTitle(label);
-        keyboardSettingsCategory.setKey(imiId);
-        // TODO: Use toggle Preference if images are ready.
-        final TwoStatePreference autoSelectionPref =
-                new SwitchWithNoTextPreference(prefContext);
-        mAutoSelectionPrefsMap.put(imiId, autoSelectionPref);
-        keyboardSettingsCategory.addPreference(autoSelectionPref);
-        autoSelectionPref.setOnPreferenceChangeListener(this);
-
-        final PreferenceCategory activeInputMethodsCategory =
-                new PreferenceCategory(prefContext);
-        activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes);
-        root.addPreference(activeInputMethodsCategory);
-
-        CharSequence autoSubtypeLabel = null;
-        final ArrayList<Preference> subtypePreferences = new ArrayList<>();
-        for (int index = 0; index < subtypeCount; ++index) {
-            final InputMethodSubtype subtype = imi.getSubtypeAt(index);
-            if (subtype.overridesImplicitlyEnabledSubtype()) {
-                if (autoSubtypeLabel == null) {
-                    autoSubtypeLabel = InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence(
-                            subtype, prefContext, imi);
-                }
-            } else {
-                final Preference subtypePref = new InputMethodSubtypePreference(
-                        prefContext, subtype, imi);
-                subtypePreferences.add(subtypePref);
-            }
-        }
-        subtypePreferences.sort((lhs, rhs) -> {
-            if (lhs instanceof InputMethodSubtypePreference) {
-                return ((InputMethodSubtypePreference) lhs).compareTo(rhs, mCollator);
-            }
-            return lhs.compareTo(rhs);
-        });
-        for (final Preference pref : subtypePreferences) {
-            activeInputMethodsCategory.addPreference(pref);
-            pref.setOnPreferenceChangeListener(this);
-            InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
-        }
-        mInputMethodAndSubtypePrefsMap.put(imiId, subtypePreferences);
-        if (TextUtils.isEmpty(autoSubtypeLabel)) {
-            autoSelectionPref.setTitle(
-                    R.string.use_system_language_to_select_input_method_subtypes);
-        } else {
-            autoSelectionPref.setTitle(autoSubtypeLabel);
-        }
-    }
-
-    private boolean isNoSubtypesExplicitlySelected(final String imiId) {
-        final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
-        for (final Preference pref : subtypePrefs) {
-            if (pref instanceof TwoStatePreference && ((TwoStatePreference) pref).isChecked()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private void setAutoSelectionSubtypesEnabled(final String imiId,
-            final boolean autoSelectionEnabled) {
-        final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
-        if (autoSelectionPref == null) {
-            return;
-        }
-        autoSelectionPref.setChecked(autoSelectionEnabled);
-        final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
-        for (final Preference pref : subtypePrefs) {
-            if (pref instanceof TwoStatePreference) {
-                // When autoSelectionEnabled is true, all subtype prefs need to be disabled with
-                // implicitly checked subtypes. In case of false, all subtype prefs need to be
-                // enabled.
-                pref.setEnabled(!autoSelectionEnabled);
-                if (autoSelectionEnabled) {
-                    ((TwoStatePreference) pref).setChecked(false);
-                }
-            }
-        }
-        if (autoSelectionEnabled) {
-            InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
-                    mFragment, mFragment.getContext().getContentResolver(),
-                    mInputMethodInfoList, mHaveHardKeyboard);
-            updateImplicitlyEnabledSubtypes(imiId);
-        }
-    }
-
-    private void updateImplicitlyEnabledSubtypes(final String targetImiId) {
-        // When targetImiId is null, apply to all subtypes of all IMEs
-        for (final InputMethodInfo imi : mInputMethodInfoList) {
-            final String imiId = imi.getId();
-            final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
-            // No need to update implicitly enabled subtypes when the user has unchecked the
-            // "subtype auto selection".
-            if (autoSelectionPref == null || !autoSelectionPref.isChecked()) {
-                continue;
-            }
-            if (imiId.equals(targetImiId) || targetImiId == null) {
-                updateImplicitlyEnabledSubtypesOf(imi);
-            }
-        }
-    }
-
-    private void updateImplicitlyEnabledSubtypesOf(final InputMethodInfo imi) {
-        final String imiId = imi.getId();
-        final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
-        final List<InputMethodSubtype> implicitlyEnabledSubtypes =
-                mImm.getEnabledInputMethodSubtypeList(imi, true);
-        if (subtypePrefs == null || implicitlyEnabledSubtypes == null) {
-            return;
-        }
-        for (final Preference pref : subtypePrefs) {
-            if (!(pref instanceof TwoStatePreference)) {
-                continue;
-            }
-            final TwoStatePreference subtypePref = (TwoStatePreference) pref;
-            subtypePref.setChecked(false);
-            for (final InputMethodSubtype subtype : implicitlyEnabledSubtypes) {
-                final String implicitlyEnabledSubtypePrefKey = imiId + subtype.hashCode();
-                if (subtypePref.getKey().equals(implicitlyEnabledSubtypePrefKey)) {
-                    subtypePref.setChecked(true);
-                    break;
-                }
-            }
-        }
-    }
-
-    private void updateAutoSelectionPreferences() {
-        for (final String imiId : mInputMethodAndSubtypePrefsMap.keySet()) {
-            setAutoSelectionSubtypesEnabled(imiId, isNoSubtypesExplicitlySelected(imiId));
-        }
-        updateImplicitlyEnabledSubtypes(null /* targetImiId */  /* check */);
-    }
-}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 17cc9f8..f78811f 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -60,7 +60,6 @@
             // except for SystemUI-core.
             // Copied from compose/features/Android.bp.
             static_libs: [
-                "CommunalLayoutLib",
                 "PlatformComposeCore",
                 "PlatformComposeSceneTransitionLayout",
 
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 9e06872..21263a9 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -6,7 +6,7 @@
     name: "floating_menu_animated_tuck"
     namespace: "accessibility"
     description: "Sets up animations for tucking/untucking and adjusts clipbounds."
-    bug: "24592044"
+    bug: "297556899"
 }
 
 flag {
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c26d5f5..8eff9bf 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -51,3 +51,11 @@
     description: "Enables the scene container framework go/flexiglass."
     bug: "283121968"
 }
+
+flag {
+    name: "visual_interruptions_refactor"
+    namespace: "systemui"
+    description: "Enables the refactored version of the code to decide when notifications "
+        "HUN, bubble, pulse, or FSI."
+    bug: "261728888"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index b8fb264..87a8c35 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -4,8 +4,14 @@
 import android.os.Bundle
 import android.util.SizeF
 import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
 import androidx.compose.material3.Card
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
@@ -13,16 +19,12 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.integerResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
-import com.android.systemui.communal.layout.ui.compose.CommunalGridLayout
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.ui.model.CommunalContentUiModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.res.R
 
 @Composable
 fun CommunalHub(
@@ -34,68 +36,91 @@
     Box(
         modifier = modifier.fillMaxSize().background(Color.White),
     ) {
-        CommunalGridLayout(
-            modifier = Modifier.align(Alignment.CenterStart),
-            layoutConfig =
-                CommunalGridLayoutConfig(
-                    gridColumnSize = dimensionResource(R.dimen.communal_grid_column_size),
-                    gridGutter = dimensionResource(R.dimen.communal_grid_gutter_size),
-                    gridHeight = dimensionResource(R.dimen.communal_grid_height),
-                    gridColumnsPerCard = integerResource(R.integer.communal_grid_columns_per_card),
-                ),
-            communalCards = if (showTutorial) tutorialContent else widgetContent.map(::contentCard),
-        )
+        LazyHorizontalGrid(
+            modifier = modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart),
+            rows = GridCells.Fixed(CommunalContentSize.FULL.span),
+            horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
+            verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
+        ) {
+            if (showTutorial) {
+                items(
+                    count = tutorialContentSizes.size,
+                    // TODO(b/308148193): a more scalable solution for unique ids.
+                    key = { index -> "tutorial_$index" },
+                    span = { index -> GridItemSpan(tutorialContentSizes[index].span) },
+                ) { index ->
+                    TutorialCard(
+                        modifier =
+                            Modifier.size(Dimensions.CardWidth, tutorialContentSizes[index].dp()),
+                    )
+                }
+            } else {
+                items(
+                    count = widgetContent.size,
+                    key = { index -> widgetContent[index].id },
+                    span = { index -> GridItemSpan(widgetContent[index].size.span) },
+                ) { index ->
+                    val widget = widgetContent[index]
+                    ContentCard(
+                        modifier = Modifier.size(Dimensions.CardWidth, widget.size.dp()),
+                        model = widget,
+                    )
+                }
+            }
+        }
     }
 }
 
-private val tutorialContent =
+// A placeholder for tutorial content.
+@Composable
+private fun TutorialCard(modifier: Modifier = Modifier) {
+    Card(modifier = modifier, content = {})
+}
+
+@Composable
+private fun ContentCard(
+    model: CommunalContentUiModel,
+    modifier: Modifier = Modifier,
+) {
+    AndroidView(
+        modifier = modifier,
+        factory = {
+            model.view.apply {
+                if (this is AppWidgetHostView) {
+                    val size = SizeF(Dimensions.CardWidth.value, model.size.dp().value)
+                    updateAppWidgetSize(Bundle.EMPTY, listOf(size))
+                }
+            }
+        },
+    )
+}
+
+private fun CommunalContentSize.dp(): Dp {
+    return when (this) {
+        CommunalContentSize.FULL -> Dimensions.CardHeightFull
+        CommunalContentSize.HALF -> Dimensions.CardHeightHalf
+        CommunalContentSize.THIRD -> Dimensions.CardHeightThird
+    }
+}
+
+// Sizes for the tutorial placeholders.
+private val tutorialContentSizes =
     listOf(
-        tutorialCard(CommunalGridLayoutCard.Size.FULL),
-        tutorialCard(CommunalGridLayoutCard.Size.THIRD),
-        tutorialCard(CommunalGridLayoutCard.Size.THIRD),
-        tutorialCard(CommunalGridLayoutCard.Size.THIRD),
-        tutorialCard(CommunalGridLayoutCard.Size.HALF),
-        tutorialCard(CommunalGridLayoutCard.Size.HALF),
-        tutorialCard(CommunalGridLayoutCard.Size.HALF),
-        tutorialCard(CommunalGridLayoutCard.Size.HALF),
+        CommunalContentSize.FULL,
+        CommunalContentSize.THIRD,
+        CommunalContentSize.THIRD,
+        CommunalContentSize.THIRD,
+        CommunalContentSize.HALF,
+        CommunalContentSize.HALF,
+        CommunalContentSize.HALF,
+        CommunalContentSize.HALF,
     )
 
-private fun tutorialCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard {
-    return object : CommunalGridLayoutCard() {
-        override val supportedSizes = listOf(size)
-
-        @Composable
-        override fun Content(modifier: Modifier, size: SizeF) {
-            Card(modifier = modifier, content = {})
-        }
-    }
-}
-
-private fun contentCard(model: CommunalContentUiModel): CommunalGridLayoutCard {
-    return object : CommunalGridLayoutCard() {
-        override val supportedSizes = listOf(convertToCardSize(model.size))
-        override val priority = model.priority
-
-        @Composable
-        override fun Content(modifier: Modifier, size: SizeF) {
-            AndroidView(
-                modifier = modifier,
-                factory = {
-                    model.view.apply {
-                        if (this is AppWidgetHostView) {
-                            updateAppWidgetSize(Bundle(), listOf(size))
-                        }
-                    }
-                },
-            )
-        }
-    }
-}
-
-private fun convertToCardSize(size: CommunalContentSize): CommunalGridLayoutCard.Size {
-    return when (size) {
-        CommunalContentSize.FULL -> CommunalGridLayoutCard.Size.FULL
-        CommunalContentSize.HALF -> CommunalGridLayoutCard.Size.HALF
-        CommunalContentSize.THIRD -> CommunalGridLayoutCard.Size.THIRD
-    }
+private object Dimensions {
+    val CardWidth = 464.dp
+    val CardHeightFull = 630.dp
+    val CardHeightHalf = 307.dp
+    val CardHeightThird = 199.dp
+    val GridHeight = CardHeightFull
+    val Spacing = 16.dp
 }
diff --git a/packages/SystemUI/res/layout/volume_dnd_icon.xml b/packages/SystemUI/res/layout/volume_dnd_icon.xml
deleted file mode 100644
index 56587b9..0000000
--- a/packages/SystemUI/res/layout/volume_dnd_icon.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
-     Copyright (C) 2018 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.
--->
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/dnd_icon"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:layout_gravity="bottom"
-    android:layout_marginTop="6dp"
-    android:layout_marginBottom="6dp">
-
-    <ImageView
-        android:layout_width="14dp"
-        android:layout_height="14dp"
-        android:layout_gravity="center"
-        android:src="@*android:drawable/ic_qs_dnd"
-        android:tint="?android:attr/textColorTertiary"/>
-</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 85122ba..e69eced 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -18,6 +18,7 @@
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION;
 
 import android.animation.Animator;
@@ -63,7 +64,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.res.R;
 import com.android.systemui.biometrics.AuthController.ScaleFactorProvider;
 import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
@@ -76,8 +76,8 @@
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
 import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
@@ -280,7 +280,6 @@
 
     // TODO(b/251476085): remove Config and further decompose these properties out of view classes
     AuthContainerView(@NonNull Config config,
-            @NonNull FeatureFlags featureFlags,
             @NonNull CoroutineScope applicationCoroutineScope,
             @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
             @Nullable List<FaceSensorPropertiesInternal> faceProps,
@@ -295,7 +294,7 @@
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
             @NonNull @Background DelayableExecutor bgExecutor,
             @NonNull VibratorHelper vibratorHelper) {
-        this(config, featureFlags, applicationCoroutineScope, fpProps, faceProps,
+        this(config, applicationCoroutineScope, fpProps, faceProps,
                 wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
                 jankMonitor, promptSelectorInteractor, promptCredentialInteractor, promptViewModel,
                 credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor,
@@ -304,7 +303,6 @@
 
     @VisibleForTesting
     AuthContainerView(@NonNull Config config,
-            @NonNull FeatureFlags featureFlags,
             @NonNull CoroutineScope applicationCoroutineScope,
             @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
             @Nullable List<FaceSensorPropertiesInternal> faceProps,
@@ -368,7 +366,7 @@
         showPrompt(config, layoutInflater, promptViewModel,
                 Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
                 Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds),
-                vibratorHelper, featureFlags);
+                vibratorHelper);
 
         // TODO: De-dupe the logic with AuthCredentialPasswordView
         setOnKeyListener((v, keyCode, event) -> {
@@ -390,8 +388,7 @@
             @NonNull PromptViewModel viewModel,
             @Nullable FingerprintSensorPropertiesInternal fpProps,
             @Nullable FaceSensorPropertiesInternal faceProps,
-            @NonNull VibratorHelper vibratorHelper,
-            @NonNull FeatureFlags featureFlags
+            @NonNull VibratorHelper vibratorHelper
     ) {
         if (Utils.isBiometricAllowed(config.mPromptInfo)) {
             mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication(
@@ -407,7 +404,7 @@
                     getJankListener(view, TRANSIT,
                             BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
                     mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
-                    vibratorHelper, featureFlags);
+                    vibratorHelper);
 
             // TODO(b/251476085): migrate these dependencies
             if (fpProps != null && fpProps.isAnyUdfpsType()) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index a64e862..05db56f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -78,7 +78,6 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeReceiver;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.data.repository.BiometricType;
 import com.android.systemui.statusbar.CommandQueue;
@@ -120,7 +119,6 @@
 
     private final Handler mHandler;
     private final Context mContext;
-    private final FeatureFlags mFeatureFlags;
     private final Execution mExecution;
     private final CommandQueue mCommandQueue;
     private final ActivityTaskManager mActivityTaskManager;
@@ -743,7 +741,6 @@
     }
     @Inject
     public AuthController(Context context,
-            @NonNull FeatureFlags featureFlags,
             @Application CoroutineScope applicationCoroutineScope,
             Execution execution,
             CommandQueue commandQueue,
@@ -770,7 +767,6 @@
             @NonNull UdfpsUtils udfpsUtils,
             @NonNull VibratorHelper vibratorHelper) {
         mContext = context;
-        mFeatureFlags = featureFlags;
         mExecution = execution;
         mUserManager = userManager;
         mLockPatternUtils = lockPatternUtils;
@@ -1316,7 +1312,7 @@
         config.mRequestId = requestId;
         config.mSensorIds = sensorIds;
         config.mScaleProvider = this::getScaleFactor;
-        return new AuthContainerView(config, mFeatureFlags, mApplicationCoroutineScope, mFpProps, mFaceProps,
+        return new AuthContainerView(config, mApplicationCoroutineScope, mFpProps, mFaceProps,
                 wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
                 mInteractionJankMonitor, mPromptCredentialInteractor, mPromptSelectorInteractor,
                 viewModel, mCredentialViewModelProvider, bgExecutor, mVibratorHelper);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index ac48b6a..32d9067 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -49,8 +49,6 @@
 import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
 import com.android.systemui.biometrics.ui.viewmodel.PromptSize
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
@@ -78,7 +76,6 @@
         legacyCallback: Spaghetti.Callback,
         applicationScope: CoroutineScope,
         vibratorHelper: VibratorHelper,
-        featureFlags: FeatureFlags,
     ): Spaghetti {
         val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
 
@@ -380,13 +377,11 @@
                 }
 
                 // Play haptics
-                if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-                    launch {
-                        viewModel.hapticsToPlay.collect { hapticFeedbackConstant ->
-                            if (hapticFeedbackConstant != HapticFeedbackConstants.NO_HAPTICS) {
-                                vibratorHelper.performHapticFeedback(view, hapticFeedbackConstant)
-                                viewModel.clearHaptics()
-                            }
+                launch {
+                    viewModel.hapticsToPlay.collect { hapticFeedbackConstant ->
+                        if (hapticFeedbackConstant != HapticFeedbackConstants.NO_HAPTICS) {
+                            vibratorHelper.performHapticFeedback(view, hapticFeedbackConstant)
+                            viewModel.clearHaptics()
                         }
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index e49b4a7..647aaf3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -29,10 +29,7 @@
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.PromptKind
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.VibratorHelper
 import javax.inject.Inject
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.coroutineScope
@@ -52,9 +49,7 @@
 constructor(
     displayStateInteractor: DisplayStateInteractor,
     promptSelectorInteractor: PromptSelectorInteractor,
-    private val vibrator: VibratorHelper,
     @Application context: Context,
-    private val featureFlags: FeatureFlags,
 ) {
     /** The set of modalities available for this prompt */
     val modalities: Flow<BiometricModalities> =
@@ -339,7 +334,7 @@
         _message.value = PromptMessage.Error(message)
 
         if (hapticFeedback) {
-            vibrator.error(failedModality)
+            vibrateOnError()
         }
 
         messageJob?.cancel()
@@ -457,7 +452,7 @@
         _message.value = PromptMessage.Empty
 
         if (!needsUserConfirmation) {
-            vibrator.success(modality)
+            vibrateOnSuccess()
         }
 
         messageJob?.cancel()
@@ -495,7 +490,7 @@
         _isAuthenticated.value = authState.asExplicitlyConfirmed()
         _message.value = PromptMessage.Empty
 
-        vibrator.success(authState.authenticatedModality)
+        vibrateOnSuccess()
 
         messageJob?.cancel()
         messageJob = null
@@ -530,20 +525,12 @@
         _forceLargeSize.value = true
     }
 
-    private fun VibratorHelper.success(modality: BiometricModality) {
-        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-            _hapticsToPlay.value = HapticFeedbackConstants.CONFIRM
-        } else {
-            vibrateAuthSuccess("$TAG, modality = $modality BP::success")
-        }
+    private fun vibrateOnSuccess() {
+        _hapticsToPlay.value = HapticFeedbackConstants.CONFIRM
     }
 
-    private fun VibratorHelper.error(modality: BiometricModality = BiometricModality.None) {
-        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-            _hapticsToPlay.value = HapticFeedbackConstants.REJECT
-        } else {
-            vibrateAuthError("$TAG, modality = $modality BP::error")
-        }
+    private fun vibrateOnError() {
+        _hapticsToPlay.value = HapticFeedbackConstants.REJECT
     }
 
     /** Clears the [hapticsToPlay] variable by setting it to the NO_HAPTICS default. */
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index f77f989..a79a654 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -75,5 +75,8 @@
 
     /** Indicates an a11y action was made. */
     void onA11yAction();
+
+    /** Initialize the class. */
+    void init();
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index 0dfaf0f..d6b9a11 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -23,6 +23,10 @@
 /** */
 public class FalsingCollectorFake implements FalsingCollector {
 
+    @Override
+    public void init() {
+    }
+
     @Inject
     public FalsingCollectorFake() {
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index a6b073d..12df96e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -32,13 +32,14 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.sensors.ThresholdSensor;
 import com.android.systemui.util.sensors.ThresholdSensorEvent;
@@ -65,9 +66,11 @@
     private final ProximitySensor mProximitySensor;
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardStateController mKeyguardStateController;
+    private final Lazy<ShadeInteractor> mShadeInteractorLazy;
     private final BatteryController mBatteryController;
     private final DockManager mDockManager;
     private final DelayableExecutor mMainExecutor;
+    private final JavaAdapter mJavaAdapter;
     private final SystemClock mSystemClock;
     private final Lazy<SelectedUserInteractor> mUserInteractor;
 
@@ -136,10 +139,11 @@
             ProximitySensor proximitySensor,
             StatusBarStateController statusBarStateController,
             KeyguardStateController keyguardStateController,
-            ShadeExpansionStateManager shadeExpansionStateManager,
+            Lazy<ShadeInteractor> shadeInteractorLazy,
             BatteryController batteryController,
             DockManager dockManager,
             @Main DelayableExecutor mainExecutor,
+            JavaAdapter javaAdapter,
             SystemClock systemClock,
             Lazy<SelectedUserInteractor> userInteractor) {
         mFalsingDataProvider = falsingDataProvider;
@@ -149,12 +153,17 @@
         mProximitySensor = proximitySensor;
         mStatusBarStateController = statusBarStateController;
         mKeyguardStateController = keyguardStateController;
+        mShadeInteractorLazy = shadeInteractorLazy;
         mBatteryController = batteryController;
         mDockManager = dockManager;
         mMainExecutor = mainExecutor;
+        mJavaAdapter = javaAdapter;
         mSystemClock = systemClock;
         mUserInteractor = userInteractor;
+    }
 
+    @Override
+    public void init() {
         mProximitySensor.setTag(PROXIMITY_SENSOR_TAG);
         mProximitySensor.setDelay(SensorManager.SENSOR_DELAY_GAME);
 
@@ -163,7 +172,10 @@
 
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
 
-        shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
+        mJavaAdapter.alwaysCollectFlow(
+                mShadeInteractorLazy.get().isQsExpanded(),
+                this::onQsExpansionChanged
+        );
 
         mBatteryController.addCallback(mBatteryListener);
         mDockManager.addListener(mDockEventListener);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
index e5b404f..c5d8c79 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
@@ -23,6 +23,10 @@
 
 @SysUISingleton
 class FalsingCollectorNoOp @Inject constructor() : FalsingCollector {
+    override fun init() {
+        logDebug("NOOP: init")
+    }
+
     override fun onSuccessfulUnlock() {
         logDebug("NOOP: onSuccessfulUnlock")
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt
new file mode 100644
index 0000000..b79538a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.classifier
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Initializes classes related to falsing. */
+@SysUISingleton
+class FalsingCoreStartable @Inject constructor(val falsingCollector: FalsingCollector) :
+    CoreStartable {
+    override fun start() {
+        falsingCollector.init()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
index 2729b7b..af467ef 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
@@ -19,9 +19,9 @@
 import android.content.res.Resources;
 import android.view.ViewConfiguration;
 
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
 import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.statusbar.phone.NotificationTapHelper;
 
@@ -37,7 +37,7 @@
 import javax.inject.Named;
 
 /** Dagger Module for Falsing. */
-@Module
+@Module(includes = {FalsingStartModule.class})
 public interface FalsingModule {
     String BRIGHT_LINE_GESTURE_CLASSIFERS = "bright_line_gesture_classifiers";
     String SINGLE_TAP_TOUCH_SLOP = "falsing_single_tap_touch_slop";
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingStartModule.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingStartModule.kt
new file mode 100644
index 0000000..a9f8f37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingStartModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.classifier
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface FalsingStartModule {
+    /**  */
+    @Binds
+    @IntoMap
+    @ClassKey(FalsingCoreStartable::class)
+    fun bindFalsingCoreStartable(falsingCoreStartable: FalsingCoreStartable?): CoreStartable?
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt
index b2bc06f..48d3742 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt
@@ -20,4 +20,7 @@
 data class SharedNotificationContainerPosition(
     val top: Float = 0f,
     val bottom: Float = 0f,
+
+    /** Whether any modifications to top/bottom are smoothly animated */
+    val animate: Boolean = false,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
index 39a6476..c903709 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
@@ -16,14 +16,19 @@
 
 package com.android.systemui.communal.shared.model
 
-/** Supported sizes for communal content in the layout grid. */
-enum class CommunalContentSize {
+/**
+ * Supported sizes for communal content in the layout grid.
+ *
+ * @param span The span of the content in a column. For example, if FULL is 6, then 3 represents
+ *   HALF, 2 represents THIRD, and 1 represents SIXTH.
+ */
+enum class CommunalContentSize(val span: Int) {
     /** Content takes the full height of the column. */
-    FULL,
+    FULL(6),
 
     /** Content takes half of the height of the column. */
-    HALF,
+    HALF(3),
 
     /** Content takes a third of the height of the column. */
-    THIRD,
+    THIRD(2),
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
index 98060dc..b60dc2a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
@@ -9,7 +9,7 @@
  * This model stays in the UI layer.
  */
 data class CommunalContentUiModel(
+    val id: String,
     val view: View,
-    val size: CommunalContentSize,
-    val priority: Int,
+    val size: CommunalContentSize = CommunalContentSize.HALF,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 25c64ea..390b580 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -20,7 +20,6 @@
 import android.content.Context
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
-import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.ui.model.CommunalContentUiModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -42,16 +41,16 @@
 
     /** List of widgets to be displayed in the communal hub. */
     val widgetContent: Flow<List<CommunalContentUiModel>> =
-        communalInteractor.widgetContent.map {
-            it.map {
+        communalInteractor.widgetContent.map { widgets ->
+            widgets.map Widget@{ widget ->
                 // TODO(b/306406256): As adding and removing widgets functionalities are
                 // supported, cache the host views so they're not recreated each time.
-                val hostView = appWidgetHost.createView(context, it.appWidgetId, it.providerInfo)
-                return@map CommunalContentUiModel(
+                val hostView =
+                    appWidgetHost.createView(context, widget.appWidgetId, widget.providerInfo)
+                return@Widget CommunalContentUiModel(
+                    // TODO(b/308148193): a more scalable solution for unique ids.
+                    id = "widget_${widget.appWidgetId}",
                     view = hostView,
-                    priority = it.priority,
-                    // All widgets have HALF size.
-                    size = CommunalContentSize.HALF,
                 )
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 00d95c0..0f038e1 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -37,8 +37,6 @@
 import com.android.systemui.controls.settings.ControlsSettingsRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -59,7 +57,6 @@
         private val controlsMetricsLogger: ControlsMetricsLogger,
         private val vibrator: VibratorHelper,
         private val controlsSettingsRepository: ControlsSettingsRepository,
-        private val featureFlags: FeatureFlags,
 ) : ControlActionCoordinator {
     private var dialog: Dialog? = null
     private var pendingAction: Action? = null
@@ -123,17 +120,12 @@
     }
 
     override fun drag(cvh: ControlViewHolder, isEdge: Boolean) {
-        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-            val constant =
-                if (isEdge)
-                    HapticFeedbackConstants.SEGMENT_TICK
-                else
-                    HapticFeedbackConstants.SEGMENT_FREQUENT_TICK
-            vibrator.performHapticFeedback(cvh.layout, constant)
-        } else {
-            val effect = if (isEdge) Vibrations.rangeEdgeEffect else Vibrations.rangeMiddleEffect
-            vibrate(effect)
-        }
+        val constant =
+            if (isEdge)
+                HapticFeedbackConstants.SEGMENT_TICK
+            else
+                HapticFeedbackConstants.SEGMENT_FREQUENT_TICK
+        vibrator.performHapticFeedback(cvh.layout, constant)
     }
 
     override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt
deleted file mode 100644
index 29b7e985..0000000
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2020 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.controls.ui
-
-import android.os.VibrationEffect
-import android.os.VibrationEffect.Composition.PRIMITIVE_TICK
-
-object Vibrations {
-    val rangeEdgeEffect = initRangeEdgeEffect()
-    val rangeMiddleEffect = initRangeMiddleEffect()
-
-    private fun initRangeEdgeEffect(): VibrationEffect {
-        val composition = VibrationEffect.startComposition()
-        composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
-        return composition.compose()
-    }
-
-    private fun initRangeMiddleEffect(): VibrationEffect {
-        val composition = VibrationEffect.startComposition()
-        composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.1f)
-        return composition.compose()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f4a9f73..0c364e1 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -400,8 +400,7 @@
     // 600- status bar
 
     // TODO(b/291315866): Tracking Bug
-    @JvmField val SIGNAL_CALLBACK_DEPRECATION =
-        unreleasedFlag("signal_callback_deprecation", teamfood = true)
+    @JvmField val SIGNAL_CALLBACK_DEPRECATION = releasedFlag("signal_callback_deprecation")
 
     // TODO(b/301610137): Tracking bug
     @JvmField val NEW_NETWORK_SLICE_UI = unreleasedFlag("new_network_slice_ui", teamfood = true)
@@ -414,15 +413,14 @@
     val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip")
 
     // TODO(b/292533677): Tracking Bug
-    val WIFI_TRACKER_LIB_FOR_WIFI_ICON =
-        unreleasedFlag("wifi_tracker_lib_for_wifi_icon", teamfood = true)
+    val WIFI_TRACKER_LIB_FOR_WIFI_ICON = releasedFlag("wifi_tracker_lib_for_wifi_icon")
 
     // TODO(b/293863612): Tracking Bug
     @JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON =
         releasedFlag("incompatible_charging_battery_icon")
 
     // TODO(b/293585143): Tracking Bug
-    val INSTANT_TETHER = unreleasedFlag("instant_tether", teamfood = true)
+    val INSTANT_TETHER = releasedFlag("instant_tether")
 
     // TODO(b/294588085): Tracking Bug
     val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 4d5c503..67a12b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -22,6 +22,8 @@
 import android.view.View.OnLayoutChangeListener
 import android.view.ViewGroup
 import android.view.ViewGroup.OnHierarchyChangeListener
+import android.view.WindowInsets
+import android.view.WindowInsets.Type
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.internal.jank.InteractionJankMonitor
@@ -242,11 +244,18 @@
             }
         )
 
+        view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
+            val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
+            viewModel.topInset = insets.getInsetsIgnoringVisibility(insetTypes).top
+            insets
+        }
+
         return object : DisposableHandle {
             override fun dispose() {
                 disposableHandle.dispose()
                 view.removeOnLayoutChangeListener(onLayoutChangeListener)
                 view.setOnHierarchyChangeListener(null)
+                view.setOnApplyWindowInsetsListener(null)
                 childViews.clear()
             }
         }
@@ -288,7 +297,6 @@
             oldBottom: Int
         ) {
             val nsslPlaceholder = v.findViewById(R.id.nssl_placeholder) as View?
-
             if (nsslPlaceholder != null) {
                 // After layout, ensure the notifications are positioned correctly
                 viewModel.onSharedNotificationContainerPositionChanged(
@@ -296,6 +304,11 @@
                     nsslPlaceholder.bottom.toFloat(),
                 )
             }
+
+            val ksv = v.findViewById(R.id.keyguard_status_view) as View?
+            if (ksv != null) {
+                viewModel.statusViewTop = ksv.top
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 1f98082..e12da53 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -65,7 +65,12 @@
      */
     private val previewMode = MutableStateFlow(PreviewMode())
 
-    public var clockControllerProvider: Provider<ClockController>? = null
+    var clockControllerProvider: Provider<ClockController>? = null
+
+    /** System insets that keyguard needs to stay out of */
+    var topInset: Int = 0
+    /** Status view top, without translation added in */
+    var statusViewTop: Int = 0
 
     val burnInLayerVisibility: Flow<Int> =
         keyguardTransitionInteractor.startedKeyguardState
@@ -102,9 +107,12 @@
                     scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolation),
                 )
             } else {
+                // Ensure the desired translation doesn't encroach on the top inset
+                val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolation).toInt()
+                val translationY = -(statusViewTop - Math.max(topInset, statusViewTop + burnInY))
                 BurnInModel(
                     translationX = MathUtils.lerp(0, burnIn.translationX, interpolation).toInt(),
-                    translationY = MathUtils.lerp(0, burnIn.translationY, interpolation).toInt(),
+                    translationY = translationY,
                     scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolation),
                     scaleClockOnly = true,
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
index 8103152..13271c3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
@@ -21,8 +21,8 @@
 import android.os.Looper
 import android.provider.Settings
 import android.view.View
+import android.widget.Switch
 import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
@@ -34,6 +34,7 @@
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.connectivity.AccessPointController
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.InternetTileBinder
 import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileModel
@@ -96,6 +97,7 @@
 
     override fun handleUpdateState(state: QSTile.BooleanState, arg: Any?) {
         state.label = mContext.resources.getString(R.string.quick_settings_internet_label)
+        state.expandedAccessibilityClassName = Switch::class.java.name
 
         model.applyTo(state, mContext)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index cc59f87..dfe6adc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1070,7 +1070,6 @@
         mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
                 mOnEmptySpaceClickListener);
         mQsController.init();
-        mShadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
         mShadeHeadsUpTracker.addTrackingHeadsUpListener(
                 mNotificationStackScrollLayoutController::setTrackingHeadsUp);
         if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
@@ -4219,7 +4218,7 @@
         return mShadeExpansionStateManager;
     }
 
-    private void onQsExpansionChanged(boolean expanded) {
+    void onQsExpansionChanged(boolean expanded) {
         updateExpandedHeightToMaxHeight();
         setStatusAccessibilityImportance(expanded
                 ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 0426388..5114826 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -156,7 +156,6 @@
             KeyguardStateController keyguardStateController,
             ScreenOffAnimationController screenOffAnimationController,
             AuthController authController,
-            ShadeExpansionStateManager shadeExpansionStateManager,
             Lazy<ShadeInteractor> shadeInteractorLazy,
             ShadeWindowLogger logger,
             Lazy<SelectedUserInteractor> userInteractor) {
@@ -185,7 +184,6 @@
                 .addCallback(mStateListener,
                         SysuiStatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
         configurationController.addCallback(this);
-        shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
 
         float desiredPreferredRefreshRate = context.getResources()
                 .getInteger(R.integer.config_keyguardRefreshRate);
@@ -303,6 +301,11 @@
                 mShadeInteractorLazy.get().isAnyExpanded(),
                 this::onShadeOrQsExpanded
         );
+        collectFlow(
+                mWindowRootView,
+                mShadeInteractorLazy.get().isQsExpanded(),
+                this::onQsExpansionChanged
+        );
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index cc46b23..866cfb4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -26,23 +26,27 @@
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.res.R
+import androidx.lifecycle.lifecycleScope
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.fragments.FragmentService
+import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.navigationbar.NavigationModeController
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.plugins.qs.QSContainerController
 import com.android.systemui.recents.OverviewProxyService
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.system.QuickStepContract
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import com.android.systemui.util.LargeScreenUtils
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.concurrency.DelayableExecutor
+import kotlinx.coroutines.launch
 import java.util.function.Consumer
 import javax.inject.Inject
 import kotlin.reflect.KMutableProperty0
@@ -56,7 +60,7 @@
         private val navigationModeController: NavigationModeController,
         private val overviewProxyService: OverviewProxyService,
         private val shadeHeaderController: ShadeHeaderController,
-        private val shadeExpansionStateManager: ShadeExpansionStateManager,
+        private val shadeInteractor: ShadeInteractor,
         private val fragmentService: FragmentService,
         @Main private val delayableExecutor: DelayableExecutor,
         private val featureFlags: FeatureFlags,
@@ -65,7 +69,6 @@
         private val splitShadeStateController: SplitShadeStateController
 ) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
 
-    private var qsExpanded = false
     private var splitShadeEnabled = false
     private var isQSDetailShowing = false
     private var isQSCustomizing = false
@@ -89,13 +92,6 @@
             taskbarVisible = visible
         }
     }
-    private val shadeQsExpansionListener: ShadeQsExpansionListener =
-        ShadeQsExpansionListener { isQsExpanded ->
-            if (qsExpanded != isQsExpanded) {
-                qsExpanded = isQsExpanded
-                mView.invalidate()
-            }
-        }
 
     // With certain configuration changes (like light/dark changes), the nav bar will disappear
     // for a bit, causing `bottomStableInsets` to be unstable for some time. Debounce the value
@@ -122,6 +118,11 @@
     }
 
     override fun onInit() {
+        mView.repeatWhenAttached {
+            lifecycleScope.launch {
+                shadeInteractor.isQsExpanded.collect{ _ -> mView.invalidate() }
+            }
+        }
         val currentMode: Int = navigationModeController.addListener { mode: Int ->
             isGestureNavigation = QuickStepContract.isGesturalMode(mode)
         }
@@ -137,7 +138,6 @@
     public override fun onViewAttached() {
         updateResources()
         overviewProxyService.addCallback(taskbarVisibilityListener)
-        shadeExpansionStateManager.addQsExpansionListener(shadeQsExpansionListener)
         mView.setInsetsChangedListener(delayedInsetSetter)
         mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) }
         mView.setConfigurationChangedListener { updateResources() }
@@ -146,7 +146,6 @@
 
     override fun onViewDetached() {
         overviewProxyService.removeCallback(taskbarVisibilityListener)
-        shadeExpansionStateManager.removeQsExpansionListener(shadeQsExpansionListener)
         mView.removeOnInsetsChangedListener()
         mView.removeQSFragmentAttachedListener()
         mView.setConfigurationChangedListener(null)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 0ec7a36..d73fa14 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -207,12 +207,6 @@
     /** pointerId of the pointer we're currently tracking */
     private int mTrackingPointer;
 
-    /**
-     * Indicates that QS is in expanded state which can happen by:
-     * - single pane shade: expanding shade and then expanding QS
-     * - split shade: just expanding shade (QS are expanded automatically)
-     */
-    private boolean mExpanded;
     /** Indicates QS is at its max height */
     private boolean mFullyExpanded;
     /**
@@ -594,9 +588,8 @@
         return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag;
     }
 
-
     public boolean getExpanded() {
-        return mExpanded;
+        return mShadeRepository.getLegacyIsQsExpanded().getValue();
     }
 
     @VisibleForTesting
@@ -613,7 +606,7 @@
         // close the whole shade with one motion. Also this will be always true when closing
         // split shade as there QS are always expanded so every collapsing motion is motion from
         // expanded QS to closed panel
-        return mExpandImmediate || (mExpanded
+        return mExpandImmediate || (getExpanded()
                 && !isTracking() && !isExpansionAnimating()
                 && !mExpansionFromOverscroll);
     }
@@ -778,11 +771,11 @@
 
     @VisibleForTesting
     void setExpanded(boolean expanded) {
-        boolean changed = mExpanded != expanded;
+        boolean changed = getExpanded() != expanded;
         if (changed) {
-            mExpanded = expanded;
+            mShadeRepository.setLegacyIsQsExpanded(expanded);
             updateQsState();
-            mShadeExpansionStateManager.onQsExpansionChanged(expanded);
+            mPanelViewControllerLazy.get().onQsExpansionChanged(expanded);
             mShadeLog.logQsExpansionChanged("QS Expansion Changed.", expanded,
                     getMinExpansionHeight(), getMaxExpansionHeight(),
                     mStackScrollerOverscrolling, mAnimatorExpand, mAnimating);
@@ -846,7 +839,7 @@
     /** Called when Shade view layout changed. Updates QS expansion or
      * starts size change animation if height has changed. */
     void handleShadeLayoutChanged(int oldMaxHeight) {
-        if (mExpanded && mFullyExpanded) {
+        if (getExpanded() && mFullyExpanded) {
             mExpansionHeight = mMaxExpansionHeight;
             if (mExpansionHeightSetToMaxListener != null) {
                 mExpansionHeightSetToMaxListener.onExpansionHeightSetToMax(true);
@@ -988,24 +981,24 @@
     }
 
     void updateQsState() {
-        boolean qsFullScreen = mExpanded && !mSplitShadeEnabled;
+        boolean qsFullScreen = getExpanded() && !mSplitShadeEnabled;
         mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen);
         mNotificationStackScrollLayoutController.setScrollingEnabled(
                 mBarState != KEYGUARD && (!qsFullScreen || mExpansionFromOverscroll));
 
         if (mQsStateUpdateListener != null) {
-            mQsStateUpdateListener.onQsStateUpdated(mExpanded, mStackScrollerOverscrolling);
+            mQsStateUpdateListener.onQsStateUpdated(getExpanded(), mStackScrollerOverscrolling);
         }
 
         if (mQs == null) return;
-        mQs.setExpanded(mExpanded);
+        mQs.setExpanded(getExpanded());
     }
 
     /** update expanded state of QS */
     public void updateExpansion() {
         if (mQs == null) return;
         final float squishiness;
-        if ((mExpandImmediate || mExpanded) && !mSplitShadeEnabled) {
+        if ((mExpandImmediate || getExpanded()) && !mSplitShadeEnabled) {
             squishiness = 1;
         } else if (mTransitioningToFullShadeProgress > 0.0f) {
             squishiness = mLockscreenShadeTransitionController.getQsSquishTransitionFraction();
@@ -1125,7 +1118,7 @@
         mMediaHierarchyManager.setCollapsingShadeFromQS(mExpandedWhenExpandingStarted
                 /* We also start expanding when flinging closed Qs. Let's exclude that */
                 && !mAnimating);
-        if (mExpanded) {
+        if (getExpanded()) {
             onExpansionStarted();
         }
         // Since there are QS tiles in the header now, we need to make sure we start listening
@@ -1234,7 +1227,7 @@
                     Math.min(top / (float) mScrimCornerRadius, 1f));
 
             float bottomRadius = mSplitShadeEnabled ? screenCornerRadius : 0;
-            if (!mExpanded) {
+            if (!getExpanded()) {
                 bottomRadius = calculateBottomCornerRadius(bottomRadius);
             }
             mScrimController.setNotificationBottomRadius(bottomRadius);
@@ -1547,7 +1540,7 @@
     @VisibleForTesting
     void onHeightChanged() {
         mMaxExpansionHeight = isQsFragmentCreated() ? mQs.getDesiredHeight() : 0;
-        if (mExpanded && mFullyExpanded) {
+        if (getExpanded() && mFullyExpanded) {
             mExpansionHeight = mMaxExpansionHeight;
             if (mExpansionHeightSetToMaxListener != null) {
                 mExpansionHeightSetToMaxListener.onExpansionHeightSetToMax(true);
@@ -2083,7 +2076,7 @@
         ipw.print("mTrackingPointer=");
         ipw.println(mTrackingPointer);
         ipw.print("mExpanded=");
-        ipw.println(mExpanded);
+        ipw.println(getExpanded());
         ipw.print("mFullyExpanded=");
         ipw.println(mFullyExpanded);
         ipw.print("mExpandImmediate=");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 9493989..fca98f5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -36,15 +36,12 @@
 class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents {
 
     private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
-    private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>()
     private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
     private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>()
 
     @PanelState private var state: Int = STATE_CLOSED
     @FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
     private var expanded: Boolean = false
-    private var qsExpanded: Boolean = false
-    private var qsExpansionFraction = 0f
     private var tracking: Boolean = false
     private var dragDownPxAmount: Float = 0f
 
@@ -64,15 +61,6 @@
         expansionListeners.remove(listener)
     }
 
-    fun addQsExpansionListener(listener: ShadeQsExpansionListener) {
-        qsExpansionListeners.add(listener)
-        listener.onQsExpansionChanged(qsExpanded)
-    }
-
-    fun removeQsExpansionListener(listener: ShadeQsExpansionListener) {
-        qsExpansionListeners.remove(listener)
-    }
-
     /** Adds a listener that will be notified when the panel state has changed. */
     fun addStateListener(listener: ShadeStateListener) {
         stateListeners.add(listener)
@@ -153,14 +141,6 @@
         expansionListeners.forEach { it.onPanelExpansionChanged(expansionChangeEvent) }
     }
 
-    /** Called when the quick settings expansion changes to fully expanded or collapsed. */
-    fun onQsExpansionChanged(qsExpanded: Boolean) {
-        this.qsExpanded = qsExpanded
-
-        debugLog("qsExpanded=$qsExpanded")
-        qsExpansionListeners.forEach { it.onQsExpansionChanged(qsExpanded) }
-    }
-
     /** Updates the panel state if necessary. */
     fun updateState(@PanelState state: Int) {
         debugLog(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 024c8e3..e2e4556 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -83,6 +83,17 @@
     val legacyExpandedOrAwaitingInputTransfer: StateFlow<Boolean>
 
     /**
+     * QuickSettingsController.mExpanded as a flow. Indicates that QS is in expanded state:
+     * - single pane shade: expanding shade and then expanding QS
+     * - split shade: just expanding shade (QS are expanded automatically)
+     */
+    @Deprecated("Use ShadeInteractor instead") val legacyIsQsExpanded: StateFlow<Boolean>
+
+    /** Sets whether QS is expanded. */
+    @Deprecated("Use ShadeInteractor instead")
+    fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean)
+
+    /**
      * Sets whether the expansion fraction is greater than zero or NPVC is about to accept an input
      * transfer from the status bar, home screen, or trackpad.
      */
@@ -175,6 +186,15 @@
     override val legacyExpandedOrAwaitingInputTransfer: StateFlow<Boolean> =
         _legacyExpandedOrAwaitingInputTransfer.asStateFlow()
 
+    private val _legacyIsQsExpanded = MutableStateFlow(false)
+    @Deprecated("Use ShadeInteractor instead")
+    override val legacyIsQsExpanded: StateFlow<Boolean> = _legacyIsQsExpanded.asStateFlow()
+
+    @Deprecated("Use ShadeInteractor instead")
+    override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) {
+        _legacyIsQsExpanded.value = legacyIsQsExpanded
+    }
+
     @Deprecated("Use ShadeInteractor instead")
     override fun setLegacyExpandedOrAwaitingInputTransfer(
         legacyExpandedOrAwaitingInputTransfer: Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index f043c71..a4c4503 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -121,16 +121,37 @@
 
     /**
      * The amount [0-1] QS has been opened. Normal shade with notifications (QQS) visible will
-     * report 0f.
+     * report 0f. If split shade is enabled, value matches shadeExpansion.
      */
     val qsExpansion: StateFlow<Float> =
         if (sceneContainerFlags.isEnabled()) {
-            sceneBasedExpansion(sceneInteractorProvider.get(), SceneKey.QuickSettings)
+            val qsExp = sceneBasedExpansion(sceneInteractorProvider.get(), SceneKey.QuickSettings)
+            combine(isSplitShadeEnabled, shadeExpansion, qsExp) {
+                    isSplitShadeEnabled,
+                    shadeExp,
+                    qsExp ->
+                    if (isSplitShadeEnabled) {
+                        shadeExp
+                    } else {
+                        qsExp
+                    }
+                }
                 .stateIn(scope, SharingStarted.Eagerly, 0f)
         } else {
             repository.qsExpansion
         }
 
+    /** Whether Quick Settings is expanded a non-zero amount. */
+    val isQsExpanded: StateFlow<Boolean> =
+        if (sceneContainerFlags.isEnabled()) {
+            qsExpansion
+                .map { it > 0 }
+                .distinctUntilChanged()
+                .stateIn(scope, SharingStarted.Eagerly, false)
+        } else {
+            repository.legacyIsQsExpanded
+        }
+
     /** The amount [0-1] either QS or the shade has been opened. */
     val anyExpansion: StateFlow<Float> =
         combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 4ea7026..e19fcd0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -32,17 +32,15 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN
-import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -64,13 +62,11 @@
     private val wakeUpCoordinator: NotificationWakeUpCoordinator,
     private val bypassController: KeyguardBypassController,
     private val headsUpManager: HeadsUpManager,
-    private val roundnessManager: NotificationRoundnessManager,
     configurationController: ConfigurationController,
     private val statusBarStateController: StatusBarStateController,
     private val falsingManager: FalsingManager,
-    shadeExpansionStateManager: ShadeExpansionStateManager,
+    private val shadeInteractor: ShadeInteractor,
     private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
-    private val falsingCollector: FalsingCollector,
     dumpManager: DumpManager
 ) : Gefingerpoken, Dumpable {
     companion object {
@@ -115,7 +111,6 @@
 
     private val isFalseTouch: Boolean
         get() = falsingManager.isFalseTouch(NOTIFICATION_DRAG_DOWN)
-    var qsExpanded: Boolean = false
     var pulseExpandAbortListener: Runnable? = null
     var bouncerShowing: Boolean = false
 
@@ -127,12 +122,6 @@
             }
         })
 
-        shadeExpansionStateManager.addQsExpansionListener { isQsExpanded ->
-            if (qsExpanded != isQsExpanded) {
-                qsExpanded = isQsExpanded
-            }
-        }
-
         mPowerManager = context.getSystemService(PowerManager::class.java)
         dumpManager.registerDumpable(this)
     }
@@ -148,7 +137,8 @@
     }
 
     private fun canHandleMotionEvent(): Boolean {
-        return wakeUpCoordinator.canShowPulsingHuns && !qsExpanded && !bouncerShowing
+        return wakeUpCoordinator.canShowPulsingHuns && !shadeInteractor.isQsExpanded.value &&
+            !bouncerShowing
     }
 
     private fun startExpansion(event: MotionEvent): Boolean {
@@ -379,7 +369,6 @@
             it.println("isExpanding: $isExpanding")
             it.println("leavingLockscreen: $leavingLockscreen")
             it.println("mPulsing: $mPulsing")
-            it.println("qsExpanded: $qsExpanded")
             it.println("bouncerShowing: $bouncerShowing")
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 5af7cfc..a36d36c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.events
 
-import androidx.core.animation.Animator
 import android.annotation.UiThread
 import android.graphics.Point
 import android.graphics.Rect
@@ -24,13 +23,15 @@
 import android.view.Gravity
 import android.view.View
 import android.widget.FrameLayout
-import com.android.internal.annotations.GuardedBy
-import com.android.systemui.res.R
+import androidx.core.animation.Animator
 import com.android.app.animation.Interpolators
+import com.android.internal.annotations.GuardedBy
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.StatusBarState.SHADE
 import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
@@ -43,6 +44,8 @@
 import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
 import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
 import com.android.systemui.util.leak.RotationUtils.Rotation
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
@@ -64,11 +67,12 @@
 @SysUISingleton
 open class PrivacyDotViewController @Inject constructor(
     @Main private val mainExecutor: Executor,
+    @Application scope: CoroutineScope,
     private val stateController: StatusBarStateController,
     private val configurationController: ConfigurationController,
     private val contentInsetsProvider: StatusBarContentInsetsProvider,
     private val animationScheduler: SystemStatusAnimationScheduler,
-    shadeExpansionStateManager: ShadeExpansionStateManager
+    shadeInteractor: ShadeInteractor?
 ) {
     private lateinit var tl: View
     private lateinit var tr: View
@@ -135,10 +139,12 @@
             }
         })
 
-        shadeExpansionStateManager.addQsExpansionListener { isQsExpanded ->
-            dlog("setQsExpanded $isQsExpanded")
-            synchronized(lock) {
-                nextViewState = nextViewState.copy(qsExpanded = isQsExpanded)
+        scope.launch {
+            shadeInteractor?.isQsExpanded?.collect { isQsExpanded ->
+                dlog("setQsExpanded $isQsExpanded")
+                synchronized(lock) {
+                    nextViewState = nextViewState.copy(qsExpanded = isQsExpanded)
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
new file mode 100644
index 0000000..75e0484
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.interruption
+
+import android.database.ContentObserver
+import android.hardware.display.AmbientDisplayConfiguration
+import android.os.Handler
+import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
+import android.provider.Settings.Global.HEADS_UP_OFF
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.util.settings.GlobalSettings
+
+class PeekDisabledSuppressor(
+    private val globalSettings: GlobalSettings,
+    private val headsUpManager: HeadsUpManager,
+    private val logger: NotificationInterruptLogger,
+    @Main private val mainHandler: Handler,
+) : VisualInterruptionCondition(types = setOf(PEEK), reason = "peek setting disabled") {
+    private var isEnabled = false
+
+    override fun shouldSuppress(): Boolean = !isEnabled
+
+    override fun start() {
+        val observer =
+            object : ContentObserver(mainHandler) {
+                override fun onChange(selfChange: Boolean) {
+                    val wasEnabled = isEnabled
+
+                    isEnabled =
+                        globalSettings.getInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_OFF) !=
+                            HEADS_UP_OFF
+
+                    // QQQ: Do we want to log this even if it hasn't changed?
+                    logger.logHeadsUpFeatureChanged(isEnabled)
+
+                    // QQQ: Is there a better place for this side effect? What if HeadsUpManager
+                    // registered for it directly?
+                    if (wasEnabled && !isEnabled) {
+                        logger.logWillDismissAll()
+                        headsUpManager.releaseAllImmediately()
+                    }
+                }
+            }
+
+        globalSettings.registerContentObserver(
+            globalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED),
+            /* notifyForDescendants = */ true,
+            observer
+        )
+
+        // QQQ: Do we need to register for SETTING_HEADS_UP_TICKER? It seems unused.
+
+        observer.onChange(/* selfChange = */ true)
+    }
+}
+
+class PulseDisabledSuppressor(
+    private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
+    private val userTracker: UserTracker,
+) : VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse setting disabled") {
+    override fun shouldSuppress(): Boolean =
+        !ambientDisplayConfiguration.pulseOnNotificationEnabled(userTracker.userId)
+}
+
+class PulseBatterySaverSuppressor(private val batteryController: BatteryController) :
+    VisualInterruptionCondition(
+        types = setOf(PULSE),
+        reason = "pulsing disabled by battery saver"
+    ) {
+    override fun shouldSuppress() = batteryController.isAodPowerSave()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 301ddbf..f2ade34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.notification.interruption;
 
+import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED;
+import static android.provider.Settings.Global.HEADS_UP_OFF;
+
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD;
 import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA;
@@ -24,12 +27,10 @@
 
 import android.app.Notification;
 import android.app.NotificationManager;
-import android.content.ContentResolver;
 import android.database.ContentObserver;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Handler;
 import android.os.PowerManager;
-import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 
 import androidx.annotation.NonNull;
@@ -48,6 +49,8 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.SystemClock;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -66,7 +69,6 @@
     private final List<NotificationInterruptSuppressor> mSuppressors = new ArrayList<>();
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardStateController mKeyguardStateController;
-    private final ContentResolver mContentResolver;
     private final PowerManager mPowerManager;
     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
     private final BatteryController mBatteryController;
@@ -77,6 +79,8 @@
     private final UiEventLogger mUiEventLogger;
     private final UserTracker mUserTracker;
     private final DeviceProvisionedController mDeviceProvisionedController;
+    private final SystemClock mSystemClock;
+    private final GlobalSettings mGlobalSettings;
 
     @VisibleForTesting
     protected boolean mUseHeadsUp = false;
@@ -111,7 +115,6 @@
 
     @Inject
     public NotificationInterruptStateProviderImpl(
-            ContentResolver contentResolver,
             PowerManager powerManager,
             AmbientDisplayConfiguration ambientDisplayConfiguration,
             BatteryController batteryController,
@@ -124,8 +127,9 @@
             KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
             UiEventLogger uiEventLogger,
             UserTracker userTracker,
-            DeviceProvisionedController deviceProvisionedController) {
-        mContentResolver = contentResolver;
+            DeviceProvisionedController deviceProvisionedController,
+            SystemClock systemClock,
+            GlobalSettings globalSettings) {
         mPowerManager = powerManager;
         mBatteryController = batteryController;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
@@ -137,15 +141,16 @@
         mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
         mUiEventLogger = uiEventLogger;
         mUserTracker = userTracker;
+        mDeviceProvisionedController = deviceProvisionedController;
+        mSystemClock = systemClock;
+        mGlobalSettings = globalSettings;
         ContentObserver headsUpObserver = new ContentObserver(mainHandler) {
             @Override
             public void onChange(boolean selfChange) {
-                boolean wasUsing = mUseHeadsUp;
-                mUseHeadsUp = ENABLE_HEADS_UP
-                        && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
-                        mContentResolver,
-                        Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
-                        Settings.Global.HEADS_UP_OFF);
+                final boolean wasUsing = mUseHeadsUp;
+                final boolean settingEnabled = HEADS_UP_OFF
+                        != mGlobalSettings.getInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_OFF);
+                mUseHeadsUp = ENABLE_HEADS_UP && settingEnabled;
                 mLogger.logHeadsUpFeatureChanged(mUseHeadsUp);
                 if (wasUsing != mUseHeadsUp) {
                     if (!mUseHeadsUp) {
@@ -157,16 +162,15 @@
         };
 
         if (ENABLE_HEADS_UP) {
-            mContentResolver.registerContentObserver(
-                    Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
+            mGlobalSettings.registerContentObserver(
+                    mGlobalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED),
                     true,
                     headsUpObserver);
-            mContentResolver.registerContentObserver(
-                    Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
+            mGlobalSettings.registerContentObserver(
+                    mGlobalSettings.getUriFor(SETTING_HEADS_UP_TICKER), true,
                     headsUpObserver);
         }
         headsUpObserver.onChange(true); // set up
-        mDeviceProvisionedController = deviceProvisionedController;
     }
 
     @Override
@@ -603,7 +607,7 @@
         }
 
         final long when = notification.when;
-        final long now = System.currentTimeMillis();
+        final long now = mSystemClock.currentTimeMillis();
         final long age = now - when;
 
         if (age < MAX_HUN_WHEN_AGE_MS) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
index 920bbe9..da8474e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.systemui.statusbar.notification.interruption
 
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -51,6 +52,13 @@
     }
 
     /**
+     * Initializes the provider.
+     *
+     * Must be called before any method except [addLegacySuppressor].
+     */
+    fun start() {}
+
+    /**
      * Adds a [component][suppressor] that can suppress visual interruptions.
      *
      * This class may call suppressors in any order.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
new file mode 100644
index 0000000..bae7134
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.interruption
+
+import android.hardware.display.AmbientDisplayConfiguration
+import android.os.Handler
+import android.os.PowerManager
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+
+class VisualInterruptionDecisionProviderImpl
+@Inject
+constructor(
+    private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
+    private val batteryController: BatteryController,
+    private val globalSettings: GlobalSettings,
+    private val headsUpManager: HeadsUpManager,
+    private val logger: NotificationInterruptLogger,
+    @Main private val mainHandler: Handler,
+    private val powerManager: PowerManager,
+    private val statusBarStateController: StatusBarStateController,
+    private val systemClock: SystemClock,
+    private val userTracker: UserTracker,
+) : VisualInterruptionDecisionProvider {
+    private var started = false
+
+    override fun start() {
+        check(!started)
+
+        addCondition(PeekDisabledSuppressor(globalSettings, headsUpManager, logger, mainHandler))
+        addCondition(PulseDisabledSuppressor(ambientDisplayConfiguration, userTracker))
+        addCondition(PulseBatterySaverSuppressor(batteryController))
+
+        started = true
+    }
+
+    private class DecisionImpl(
+        override val shouldInterrupt: Boolean,
+        override val logReason: String
+    ) : Decision
+
+    private class FullScreenIntentDecisionImpl(
+        override val shouldInterrupt: Boolean,
+        override val wouldInterruptWithoutDnd: Boolean,
+        override val logReason: String,
+        val originalEntry: NotificationEntry,
+    ) : FullScreenIntentDecision {
+        var hasBeenLogged = false
+    }
+
+    private val legacySuppressors = mutableSetOf<NotificationInterruptSuppressor>()
+    private val conditions = mutableListOf<VisualInterruptionCondition>()
+    private val filters = mutableListOf<VisualInterruptionFilter>()
+
+    override fun addLegacySuppressor(suppressor: NotificationInterruptSuppressor) {
+        legacySuppressors.add(suppressor)
+    }
+
+    override fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor) {
+        legacySuppressors.remove(suppressor)
+    }
+
+    fun addCondition(condition: VisualInterruptionCondition) {
+        conditions.add(condition)
+        condition.start()
+    }
+
+    fun addFilter(filter: VisualInterruptionFilter) {
+        filters.add(filter)
+        filter.start()
+    }
+
+    override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision {
+        check(started)
+        return makeHeadsUpDecision(entry)
+    }
+
+    override fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision {
+        check(started)
+        return makeHeadsUpDecision(entry).also { logHeadsUpDecision(entry, it) }
+    }
+
+    override fun makeUnloggedFullScreenIntentDecision(
+        entry: NotificationEntry
+    ): FullScreenIntentDecision {
+        check(started)
+        return makeFullScreenDecision(entry)
+    }
+
+    override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) {
+        check(started)
+        val decisionImpl =
+            decision as? FullScreenIntentDecisionImpl
+                ?: run {
+                    Log.wtf(TAG, "Wrong subclass of FullScreenIntentDecision: $decision")
+                    return
+                }
+        if (decision.hasBeenLogged) {
+            Log.wtf(TAG, "Already logged decision: $decision")
+            return
+        }
+        logFullScreenIntentDecision(decisionImpl)
+        decision.hasBeenLogged = true
+    }
+
+    override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision {
+        check(started)
+        return makeBubbleDecision(entry).also { logBubbleDecision(entry, it) }
+    }
+
+    private fun makeHeadsUpDecision(entry: NotificationEntry): DecisionImpl {
+        if (statusBarStateController.isDozing) {
+            return makePulseDecision(entry)
+        } else {
+            return makePeekDecision(entry)
+        }
+    }
+
+    private fun makePeekDecision(entry: NotificationEntry): DecisionImpl {
+        checkConditions(PEEK)?.let {
+            return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+        }
+        checkFilters(PEEK, entry)?.let {
+            return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+        }
+        checkSuppressors(entry)?.let {
+            return DecisionImpl(
+                shouldInterrupt = false,
+                logReason = "${it.name}.suppressInterruptions"
+            )
+        }
+        checkAwakeSuppressors(entry)?.let {
+            return DecisionImpl(
+                shouldInterrupt = false,
+                logReason = "${it.name}.suppressAwakeInterruptions"
+            )
+        }
+        checkAwakeHeadsUpSuppressors(entry)?.let {
+            return DecisionImpl(
+                shouldInterrupt = false,
+                logReason = "${it.name}.suppressAwakeHeadsUpInterruptions"
+            )
+        }
+        return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")
+    }
+
+    private fun makePulseDecision(entry: NotificationEntry): DecisionImpl {
+        checkConditions(PULSE)?.let {
+            return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+        }
+        checkFilters(PULSE, entry)?.let {
+            return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+        }
+        checkSuppressors(entry)?.let {
+            return DecisionImpl(
+                shouldInterrupt = false,
+                logReason = "${it.name}.suppressInterruptions"
+            )
+        }
+        return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")
+    }
+
+    private fun makeBubbleDecision(entry: NotificationEntry): DecisionImpl {
+        checkConditions(BUBBLE)?.let {
+            return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+        }
+        checkFilters(BUBBLE, entry)?.let {
+            return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+        }
+        checkSuppressors(entry)?.let {
+            return DecisionImpl(
+                shouldInterrupt = false,
+                logReason = "${it.name}.suppressInterruptions"
+            )
+        }
+        checkAwakeSuppressors(entry)?.let {
+            return DecisionImpl(
+                shouldInterrupt = false,
+                logReason = "${it.name}.suppressAwakeInterruptions"
+            )
+        }
+        return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")
+    }
+
+    private fun makeFullScreenDecision(entry: NotificationEntry): FullScreenIntentDecisionImpl {
+        // Not yet implemented.
+        return FullScreenIntentDecisionImpl(
+            shouldInterrupt = true,
+            wouldInterruptWithoutDnd = true,
+            logReason = "FSI logic not yet implemented in VisualInterruptionDecisionProviderImpl",
+            originalEntry = entry
+        )
+    }
+
+    private fun logHeadsUpDecision(entry: NotificationEntry, decision: DecisionImpl) {
+        // Not yet implemented.
+    }
+
+    private fun logBubbleDecision(entry: NotificationEntry, decision: DecisionImpl) {
+        // Not yet implemented.
+    }
+
+    private fun logFullScreenIntentDecision(decision: FullScreenIntentDecisionImpl) {
+        // Not yet implemented.
+    }
+
+    private fun checkSuppressors(entry: NotificationEntry) =
+        legacySuppressors.firstOrNull { it.suppressInterruptions(entry) }
+
+    private fun checkAwakeSuppressors(entry: NotificationEntry) =
+        legacySuppressors.firstOrNull { it.suppressAwakeInterruptions(entry) }
+
+    private fun checkAwakeHeadsUpSuppressors(entry: NotificationEntry) =
+        legacySuppressors.firstOrNull { it.suppressAwakeHeadsUp(entry) }
+
+    private fun checkConditions(type: VisualInterruptionType): VisualInterruptionCondition? =
+        conditions.firstOrNull { it.types.contains(type) && it.shouldSuppress() }
+
+    private fun checkFilters(
+        type: VisualInterruptionType,
+        entry: NotificationEntry
+    ): VisualInterruptionFilter? =
+        filters.firstOrNull { it.types.contains(type) && it.shouldSuppress(entry) }
+}
+
+private const val TAG = "VisualInterruptionDecisionProviderImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt
new file mode 100644
index 0000000..2624363
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.interruption
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the visual interruptions refactor flag state. */
+object VisualInterruptionRefactor {
+    const val FLAG_NAME = Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR
+
+    /** Whether the refactor is enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.visualInterruptionsRefactor()
+
+    /**
+     * 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/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
index 4ef80e3..39199df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.systemui.statusbar.notification.interruption
 
 import com.android.internal.logging.UiEventLogger.UiEventEnum
@@ -50,6 +51,12 @@
 
     /** An optional UiEvent ID to be recorded when this suppresses an interruption. */
     val uiEventId: UiEventEnum?
+
+    /**
+     * Called after the suppressor is added to the [VisualInterruptionDecisionProvider] but before
+     * any other methods are called on the suppressor.
+     */
+    fun start() {}
 }
 
 /** A reason why visual interruptions might be suppressed regardless of the notification. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 2af7181..6785da4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -59,10 +59,8 @@
 
                 launch {
                     viewModel.position.collect {
-                        controller.updateTopPadding(
-                            it.top,
-                            controller.isAddOrRemoveAnimationPending()
-                        )
+                        val animate = it.animate || controller.isAddOrRemoveAnimationPending()
+                        controller.updateTopPadding(it.top, animate)
                     }
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 1229cb9..b86b5dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -118,8 +119,15 @@
                     }
                 }
             } else {
-                interactor.topPosition.map { top ->
-                    keyguardInteractor.sharedNotificationContainerPosition.value.copy(top = top)
+                interactor.topPosition.sample(shadeInteractor.qsExpansion, ::Pair).map {
+                    (top, qsExpansion) ->
+                    // When QS expansion > 0, it should directly set the top padding so do not
+                    // animate it
+                    val animate = qsExpansion == 0f
+                    keyguardInteractor.sharedNotificationContainerPosition.value.copy(
+                        top = top,
+                        animate = animate
+                    )
                 }
             }
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index f899e2f..5e7b857 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -505,7 +505,6 @@
                 this.authenticators = authenticators
             }
         },
-        featureFlags,
         testScope.backgroundScope,
         fingerprintProps,
         faceProps,
@@ -519,9 +518,7 @@
         PromptViewModel(
             displayStateInteractor,
             promptSelectorInteractor,
-            vibrator,
             context,
-            featureFlags
         ),
         { credentialViewModel },
         Handler(TestableLooper.get(this).looper),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 885abcb..11c5d3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -88,7 +88,6 @@
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
-import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -195,7 +194,6 @@
     private Handler mHandler;
     private DelayableExecutor mBackgroundExecutor;
     private TestableAuthController mAuthController;
-    private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
     @Before
     public void setup() throws RemoteException {
@@ -1023,7 +1021,7 @@
         private PromptInfo mLastBiometricPromptInfo;
 
         TestableAuthController(Context context) {
-            super(context, mFeatureFlags, null /* applicationCoroutineScope */,
+            super(context, null /* applicationCoroutineScope */,
                     mExecution, mCommandQueue, mActivityTaskManager, mWindowManager,
                     mFingerprintManager, mFaceManager, () -> mUdfpsController,
                     () -> mSideFpsController, mDisplayManager, mWakefulnessLifecycle,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index b695a0e..d06cbbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -44,18 +44,16 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.display.data.repository.FakeDisplayRepository
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -64,9 +62,7 @@
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 import org.mockito.Mock
-import org.mockito.Mockito.never
 import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 
 private const val USER_ID = 4
@@ -95,7 +91,6 @@
     private lateinit var selector: PromptSelectorInteractor
     private lateinit var viewModel: PromptViewModel
     private lateinit var iconViewModel: PromptIconViewModel
-    private val featureFlags = FakeFeatureFlags()
 
     @Before
     fun setup() {
@@ -125,10 +120,8 @@
             PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
         selector.resetPrompt()
 
-        viewModel =
-            PromptViewModel(displayStateInteractor, selector, vibrator, mContext, featureFlags)
+        viewModel = PromptViewModel(displayStateInteractor, selector, mContext)
         iconViewModel = viewModel.iconViewModel
-        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
     }
 
     @Test
@@ -180,26 +173,29 @@
     }
 
     @Test
-    fun play_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() =
+    fun set_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() =
         runGenericTest {
             val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
 
             viewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
 
-            verify(vibrator, if (expectConfirmation) never() else times(1))
-                .vibrateAuthSuccess(any())
+            val confirmConstant by collectLastValue(viewModel.hapticsToPlay)
+            assertThat(confirmConstant)
+                .isEqualTo(
+                    if (expectConfirmation) HapticFeedbackConstants.NO_HAPTICS
+                    else HapticFeedbackConstants.CONFIRM
+                )
 
             if (expectConfirmation) {
                 viewModel.confirmAuthenticated()
             }
 
-            verify(vibrator).vibrateAuthSuccess(any())
-            verify(vibrator, never()).vibrateAuthError(any())
+            val confirmedConstant by collectLastValue(viewModel.hapticsToPlay)
+            assertThat(confirmedConstant).isEqualTo(HapticFeedbackConstants.CONFIRM)
         }
 
     @Test
-    fun playSuccessHaptic_onwayHapticsEnabled_SetsConfirmConstant() = runGenericTest {
-        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+    fun playSuccessHaptic_SetsConfirmConstant() = runGenericTest {
         val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
         viewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
 
@@ -212,8 +208,7 @@
     }
 
     @Test
-    fun playErrorHaptic_onwayHapticsEnabled_SetsRejectConstant() = runGenericTest {
-        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+    fun playErrorHaptic_SetsRejectConstant() = runGenericTest {
         viewModel.showTemporaryError("test", "messageAfterError", false)
 
         val currentConstant by collectLastValue(viewModel.hapticsToPlay)
@@ -251,7 +246,8 @@
                             DisplayRotation.ROTATION_180 ->
                                 R.raw.biometricprompt_fingerprint_to_error_landscape
                             DisplayRotation.ROTATION_270 ->
-                                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+                                R.raw
+                                    .biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
                             else -> throw Exception("invalid rotation")
                         }
                     assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
@@ -496,7 +492,8 @@
                             DisplayRotation.ROTATION_180 ->
                                 R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
                             DisplayRotation.ROTATION_270 ->
-                                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
+                                R.raw
+                                    .biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
                             else -> throw Exception("invalid rotation")
                         }
                     assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
@@ -733,7 +730,7 @@
     }
 
     @Test
-    fun plays_haptic_on_errors() = runGenericTest {
+    fun set_haptic_on_errors() = runGenericTest {
         viewModel.showTemporaryError(
             "so sad",
             messageAfterError = "",
@@ -741,8 +738,8 @@
             hapticFeedback = true,
         )
 
-        verify(vibrator).vibrateAuthError(any())
-        verify(vibrator, never()).vibrateAuthSuccess(any())
+        val constant by collectLastValue(viewModel.hapticsToPlay)
+        assertThat(constant).isEqualTo(HapticFeedbackConstants.REJECT)
     }
 
     @Test
@@ -754,8 +751,8 @@
             hapticFeedback = false,
         )
 
-        verify(vibrator, never()).vibrateAuthError(any())
-        verify(vibrator, never()).vibrateAuthSuccess(any())
+        val constant by collectLastValue(viewModel.hapticsToPlay)
+        assertThat(constant).isEqualTo(HapticFeedbackConstants.NO_HAPTICS)
     }
 
     private suspend fun TestScope.showTemporaryErrors(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 80fe9e7..fcb18f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.when;
 
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.view.MotionEvent;
 
 import androidx.test.filters.SmallTest;
@@ -35,13 +36,14 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerFake;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.sensors.ThresholdSensor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -54,8 +56,11 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import kotlinx.coroutines.flow.StateFlowKt;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class FalsingCollectorImplTest extends SysuiTestCase {
 
     private FalsingCollectorImpl mFalsingCollector;
@@ -73,7 +78,9 @@
     @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
-    private ShadeExpansionStateManager mShadeExpansionStateManager;
+    private ShadeInteractor mShadeInteractor;
+    @Mock
+    private JavaAdapter mJavaAdapter;
     @Mock
     private BatteryController mBatteryController;
     @Mock
@@ -88,12 +95,16 @@
 
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mShadeInteractor.isQsExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
 
         mFalsingCollector = new FalsingCollectorImpl(mFalsingDataProvider, mFalsingManager,
                 mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor,
-                mStatusBarStateController, mKeyguardStateController, mShadeExpansionStateManager,
-                mBatteryController,
-                mDockManager, mFakeExecutor, mFakeSystemClock, () -> mSelectedUserInteractor);
+                mStatusBarStateController, mKeyguardStateController,
+                () -> mShadeInteractor, mBatteryController,
+                mDockManager, mFakeExecutor,
+                mJavaAdapter, mFakeSystemClock, () -> mSelectedUserInteractor
+        );
+        mFalsingCollector.init();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 8416c46..6a79ee8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -24,8 +24,6 @@
 import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.controls.settings.ControlsSettingsDialogManager
 import com.android.systemui.controls.settings.FakeControlsSettingsRepository
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -83,8 +81,6 @@
     private lateinit var action: ControlActionCoordinatorImpl.Action
     private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
 
-    private val featureFlags = FakeFeatureFlags()
-
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -104,7 +100,6 @@
                 metricsLogger,
                 vibratorHelper,
                 controlsSettingsRepository,
-                featureFlags
         ))
         coordinator.activityContext = mContext
 
@@ -200,31 +195,7 @@
     }
 
     @Test
-    fun drag_isEdge_oneWayHapticsDisabled_usesVibrate() {
-        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
-
-        coordinator.drag(cvh, true)
-
-        verify(vibratorHelper).vibrate(
-            Vibrations.rangeEdgeEffect
-        )
-    }
-
-    @Test
-    fun drag_isNotEdge_oneWayHapticsDisabled_usesVibrate() {
-        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
-
-        coordinator.drag(cvh, false)
-
-        verify(vibratorHelper).vibrate(
-            Vibrations.rangeMiddleEffect
-        )
-    }
-
-    @Test
-    fun drag_isEdge_oneWayHapticsEnabled_usesPerformHapticFeedback() {
-        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
-
+    fun drag_isEdge_performsSegmentTickHaptics() {
         coordinator.drag(cvh, true)
 
         verify(vibratorHelper).performHapticFeedback(
@@ -234,9 +205,7 @@
     }
 
     @Test
-    fun drag_isNotEdge_oneWayHapticsEnabled_usesPerformHapticFeedback() {
-        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
-
+    fun drag_isNotEdge_performsFrequentTickHaptics() {
         coordinator.drag(cvh, false)
 
         verify(vibratorHelper).performHapticFeedback(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 2b280c0..814a317 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -253,7 +253,6 @@
                 mKeyguardStateController,
                 mScreenOffAnimationController,
                 mAuthController,
-                mShadeExpansionStateManager,
                 () -> mShadeInteractor,
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 4f545cb..b80771f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -179,6 +179,8 @@
             val translationY by collectLastValue(underTest.translationY)
             val scale by collectLastValue(underTest.scale)
 
+            underTest.statusViewTop = 100
+
             // Set to dozing (on AOD)
             dozeAmountTransitionStep.emit(TransitionStep(value = 1f))
             // Trigger a change to the burn-in model
@@ -200,6 +202,37 @@
         }
 
     @Test
+    fun translationAndScaleFromBurnFullyDozingStaysOutOfTopInset() =
+        testScope.runTest {
+            val translationX by collectLastValue(underTest.translationX)
+            val translationY by collectLastValue(underTest.translationY)
+            val scale by collectLastValue(underTest.scale)
+
+            underTest.statusViewTop = 100
+            underTest.topInset = 80
+
+            // Set to dozing (on AOD)
+            dozeAmountTransitionStep.emit(TransitionStep(value = 1f))
+            // Trigger a change to the burn-in model
+            burnInFlow.value =
+                BurnInModel(
+                    translationX = 20,
+                    translationY = -30,
+                    scale = 0.5f,
+                )
+            assertThat(translationX).isEqualTo(20)
+            // -20 instead of -30, due to inset of 80
+            assertThat(translationY).isEqualTo(-20)
+            assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */))
+
+            // Set to the beginning of GONE->AOD transition
+            goneToAodTransitionStep.emit(TransitionStep(value = 0f))
+            assertThat(translationX).isEqualTo(0)
+            assertThat(translationY).isEqualTo(0)
+            assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */))
+        }
+
+    @Test
     fun translationAndScaleFromBurnInUseScaleOnly() =
         testScope.runTest {
             whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true)
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 2ce4b04..446a0b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -99,7 +99,6 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.ui.view.KeyguardRootView;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
@@ -148,7 +147,6 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
@@ -335,7 +333,6 @@
     @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     @Mock private JavaAdapter mJavaAdapter;
     @Mock private CastController mCastController;
-    @Mock private KeyguardRootView mKeyguardRootView;
     @Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
     @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm;
 
@@ -575,14 +572,13 @@
         PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
                 mContext,
                 coordinator,
-                mKeyguardBypassController, mHeadsUpManager,
-                mock(NotificationRoundnessManager.class),
+                mKeyguardBypassController,
+                mHeadsUpManager,
                 mConfigurationController,
                 mStatusBarStateController,
                 mFalsingManager,
-                mShadeExpansionStateManager,
+                mShadeInteractor,
                 mLockscreenShadeTransitionController,
-                new FalsingCollectorFake(),
                 mDumpManager);
         when(mKeyguardStatusViewComponentFactory.build(any(), any()))
                 .thenReturn(mKeyguardStatusViewComponent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 4ba850c..8e0cf7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -254,7 +254,6 @@
                 mKeyguardStateController,
                 mScreenOffAnimationController,
                 mAuthController,
-                mShadeExpansionStateManager,
                 () -> mShadeInteractor,
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index e70dbc7..778cfa6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -18,7 +18,6 @@
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import android.testing.TestableResources
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowInsets
@@ -27,7 +26,6 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -38,6 +36,8 @@
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.recents.OverviewProxyService
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -46,6 +46,7 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.function.Consumer
+import kotlinx.coroutines.flow.MutableStateFlow
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -66,7 +67,7 @@
 
 /** Uses Flags.MIGRATE_NSSL set to false. If all goes well, this set of tests will be deleted. */
 @RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
 
@@ -74,7 +75,7 @@
     @Mock lateinit var navigationModeController: NavigationModeController
     @Mock lateinit var overviewProxyService: OverviewProxyService
     @Mock lateinit var shadeHeaderController: ShadeHeaderController
-    @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+    @Mock lateinit var shadeInteractor: ShadeInteractor
     @Mock lateinit var fragmentService: FragmentService
     @Mock lateinit var fragmentHostManager: FragmentHostManager
     @Mock
@@ -88,7 +89,6 @@
 
     lateinit var underTest: NotificationsQSContainerController
 
-    private lateinit var fakeResources: TestableResources
     private lateinit var featureFlags: FakeFeatureFlags
     private lateinit var navigationModeCallback: ModeChangedListener
     private lateinit var taskbarVisibilityCallback: OverviewProxyListener
@@ -111,6 +111,7 @@
         whenever(view.resources).thenReturn(mContext.resources)
 
         whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
+        whenever(shadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
 
         underTest =
             NotificationsQSContainerController(
@@ -118,7 +119,7 @@
                 navigationModeController,
                 overviewProxyService,
                 shadeHeaderController,
-                shadeExpansionStateManager,
+                shadeInteractor,
                 fragmentService,
                 delayableExecutor,
                 featureFlags,
@@ -475,7 +476,7 @@
                 navigationModeController,
                 overviewProxyService,
                 shadeHeaderController,
-                shadeExpansionStateManager,
+                shadeInteractor,
                 fragmentService,
                 delayableExecutor,
                 featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index ac8c924..2342003 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -18,7 +18,6 @@
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import android.testing.TestableResources
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowInsets
@@ -27,7 +26,6 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -38,6 +36,8 @@
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.recents.OverviewProxyService
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -46,6 +46,7 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.function.Consumer
+import kotlinx.coroutines.flow.MutableStateFlow
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -65,7 +66,7 @@
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class NotificationsQSContainerControllerTest : SysuiTestCase() {
 
@@ -73,7 +74,7 @@
     @Mock lateinit var navigationModeController: NavigationModeController
     @Mock lateinit var overviewProxyService: OverviewProxyService
     @Mock lateinit var shadeHeaderController: ShadeHeaderController
-    @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+    @Mock lateinit var shadeInteractor: ShadeInteractor
     @Mock lateinit var fragmentService: FragmentService
     @Mock lateinit var fragmentHostManager: FragmentHostManager
     @Mock
@@ -87,7 +88,6 @@
 
     lateinit var underTest: NotificationsQSContainerController
 
-    private lateinit var fakeResources: TestableResources
     private lateinit var featureFlags: FakeFeatureFlags
     private lateinit var navigationModeCallback: ModeChangedListener
     private lateinit var taskbarVisibilityCallback: OverviewProxyListener
@@ -111,13 +111,15 @@
 
         whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
 
+        whenever(shadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
+
         underTest =
             NotificationsQSContainerController(
                 view,
                 navigationModeController,
                 overviewProxyService,
                 shadeHeaderController,
-                shadeExpansionStateManager,
+                shadeInteractor,
                 fragmentService,
                 delayableExecutor,
                 featureFlags,
@@ -458,7 +460,7 @@
                 navigationModeController,
                 overviewProxyService,
                 shadeHeaderController,
-                shadeExpansionStateManager,
+                shadeInteractor,
                 fragmentService,
                 delayableExecutor,
                 featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
index 6eeafefd..e920687 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
@@ -189,4 +189,13 @@
             underTest.setUdfpsTransitionToFullShadeProgress(1f)
             assertThat(underTest.udfpsTransitionToFullShadeProgress.value).isEqualTo(1f)
         }
+
+    @Test
+    fun updateLegacyIsQsExpanded() =
+        testScope.runTest {
+            assertThat(underTest.legacyIsQsExpanded.value).isEqualTo(false)
+
+            underTest.setLegacyIsQsExpanded(true)
+            assertThat(underTest.legacyIsQsExpanded.value).isEqualTo(true)
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
index 20e5c43..49e5c45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
@@ -21,18 +21,17 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
 import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.flow.MutableStateFlow
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -53,19 +52,18 @@
     private val wakeUpCoordinator: NotificationWakeUpCoordinator = mock()
     private val bypassController: KeyguardBypassController = mock()
     private val headsUpManager: HeadsUpManager = mock()
-    private val roundnessManager: NotificationRoundnessManager = mock()
     private val configurationController: ConfigurationController = mock()
     private val statusBarStateController: StatusBarStateController = mock()
     private val falsingManager: FalsingManager = mock()
-    private val shadeExpansionStateManager: ShadeExpansionStateManager = mock()
+    private val shadeInteractor: ShadeInteractor = mock()
     private val lockscreenShadeTransitionController: LockscreenShadeTransitionController = mock()
-    private val falsingCollector: FalsingCollector = mock()
     private val dumpManager: DumpManager = mock()
     private val expandableView: ExpandableView = mock()
 
     @Before
     fun setUp() {
         whenever(expandableView.collapsedHeight).thenReturn(collapsedHeight)
+        whenever(shadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
 
         pulseExpansionHandler =
             PulseExpansionHandler(
@@ -73,13 +71,11 @@
                 wakeUpCoordinator,
                 bypassController,
                 headsUpManager,
-                roundnessManager,
                 configurationController,
                 statusBarStateController,
                 falsingManager,
-                shadeExpansionStateManager,
+                shadeInteractor,
                 lockscreenShadeTransitionController,
-                falsingCollector,
                 dumpManager
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 50ce265..1c62161 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -13,8 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.statusbar.notification.interruption;
 
+package com.android.systemui.statusbar.notification.interruption;
 
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
@@ -27,6 +27,8 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
+import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED;
+import static android.provider.Settings.Global.HEADS_UP_ON;
 
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -61,9 +63,9 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -74,6 +76,8 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.FakeGlobalSettings;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -120,6 +124,8 @@
     UserTracker mUserTracker;
     @Mock
     DeviceProvisionedController mDeviceProvisionedController;
+    FakeSystemClock mSystemClock;
+    FakeGlobalSettings mGlobalSettings;
 
     private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider;
 
@@ -129,10 +135,12 @@
         when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
 
         mUiEventLoggerFake = new UiEventLoggerFake();
+        mSystemClock = new FakeSystemClock();
+        mGlobalSettings = new FakeGlobalSettings();
+        mGlobalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON);
 
         mNotifInterruptionStateProvider =
                 new NotificationInterruptStateProviderImpl(
-                        mContext.getContentResolver(),
                         mPowerManager,
                         mAmbientDisplayConfiguration,
                         mBatteryController,
@@ -145,7 +153,9 @@
                         mKeyguardNotificationVisibilityProvider,
                         mUiEventLoggerFake,
                         mUserTracker,
-                        mDeviceProvisionedController);
+                        mDeviceProvisionedController,
+                        mSystemClock,
+                        mGlobalSettings);
         mNotifInterruptionStateProvider.mUseHeadsUp = true;
     }
 
@@ -426,7 +436,7 @@
     }
 
     private long makeWhenHoursAgo(long hoursAgo) {
-        return System.currentTimeMillis() - (1000 * 60 * 60 * hoursAgo);
+        return mSystemClock.currentTimeMillis() - (1000 * 60 * 60 * hoursAgo);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
index 947bcfb..1d2055e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.statusbar.notification.interruption
 
 import android.testing.AndroidTestingRunner
@@ -19,27 +35,27 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() {
-    override val provider: VisualInterruptionDecisionProvider
-        get() =
-            NotificationInterruptStateProviderWrapper(
-                NotificationInterruptStateProviderImpl(
-                        context.contentResolver,
-                        powerManager,
-                        ambientDisplayConfiguration,
-                        batteryController,
-                        statusBarStateController,
-                        keyguardStateController,
-                        headsUpManager,
-                        logger,
-                        mainHandler,
-                        flags,
-                        keyguardNotificationVisibilityProvider,
-                        uiEventLogger,
-                        userTracker,
-                        deviceProvisionedController
-                    )
-                    .also { it.mUseHeadsUp = true }
+    override val provider by lazy {
+        NotificationInterruptStateProviderWrapper(
+            NotificationInterruptStateProviderImpl(
+                powerManager,
+                ambientDisplayConfiguration,
+                batteryController,
+                statusBarStateController,
+                keyguardStateController,
+                headsUpManager,
+                logger,
+                mainHandler,
+                flags,
+                keyguardNotificationVisibilityProvider,
+                uiEventLogger,
+                userTracker,
+                deviceProvisionedController,
+                systemClock,
+                globalSettings,
             )
+        )
+    }
 
     // Tests of internals of the wrapper:
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
new file mode 100644
index 0000000..ff89bdb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.interruption
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionProviderTestBase() {
+    override val provider by lazy {
+        VisualInterruptionDecisionProviderImpl(
+            ambientDisplayConfiguration,
+            batteryController,
+            globalSettings,
+            headsUpManager,
+            logger,
+            mainHandler,
+            powerManager,
+            statusBarStateController,
+            systemClock,
+            userTracker,
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 6f4bbd5..5511194 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.statusbar.notification.interruption
 
 import android.app.ActivityManager
@@ -9,12 +25,16 @@
 import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE
 import android.app.PendingIntent
 import android.app.PendingIntent.FLAG_MUTABLE
+import android.content.Context
 import android.content.Intent
 import android.content.pm.UserInfo
 import android.graphics.drawable.Icon
 import android.hardware.display.FakeAmbientDisplayConfiguration
-import android.os.Handler
+import android.os.Looper
 import android.os.PowerManager
+import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
+import android.provider.Settings.Global.HEADS_UP_OFF
+import android.provider.Settings.Global.HEADS_UP_ON
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.res.R
@@ -31,8 +51,12 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeGlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.utils.leaks.FakeBatteryController
 import com.android.systemui.utils.leaks.LeakCheckedTest
+import com.android.systemui.utils.os.FakeHandler
+import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
@@ -45,172 +69,399 @@
     protected val batteryController = FakeBatteryController(leakCheck)
     protected val deviceProvisionedController: DeviceProvisionedController = mock()
     protected val flags: NotifPipelineFlags = mock()
+    protected val globalSettings = FakeGlobalSettings()
     protected val headsUpManager: HeadsUpManager = mock()
     protected val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider =
         mock()
     protected val keyguardStateController: KeyguardStateController = mock()
     protected val logger: NotificationInterruptLogger = mock()
-    protected val mainHandler: Handler = mock()
+    protected val mainHandler = FakeHandler(Looper.getMainLooper())
     protected val powerManager: PowerManager = mock()
     protected val statusBarStateController = FakeStatusBarStateController()
+    protected val systemClock = FakeSystemClock()
     protected val uiEventLogger = UiEventLoggerFake()
     protected val userTracker = FakeUserTracker()
 
     protected abstract val provider: VisualInterruptionDecisionProvider
 
+    private val neverSuppresses = object : NotificationInterruptSuppressor {}
+
+    private val alwaysSuppressesInterruptions =
+        object : NotificationInterruptSuppressor {
+            override fun suppressInterruptions(entry: NotificationEntry?) = true
+        }
+
+    private val alwaysSuppressesAwakeInterruptions =
+        object : NotificationInterruptSuppressor {
+            override fun suppressAwakeInterruptions(entry: NotificationEntry?) = true
+        }
+
+    private val alwaysSuppressesAwakeHeadsUp =
+        object : NotificationInterruptSuppressor {
+            override fun suppressAwakeHeadsUp(entry: NotificationEntry?) = true
+        }
+
     @Before
     fun setUp() {
+        globalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON)
+
         val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0)
         userTracker.set(listOf(user), /* currentUserIndex = */ 0)
 
-        whenever(headsUpManager.isSnoozed(any())).thenReturn(false)
         whenever(keyguardNotificationVisibilityProvider.shouldHideNotification(any()))
             .thenReturn(false)
+
+        provider.start()
     }
 
     @Test
     fun testShouldPeek() {
-        ensureStateForPeek()
+        ensurePeekState()
+        assertShouldHeadsUp(buildPeekEntry())
+    }
 
-        assertTrue(provider.makeUnloggedHeadsUpDecision(createPeekEntry()).shouldInterrupt)
+    @Test
+    fun testShouldNotPeek_settingDisabled() {
+        ensurePeekState { hunSettingEnabled = false }
+        assertShouldNotHeadsUp(buildPeekEntry())
+    }
+
+    @Test
+    fun testShouldPeek_defaultLegacySuppressor() {
+        ensurePeekState()
+        provider.addLegacySuppressor(neverSuppresses)
+        assertShouldHeadsUp(buildPeekEntry())
+    }
+
+    @Test
+    fun testShouldNotPeek_legacySuppressInterruptions() {
+        ensurePeekState()
+        provider.addLegacySuppressor(alwaysSuppressesInterruptions)
+        assertShouldNotHeadsUp(buildPeekEntry())
+    }
+
+    @Test
+    fun testShouldNotPeek_legacySuppressAwakeInterruptions() {
+        ensurePeekState()
+        provider.addLegacySuppressor(alwaysSuppressesAwakeInterruptions)
+        assertShouldNotHeadsUp(buildPeekEntry())
+    }
+
+    @Test
+    fun testShouldNotPeek_legacySuppressAwakeHeadsUp() {
+        ensurePeekState()
+        provider.addLegacySuppressor(alwaysSuppressesAwakeHeadsUp)
+        assertShouldNotHeadsUp(buildPeekEntry())
     }
 
     @Test
     fun testShouldPulse() {
-        ensureStateForPulse()
-
-        assertTrue(provider.makeUnloggedHeadsUpDecision(createPulseEntry()).shouldInterrupt)
+        ensurePulseState()
+        assertShouldHeadsUp(buildPulseEntry())
     }
 
     @Test
-    fun testShouldFsi_awake() {
-        ensureStateForAwakeFsi()
-
-        assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt)
+    fun testShouldPulse_defaultLegacySuppressor() {
+        ensurePulseState()
+        provider.addLegacySuppressor(neverSuppresses)
+        assertShouldHeadsUp(buildPulseEntry())
     }
 
     @Test
-    fun testShouldFsi_dreaming() {
-        ensureStateForDreamingFsi()
-
-        assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt)
+    fun testShouldNotPulse_legacySuppressInterruptions() {
+        ensurePulseState()
+        provider.addLegacySuppressor(alwaysSuppressesInterruptions)
+        assertShouldNotHeadsUp(buildPulseEntry())
     }
 
     @Test
-    fun testShouldFsi_keyguard() {
-        ensureStateForKeyguardFsi()
+    fun testShouldPulse_legacySuppressAwakeInterruptions() {
+        ensurePulseState()
+        provider.addLegacySuppressor(alwaysSuppressesAwakeInterruptions)
+        assertShouldHeadsUp(buildPulseEntry())
+    }
 
-        assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt)
+    @Test
+    fun testShouldPulse_legacySuppressAwakeHeadsUp() {
+        ensurePulseState()
+        provider.addLegacySuppressor(alwaysSuppressesAwakeHeadsUp)
+        assertShouldHeadsUp(buildPulseEntry())
+    }
+
+    @Test
+    fun testShouldNotPulse_disabled() {
+        ensurePulseState { pulseOnNotificationsEnabled = false }
+        assertShouldNotHeadsUp(buildPulseEntry())
+    }
+
+    @Test
+    fun testShouldNotPulse_batterySaver() {
+        ensurePulseState { isAodPowerSave = true }
+        assertShouldNotHeadsUp(buildPulseEntry())
     }
 
     @Test
     fun testShouldBubble() {
-        assertTrue(provider.makeAndLogBubbleDecision(createBubbleEntry()).shouldInterrupt)
+        ensureBubbleState()
+        assertShouldBubble(buildBubbleEntry())
     }
 
-    private fun ensureStateForPeek() {
-        whenever(powerManager.isScreenOn).thenReturn(true)
-        statusBarStateController.dozing = false
-        statusBarStateController.dreaming = false
+    @Test
+    fun testShouldBubble_defaultLegacySuppressor() {
+        ensureBubbleState()
+        provider.addLegacySuppressor(neverSuppresses)
+        assertShouldBubble(buildBubbleEntry())
     }
 
-    private fun ensureStateForPulse() {
-        ambientDisplayConfiguration.fakePulseOnNotificationEnabled = true
-        batteryController.setIsAodPowerSave(false)
-        statusBarStateController.dozing = true
+    @Test
+    fun testShouldNotBubble_legacySuppressInterruptions() {
+        ensureBubbleState()
+        provider.addLegacySuppressor(alwaysSuppressesInterruptions)
+        assertShouldNotBubble(buildBubbleEntry())
     }
 
-    private fun ensureStateForAwakeFsi() {
-        whenever(powerManager.isInteractive).thenReturn(false)
-        statusBarStateController.dreaming = false
-        statusBarStateController.state = SHADE
+    @Test
+    fun testShouldNotBubble_legacySuppressAwakeInterruptions() {
+        ensureBubbleState()
+        provider.addLegacySuppressor(alwaysSuppressesAwakeInterruptions)
+        assertShouldNotBubble(buildBubbleEntry())
     }
 
-    private fun ensureStateForDreamingFsi() {
-        whenever(powerManager.isInteractive).thenReturn(true)
-        statusBarStateController.dreaming = true
-        statusBarStateController.state = SHADE
+    @Test
+    fun testShouldBubble_legacySuppressAwakeHeadsUp() {
+        ensureBubbleState()
+        provider.addLegacySuppressor(alwaysSuppressesAwakeHeadsUp)
+        assertShouldBubble(buildBubbleEntry())
     }
 
-    private fun ensureStateForKeyguardFsi() {
-        whenever(powerManager.isInteractive).thenReturn(true)
-        statusBarStateController.dreaming = false
-        statusBarStateController.state = KEYGUARD
+    @Test
+    fun testShouldFsi_notInteractive() {
+        ensureNotInteractiveFsiState()
+        assertShouldFsi(buildFsiEntry())
     }
 
-    private fun createNotif(
-        hasFsi: Boolean = false,
-        bubbleMetadata: BubbleMetadata? = null
-    ): Notification {
-        return Notification.Builder(context, TEST_CHANNEL_ID)
-            .apply {
-                setContentTitle(TEST_CONTENT_TITLE)
-                setContentText(TEST_CONTENT_TEXT)
+    @Test
+    fun testShouldFsi_dreaming() {
+        ensureDreamingFsiState()
+        assertShouldFsi(buildFsiEntry())
+    }
 
-                if (hasFsi) {
-                    setFullScreenIntent(mock(), /* highPriority = */ true)
-                }
+    @Test
+    fun testShouldFsi_keyguard() {
+        ensureKeyguardFsiState()
+        assertShouldFsi(buildFsiEntry())
+    }
 
-                if (bubbleMetadata != null) {
-                    setBubbleMetadata(bubbleMetadata)
-                }
+    private data class State(
+        var hunSettingEnabled: Boolean? = null,
+        var hunSnoozed: Boolean? = null,
+        var isAodPowerSave: Boolean? = null,
+        var isDozing: Boolean? = null,
+        var isDreaming: Boolean? = null,
+        var isInteractive: Boolean? = null,
+        var isScreenOn: Boolean? = null,
+        var keyguardShouldHideNotification: Boolean? = null,
+        var pulseOnNotificationsEnabled: Boolean? = null,
+        var statusBarState: Int? = null,
+    )
+
+    private fun setState(state: State): Unit =
+        state.run {
+            hunSettingEnabled?.let {
+                val newSetting = if (it) HEADS_UP_ON else HEADS_UP_OFF
+                globalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, newSetting)
             }
-            .setContentTitle(TEST_CONTENT_TITLE)
-            .setContentText(TEST_CONTENT_TEXT)
-            .build()
-    }
 
-    private fun createBubbleMetadata(): BubbleMetadata {
-        val pendingIntent =
-            PendingIntent.getActivity(
-                context,
-                /* requestCode = */ 0,
-                Intent().setPackage(context.packageName),
-                FLAG_MUTABLE
-            )
+            hunSnoozed?.let { whenever(headsUpManager.isSnoozed(TEST_PACKAGE)).thenReturn(it) }
 
-        val icon = Icon.createWithResource(context.resources, R.drawable.android)
+            isAodPowerSave?.let { batteryController.setIsAodPowerSave(it) }
 
-        return BubbleMetadata.Builder(pendingIntent, icon).build()
-    }
+            isDozing?.let { statusBarStateController.dozing = it }
 
-    private fun createEntry(
-        notif: Notification,
-        importance: Int = IMPORTANCE_DEFAULT,
-        canBubble: Boolean? = null
-    ): NotificationEntry {
-        return NotificationEntryBuilder()
-            .apply {
-                setPkg(TEST_PACKAGE)
-                setOpPkg(TEST_PACKAGE)
-                setTag(TEST_TAG)
-                setChannel(NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance))
-                setNotification(notif)
-                setImportance(importance)
+            isDreaming?.let { statusBarStateController.dreaming = it }
 
-                if (canBubble != null) {
-                    setCanBubble(canBubble)
-                }
+            isInteractive?.let { whenever(powerManager.isInteractive).thenReturn(it) }
+
+            isScreenOn?.let { whenever(powerManager.isScreenOn).thenReturn(it) }
+
+            keyguardShouldHideNotification?.let {
+                whenever(keyguardNotificationVisibilityProvider.shouldHideNotification(any()))
+                    .thenReturn(it)
             }
-            .build()
-    }
 
-    private fun createPeekEntry() = createEntry(notif = createNotif(), importance = IMPORTANCE_HIGH)
+            pulseOnNotificationsEnabled?.let {
+                ambientDisplayConfiguration.fakePulseOnNotificationEnabled = it
+            }
 
-    private fun createPulseEntry() =
-        createEntry(notif = createNotif(), importance = IMPORTANCE_HIGH).also {
-            modifyRanking(it).setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()
+            statusBarState?.let { statusBarStateController.state = it }
         }
 
-    private fun createFsiEntry() =
-        createEntry(notif = createNotif(hasFsi = true), importance = IMPORTANCE_HIGH)
+    private fun ensureState(block: State.() -> Unit) =
+        State()
+            .apply {
+                keyguardShouldHideNotification = false
+                apply(block)
+            }
+            .run(this::setState)
 
-    private fun createBubbleEntry() =
-        createEntry(
-            notif = createNotif(bubbleMetadata = createBubbleMetadata()),
-            importance = IMPORTANCE_HIGH,
-            canBubble = true
-        )
+    private fun ensurePeekState(block: State.() -> Unit = {}) = ensureState {
+        hunSettingEnabled = true
+        hunSnoozed = false
+        isDozing = false
+        isDreaming = false
+        isScreenOn = true
+        run(block)
+    }
+
+    private fun ensurePulseState(block: State.() -> Unit = {}) = ensureState {
+        isAodPowerSave = false
+        isDozing = true
+        pulseOnNotificationsEnabled = true
+        run(block)
+    }
+
+    private fun ensureBubbleState(block: State.() -> Unit = {}) = ensureState(block)
+
+    private fun ensureNotInteractiveFsiState(block: State.() -> Unit = {}) = ensureState {
+        isDreaming = false
+        isInteractive = false
+        statusBarState = SHADE
+        run(block)
+    }
+
+    private fun ensureDreamingFsiState(block: State.() -> Unit = {}) = ensureState {
+        isDreaming = true
+        isInteractive = true
+        statusBarState = SHADE
+        run(block)
+    }
+
+    private fun ensureKeyguardFsiState(block: State.() -> Unit = {}) = ensureState {
+        isDreaming = false
+        isInteractive = true
+        statusBarState = KEYGUARD
+        run(block)
+    }
+
+    private fun assertShouldHeadsUp(entry: NotificationEntry) =
+        provider.makeUnloggedHeadsUpDecision(entry).let {
+            assertTrue("unexpected suppressed HUN: ${it.logReason}", it.shouldInterrupt)
+        }
+
+    private fun assertShouldNotHeadsUp(entry: NotificationEntry) =
+        provider.makeUnloggedHeadsUpDecision(entry).let {
+            assertFalse("unexpected unsuppressed HUN: ${it.logReason}", it.shouldInterrupt)
+        }
+
+    private fun assertShouldBubble(entry: NotificationEntry) =
+        provider.makeAndLogBubbleDecision(entry).let {
+            assertTrue("unexpected suppressed bubble: ${it.logReason}", it.shouldInterrupt)
+        }
+
+    private fun assertShouldNotBubble(entry: NotificationEntry) =
+        provider.makeAndLogBubbleDecision(entry).let {
+            assertFalse("unexpected unsuppressed bubble: ${it.logReason}", it.shouldInterrupt)
+        }
+
+    private fun assertShouldFsi(entry: NotificationEntry) =
+        provider.makeUnloggedFullScreenIntentDecision(entry).let {
+            assertTrue("unexpected suppressed FSI: ${it.logReason}", it.shouldInterrupt)
+        }
+
+    private fun assertShouldNotFsi(entry: NotificationEntry) =
+        provider.makeUnloggedFullScreenIntentDecision(entry).let {
+            assertFalse("unexpected unsuppressed FSI: ${it.logReason}", it.shouldInterrupt)
+        }
+
+    private class EntryBuilder(val context: Context) {
+        var importance = IMPORTANCE_DEFAULT
+        var suppressedVisualEffects: Int? = null
+        var whenMs: Long? = null
+        var visibilityOverride: Int? = null
+        var hasFsi = false
+        var canBubble: Boolean? = null
+        var hasBubbleMetadata = false
+        var bubbleSuppressNotification: Boolean? = null
+
+        private fun buildBubbleMetadata() =
+            BubbleMetadata.Builder(
+                    PendingIntent.getActivity(
+                        context,
+                        /* requestCode = */ 0,
+                        Intent().setPackage(context.packageName),
+                        FLAG_MUTABLE
+                    ),
+                    Icon.createWithResource(context.resources, R.drawable.android)
+                )
+                .apply { bubbleSuppressNotification?.let { setSuppressNotification(it) } }
+                .build()
+
+        fun build() =
+            Notification.Builder(context, TEST_CHANNEL_ID)
+                .apply {
+                    setContentTitle(TEST_CONTENT_TITLE)
+                    setContentText(TEST_CONTENT_TEXT)
+
+                    if (hasFsi) {
+                        setFullScreenIntent(mock(), /* highPriority = */ true)
+                    }
+
+                    whenMs?.let { setWhen(it) }
+
+                    if (hasBubbleMetadata) {
+                        setBubbleMetadata(buildBubbleMetadata())
+                    }
+                }
+                .build()
+                .let { NotificationEntryBuilder().setNotification(it) }
+                .apply {
+                    setPkg(TEST_PACKAGE)
+                    setOpPkg(TEST_PACKAGE)
+                    setTag(TEST_TAG)
+
+                    setImportance(importance)
+                    setChannel(NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance))
+
+                    canBubble?.let { setCanBubble(it) }
+                }
+                .build()!!
+                .also {
+                    modifyRanking(it)
+                        .apply {
+                            suppressedVisualEffects?.let { setSuppressedVisualEffects(it) }
+                            visibilityOverride?.let { setVisibilityOverride(it) }
+                        }
+                        .build()
+                }
+    }
+
+    private fun buildEntry(block: EntryBuilder.() -> Unit) =
+        EntryBuilder(context).also(block).build()
+
+    private fun buildPeekEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
+        importance = IMPORTANCE_HIGH
+        run(block)
+    }
+
+    private fun buildPulseEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
+        importance = IMPORTANCE_DEFAULT
+        visibilityOverride = VISIBILITY_NO_OVERRIDE
+        run(block)
+    }
+
+    private fun buildFsiEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
+        importance = IMPORTANCE_HIGH
+        hasFsi = true
+        run(block)
+    }
+
+    private fun buildBubbleEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
+        canBubble = true
+        hasBubbleMetadata = true
+        run(block)
+    }
+
+    private fun whenAgo(whenAgeMs: Long) = systemClock.currentTimeMillis() - whenAgeMs
 }
 
 private const val TEST_CONTENT_TITLE = "Test Content Title"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 60421c98..3a9d111 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -314,7 +314,26 @@
             sharedNotificationContainerInteractor.setTopPosition(10f)
 
             assertThat(position)
-                .isEqualTo(SharedNotificationContainerPosition(top = 10f, bottom = 0f))
+                .isEqualTo(
+                    SharedNotificationContainerPosition(top = 10f, bottom = 0f, animate = true)
+                )
+        }
+
+    @Test
+    fun positionOnQS() =
+        testScope.runTest {
+            val position by collectLastValue(underTest.position)
+
+            // Start on lockscreen with shade expanded
+            showLockscreenWithQSExpanded()
+
+            // When not in split shade
+            sharedNotificationContainerInteractor.setTopPosition(10f)
+
+            assertThat(position)
+                .isEqualTo(
+                    SharedNotificationContainerPosition(top = 10f, bottom = 0f, animate = false)
+                )
         }
 
     @Test
@@ -390,6 +409,17 @@
         )
     }
 
+    private suspend fun TestScope.showLockscreenWithQSExpanded() {
+        shadeRepository.setLockscreenShadeExpansion(0f)
+        shadeRepository.setQsExpansion(1f)
+        keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+        keyguardTransitionRepository.sendTransitionSteps(
+            from = KeyguardState.AOD,
+            to = KeyguardState.LOCKSCREEN,
+            this,
+        )
+    }
+
     @SysUISingleton
     @Component(
         modules =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 05fd6d2..4a20f83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -20,6 +20,8 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED;
+import static android.provider.Settings.Global.HEADS_UP_ON;
 
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -51,7 +53,6 @@
 import android.app.WallpaperManager;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.IntentFilter;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.display.AmbientDisplayConfiguration;
@@ -180,11 +181,16 @@
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.MessageRouterImpl;
 import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.settings.FakeGlobalSettings;
+import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.util.time.SystemClock;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.startingsurface.StartingSurface;
 
+import dagger.Lazy;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -198,8 +204,6 @@
 
 import javax.inject.Provider;
 
-import dagger.Lazy;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
@@ -319,6 +323,7 @@
 
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
     private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
     private final FakeExecutor mUiBgExecutor = new FakeExecutor(mFakeSystemClock);
     private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@@ -349,8 +354,10 @@
         mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
                 Handler.createAsync(Looper.myLooper()));
 
+        mFakeGlobalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON);
+
         mNotificationInterruptStateProvider =
-                new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
+                new TestableNotificationInterruptStateProviderImpl(
                         mPowerManager,
                         mAmbientDisplayConfiguration,
                         mStatusBarStateController,
@@ -363,7 +370,9 @@
                         mock(KeyguardNotificationVisibilityProvider.class),
                         mock(UiEventLogger.class),
                         mUserTracker,
-                        mDeviceProvisionedController);
+                        mDeviceProvisionedController,
+                        mFakeSystemClock,
+                        mFakeGlobalSettings);
 
         mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
         mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
@@ -1162,7 +1171,6 @@
             NotificationInterruptStateProviderImpl {
 
         TestableNotificationInterruptStateProviderImpl(
-                ContentResolver contentResolver,
                 PowerManager powerManager,
                 AmbientDisplayConfiguration ambientDisplayConfiguration,
                 StatusBarStateController controller,
@@ -1175,9 +1183,10 @@
                 KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
                 UiEventLogger uiEventLogger,
                 UserTracker userTracker,
-                DeviceProvisionedController deviceProvisionedController) {
+                DeviceProvisionedController deviceProvisionedController,
+                SystemClock systemClock,
+                GlobalSettings globalSettings) {
             super(
-                    contentResolver,
                     powerManager,
                     ambientDisplayConfiguration,
                     batteryController,
@@ -1190,7 +1199,9 @@
                     keyguardNotificationVisibilityProvider,
                     uiEventLogger,
                     userTracker,
-                    deviceProvisionedController
+                    deviceProvisionedController,
+                    systemClock,
+                    globalSettings
             );
             mUseHeadsUp = true;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index ec808c7..8309b85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -18,6 +18,8 @@
 
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.PendingIntent.FLAG_MUTABLE;
+import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED;
+import static android.provider.Settings.Global.HEADS_UP_ON;
 import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
 import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
@@ -157,6 +159,8 @@
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
+import com.android.systemui.util.settings.FakeGlobalSettings;
+import com.android.systemui.util.time.SystemClock;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.Bubble;
@@ -476,7 +480,6 @@
                 mKeyguardStateController,
                 mScreenOffAnimationController,
                 mAuthController,
-                mShadeExpansionStateManager,
                 () -> mShadeInteractor,
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor
@@ -507,8 +510,11 @@
         when(mUserManager.getProfiles(ActivityManager.getCurrentUser())).thenReturn(
                 Collections.singletonList(mock(UserInfo.class)));
 
+        final FakeGlobalSettings fakeGlobalSettings = new FakeGlobalSettings();
+        fakeGlobalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON);
+
         TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
-                new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
+                new TestableNotificationInterruptStateProviderImpl(
                         mock(PowerManager.class),
                         mock(AmbientDisplayConfiguration.class),
                         mock(StatusBarStateController.class),
@@ -521,7 +527,9 @@
                         mock(KeyguardNotificationVisibilityProvider.class),
                         mock(UiEventLogger.class),
                         mock(UserTracker.class),
-                        mock(DeviceProvisionedController.class)
+                        mock(DeviceProvisionedController.class),
+                        mock(SystemClock.class),
+                        fakeGlobalSettings
                 );
 
         mShellTaskOrganizer = new ShellTaskOrganizer(mock(ShellInit.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index 0df235d..975555c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.wmshell;
 
-import android.content.ContentResolver;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -32,12 +31,13 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.SystemClock;
 
 public class TestableNotificationInterruptStateProviderImpl
         extends NotificationInterruptStateProviderImpl {
 
     TestableNotificationInterruptStateProviderImpl(
-            ContentResolver contentResolver,
             PowerManager powerManager,
             AmbientDisplayConfiguration ambientDisplayConfiguration,
             StatusBarStateController statusBarStateController,
@@ -50,8 +50,10 @@
             KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
             UiEventLogger uiEventLogger,
             UserTracker userTracker,
-            DeviceProvisionedController deviceProvisionedController) {
-        super(contentResolver,
+            DeviceProvisionedController deviceProvisionedController,
+            SystemClock systemClock,
+            GlobalSettings globalSettings) {
+        super(
                 powerManager,
                 ambientDisplayConfiguration,
                 batteryController,
@@ -64,7 +66,9 @@
                 keyguardNotificationVisibilityProvider,
                 uiEventLogger,
                 userTracker,
-                deviceProvisionedController);
+                deviceProvisionedController,
+                systemClock,
+                globalSettings);
         mUseHeadsUp = true;
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 3c49c58..800593f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -56,6 +56,14 @@
     @Deprecated("Use ShadeInteractor instead")
     override val legacyExpandedOrAwaitingInputTransfer = _legacyExpandedOrAwaitingInputTransfer
 
+    private val _legacyIsQsExpanded = MutableStateFlow(false)
+    @Deprecated("Use ShadeInteractor instead") override val legacyIsQsExpanded = _legacyIsQsExpanded
+
+    @Deprecated("Use ShadeInteractor instead")
+    override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) {
+        _legacyIsQsExpanded.value = legacyIsQsExpanded
+    }
+
     @Deprecated("Use ShadeInteractor instead")
     override fun setLegacyExpandedOrAwaitingInputTransfer(
         legacyExpandedOrAwaitingInputTransfer: Boolean
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt
index 19fdb6d..a65813a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt
@@ -104,7 +104,7 @@
 
     override fun isDreaming() = dreaming
 
-    override fun setIsDreaming(drreaming: Boolean): Boolean {
+    override fun setIsDreaming(dreaming: Boolean): Boolean {
         dreaming != this.dreaming || return false
         this.dreaming = dreaming
         callbacks.forEach { it.onDreamingChanged(dreaming) }
diff --git a/packages/SystemUI/tools/lint/baseline.xml b/packages/SystemUI/tools/lint/baseline.xml
index 301c9b8..43f8300 100644
--- a/packages/SystemUI/tools/lint/baseline.xml
+++ b/packages/SystemUI/tools/lint/baseline.xml
@@ -1271,17 +1271,6 @@
     </issue>
 
     <issue
-        id="MergeRootFrame"
-        message="This `&lt;FrameLayout>` can be replaced with a `&lt;merge>` tag"
-        errorLine1="&lt;FrameLayout"
-        errorLine2="^">
-        <location
-            file="res/layout/volume_dnd_icon.xml"
-            line="16"
-            column="1"/>
-    </issue>
-
-    <issue
         id="InefficientWeight"
         message="Use a `layout_height` of `0dp` instead of `wrap_content` for better performance"
         errorLine1="                android:layout_height=&quot;wrap_content&quot;"
@@ -88853,17 +88842,6 @@
         errorLine1="    &lt;ImageView"
         errorLine2="    ^">
         <location
-            file="res/layout/volume_dnd_icon.xml"
-            line="23"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="ContentDescription"
-        message="[Accessibility] Missing `contentDescription` attribute on image"
-        errorLine1="    &lt;ImageView"
-        errorLine2="    ^">
-        <location
             file="res/layout/wireless_charging_layout.xml"
             line="26"
             column="5"/>
@@ -89783,15 +89761,4 @@
             column="22"/>
     </issue>
 
-    <issue
-        id="RtlHardcoded"
-        message="Use &quot;`end`&quot; instead of &quot;`right`&quot; to ensure correct behavior in right-to-left locales"
-        errorLine1="        android:layout_gravity=&quot;right|top&quot;"
-        errorLine2="                                ~~~~~~~~~">
-        <location
-            file="res/layout/volume_dnd_icon.xml"
-            line="26"
-            column="33"/>
-    </issue>
-
 </issues>
diff --git a/services/Android.bp b/services/Android.bp
index aca8409..f1534b4 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -41,7 +41,10 @@
     name: "system_optimized_java_defaults",
     module_type: "java_defaults",
     config_namespace: "ANDROID",
-    bool_variables: ["SYSTEM_OPTIMIZE_JAVA"],
+    bool_variables: [
+        "SYSTEM_OPTIMIZE_JAVA",
+        "FULL_SYSTEM_OPTIMIZE_JAVA",
+    ],
     properties: [
         "optimize",
         "dxflags",
@@ -56,6 +59,7 @@
                 enabled: true,
                 // TODO(b/210510433): Enable optimizations after improving
                 // retracing infra.
+                // See also FULL_SYSTEM_OPTIMIZE_JAVA.
                 optimize: false,
                 shrink: true,
                 ignore_warnings: false,
@@ -81,6 +85,12 @@
                 dxflags: ["--debug"],
             },
         },
+        // Allow form factors to opt-in full system java optimization
+        FULL_SYSTEM_OPTIMIZE_JAVA: {
+            optimize: {
+                optimize: true,
+            },
+        },
     },
 }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 32666e7..bb50a99 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3406,7 +3406,8 @@
         // displays in one display. It's not a real display and there's no input events for it.
         final ArrayList<Display> displays = getValidDisplayList();
         if (userState.isMagnificationSingleFingerTripleTapEnabledLocked()
-                || userState.isMagnificationTwoFingerTripleTapEnabledLocked()
+                || (Flags.enableMagnificationMultipleFingerMultipleTapGesture()
+                && userState.isMagnificationTwoFingerTripleTapEnabledLocked())
                 || userState.isShortcutMagnificationEnabledLocked()) {
             for (int i = 0; i < displays.size(); i++) {
                 final Display display = displays.get(i);
@@ -3435,7 +3436,9 @@
             return;
         }
         final boolean connect = (userState.isShortcutMagnificationEnabledLocked()
-                || userState.isMagnificationSingleFingerTripleTapEnabledLocked())
+                || userState.isMagnificationSingleFingerTripleTapEnabledLocked()
+                || (Flags.enableMagnificationMultipleFingerMultipleTapGesture()
+                && userState.isMagnificationTwoFingerTripleTapEnabledLocked()))
                 && (userState.getMagnificationCapabilitiesLocked()
                 != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)
                 || userHasMagnificationServicesLocked(userState);
@@ -5133,6 +5136,8 @@
         // Remove magnification button UI when the magnification capability is not all mode or
         // magnification is disabled.
         if (!(userState.isMagnificationSingleFingerTripleTapEnabledLocked()
+                || (Flags.enableMagnificationMultipleFingerMultipleTapGesture()
+                && userState.isMagnificationTwoFingerTripleTapEnabledLocked())
                 || userState.isShortcutMagnificationEnabledLocked())
                 || userState.getMagnificationCapabilitiesLocked()
                 != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 08503cb..3e46ee2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -50,6 +50,8 @@
     /**
      * Called by the power manager to tell the input method manager whether it
      * should start watching for wake events.
+     *
+     * @param interactive the interactive mode parameter
      */
     public abstract void setInteractive(boolean interactive);
 
@@ -61,16 +63,16 @@
     /**
      * Returns the list of installed input methods for the specified user.
      *
-     * @param userId The user ID to be queried.
-     * @return A list of {@link InputMethodInfo}.  VR-only IMEs are already excluded.
+     * @param userId the user ID to be queried
+     * @return a list of {@link InputMethodInfo}. VR-only IMEs are already excluded
      */
     public abstract List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId);
 
     /**
      * Returns the list of installed input methods that are enabled for the specified user.
      *
-     * @param userId The user ID to be queried.
-     * @return A list of {@link InputMethodInfo} that are enabled for {@code userId}.
+     * @param userId the user ID to be queried
+     * @return a list of {@link InputMethodInfo} that are enabled for {@code userId}
      */
     public abstract List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId);
 
@@ -78,8 +80,10 @@
      * Called by the Autofill Frameworks to request an {@link InlineSuggestionsRequest} from
      * the input method.
      *
+     * @param userId      the user ID to be queried
      * @param requestInfo information needed to create an {@link InlineSuggestionsRequest}.
-     * @param cb {@link IInlineSuggestionsRequestCallback} used to pass back the request object.
+     * @param cb          {@link IInlineSuggestionsRequestCallback} used to pass back the request
+     *                    object
      */
     public abstract void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
             InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb);
@@ -88,8 +92,8 @@
      * Force switch to the enabled input method by {@code imeId} for current user. If the input
      * method with {@code imeId} is not enabled or not installed, do nothing.
      *
-     * @param imeId  The input method ID to be switched to.
-     * @param userId The user ID to be queried.
+     * @param imeId  the input method ID to be switched to
+     * @param userId the user ID to be queried
      * @return {@code true} if the current input method was successfully switched to the input
      * method by {@code imeId}; {@code false} the input method with {@code imeId} is not available
      * to be switched.
@@ -100,28 +104,30 @@
      * Force enable or disable the input method associated with {@code imeId} for given user. If
      * the input method associated with {@code imeId} is not installed, do nothing.
      *
-     * @param imeId  The input method ID to be enabled or disabled.
+     * @param imeId   the input method ID to be enabled or disabled
      * @param enabled {@code true} if the input method associated with {@code imeId} should be
-     *                enabled.
-     * @param userId The user ID to be queried.
+     *                enabled
+     * @param userId  the user ID to be queried
      * @return {@code true} if the input method associated with {@code imeId} was successfully
-     *         enabled or disabled, {@code false} if the input method specified is not installed
-     *         or was unable to be enabled/disabled for some other reason.
+     * enabled or disabled, {@code false} if the input method specified is not installed
+     * or was unable to be enabled/disabled for some other reason.
      */
     public abstract boolean setInputMethodEnabled(String imeId, boolean enabled,
             @UserIdInt int userId);
 
     /**
      * Registers a new {@link InputMethodListListener}.
+     *
+     * @param listener the listener to add
      */
     public abstract void registerInputMethodListListener(InputMethodListListener listener);
 
     /**
      * Transfers input focus from a given input token to that of the IME window.
      *
-     * @param sourceInputToken The source token.
-     * @param displayId The display hosting the IME window.
-     * @return {@code true} if the transfer is successful.
+     * @param sourceInputToken the source token.
+     * @param displayId        the display hosting the IME window
+     * @return {@code true} if the transfer is successful
      */
     public abstract boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
             int displayId);
@@ -132,7 +138,7 @@
      * or SystemUI).
      *
      * @param windowToken the window token that is now in control, or {@code null} if no client
-     *                   window is in control of the IME.
+     *                    window is in control of the IME
      */
     public abstract void reportImeControl(@Nullable IBinder windowToken);
 
@@ -163,8 +169,8 @@
      * Callback when the IInputMethodSession from the accessibility service with the specified
      * accessibilityConnectionId is created.
      *
-     * @param accessibilityConnectionId The connection id of the accessibility service.
-     * @param session The session passed back from the accessibility service.
+     * @param accessibilityConnectionId the connection id of the accessibility service
+     * @param session                   the session passed back from the accessibility service
      */
     public abstract void onSessionForAccessibilityCreated(int accessibilityConnectionId,
             IAccessibilityInputMethodSession session);
@@ -173,7 +179,7 @@
      * Unbind the accessibility service with the specified accessibilityConnectionId from current
      * client.
      *
-     * @param accessibilityConnectionId The connection id of the accessibility service.
+     * @param accessibilityConnectionId the connection id of the accessibility service
      */
     public abstract void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId);
 
@@ -181,7 +187,7 @@
      * Switch the keyboard layout in response to a keyboard shortcut.
      *
      * @param direction {@code 1} to switch to the next subtype, {@code -1} to switch to the
-     *                           previous subtype.
+     *                  previous subtype
      */
     public abstract void switchKeyboardLayout(int direction);
 
@@ -192,7 +198,7 @@
     public abstract boolean isAnyInputConnectionActive();
 
     /**
-     * Fake implementation of {@link InputMethodManagerInternal}.  All the methods do nothing.
+     * Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing.
      */
     private static final InputMethodManagerInternal NOP =
             new InputMethodManagerInternal() {
@@ -282,7 +288,7 @@
             };
 
     /**
-     * @return Global instance if exists.  Otherwise, a fallback no-op instance.
+     * @return Global instance if exists. Otherwise, a fallback no-op instance.
      */
     @NonNull
     public static InputMethodManagerInternal get() {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 2951ef6..40d3ef0 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1867,8 +1867,7 @@
 
         final File targetDir = resolveTargetDir(request.getInstallFlags(), request.getCodeFile());
         final File beforeCodeFile = request.getCodeFile();
-        final File afterCodeFile = PackageManagerServiceUtils.getNextCodePath(targetDir,
-                parsedPackage.getPackageName());
+        final File afterCodeFile = PackageManagerServiceUtils.getNextCodePath(targetDir);
 
         if (DEBUG_INSTALL) Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
         final boolean onIncremental = mPm.mIncrementalManager != null
@@ -3099,8 +3098,7 @@
             return null;
         }
         final File dstCodePath =
-                PackageManagerServiceUtils.getNextCodePath(Environment.getDataAppDirectory(null),
-                        packageName);
+                PackageManagerServiceUtils.getNextCodePath(Environment.getDataAppDirectory(null));
         int ret = PackageManagerServiceUtils.decompressFiles(codePath, dstCodePath, packageName);
         if (ret == PackageManager.INSTALL_SUCCEEDED) {
             ret = PackageManagerServiceUtils.extractNativeBinaries(dstCodePath, packageName);
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index bcb7bde..f8e909e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -30,7 +30,6 @@
 import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
 import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
 import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED;
-import static com.android.server.pm.PackageManagerService.RANDOM_CODEPATH_PREFIX;
 import static com.android.server.pm.PackageManagerService.RANDOM_DIR_PREFIX;
 import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
 import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
@@ -1299,16 +1298,15 @@
     }
 
     /**
-     * Given {@code targetDir}, returns {@code targetDir/~~[randomStrA]/[packageName]-[randomStrB].}
+     * Given {@code targetDir}, returns {@code targetDir/~~[randomStrA]/[randomStrB].}
      * Makes sure that {@code targetDir/~~[randomStrA]} directory doesn't exist.
      * Notice that this method doesn't actually create any directory.
      *
      * @param targetDir Directory that is two-levels up from the result directory.
-     * @param packageName Name of the package whose code files are to be installed under the result
-     *                    directory.
-     * @return File object for the directory that should hold the code files of {@code packageName}.
+     *
+     * @return File object for the directory that should hold the code files.
      */
-    public static File getNextCodePath(File targetDir, String packageName) {
+    public static File getNextCodePath(File targetDir) {
         SecureRandom random = new SecureRandom();
         byte[] bytes = new byte[16];
         File firstLevelDir;
@@ -1320,22 +1318,8 @@
         } while (firstLevelDir.exists());
 
         random.nextBytes(bytes);
-        String dirName = packageName + RANDOM_CODEPATH_PREFIX + Base64.encodeToString(bytes,
-                Base64.URL_SAFE | Base64.NO_WRAP);
-        final File result = new File(firstLevelDir, dirName);
-        if (DEBUG && !Objects.equals(tryParsePackageName(result.getName()), packageName)) {
-            throw new RuntimeException(
-                    "codepath is off: " + result.getName() + " (" + packageName + ")");
-        }
-        return result;
-    }
-
-    static String tryParsePackageName(@NonNull String codePath) throws IllegalArgumentException {
-        int packageNameEnds = codePath.indexOf(RANDOM_CODEPATH_PREFIX);
-        if (packageNameEnds == -1) {
-            throw new IllegalArgumentException("Not a valid package folder name");
-        }
-        return codePath.substring(0, packageNameEnds);
+        String dirName = Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);
+        return new File(firstLevelDir, dirName);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 534f176..c97fbda 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3836,7 +3836,8 @@
                     if (type == XmlPullParser.START_TAG) {
                         final String name = parser.getName();
                         if (name.equals(TAG_USER)) {
-                            UserData userData = readUserLP(parser.getAttributeInt(null, ATTR_ID));
+                            UserData userData = readUserLP(parser.getAttributeInt(null, ATTR_ID),
+                                    mUserVersion);
 
                             if (userData != null) {
                                 synchronized (mUsersLock) {
@@ -4555,7 +4556,7 @@
     }
 
     @GuardedBy({"mPackagesLock"})
-    private UserData readUserLP(int id) {
+    private UserData readUserLP(int id, int userVersion) {
         try (ResilientAtomicFile file = getUserFile(id)) {
             FileInputStream fis = null;
             try {
@@ -4564,19 +4565,19 @@
                     Slog.e(LOG_TAG, "User info not found, returning null, user id: " + id);
                     return null;
                 }
-                return readUserLP(id, fis);
+                return readUserLP(id, fis, userVersion);
             } catch (Exception e) {
                 // Remove corrupted file and retry.
                 Slog.e(LOG_TAG, "Error reading user info, user id: " + id);
                 file.failRead(fis, e);
-                return readUserLP(id);
+                return readUserLP(id, userVersion);
             }
         }
     }
 
     @GuardedBy({"mPackagesLock"})
     @VisibleForTesting
-    UserData readUserLP(int id, InputStream is) throws IOException,
+    UserData readUserLP(int id, InputStream is, int userVersion) throws IOException,
             XmlPullParserException {
         int flags = 0;
         String userType = null;
@@ -4669,7 +4670,17 @@
                 } else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) {
                     legacyLocalRestrictions = UserRestrictionsUtils.readRestrictions(parser);
                 } else if (TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS.equals(tag)) {
-                    localRestrictions = UserRestrictionsUtils.readRestrictions(parser);
+                    if (userVersion < 10) {
+                        // Prior to version 10, the local user restrictions were stored as sub tags
+                        // grouped by the user id of the source user. The source is no longer stored
+                        // on versions 10+ as this is now stored in the DevicePolicyEngine.
+                        RestrictionsSet oldLocalRestrictions =
+                                RestrictionsSet.readRestrictions(
+                                    parser, TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS);
+                        localRestrictions = oldLocalRestrictions.mergeAll();
+                    } else {
+                        localRestrictions = UserRestrictionsUtils.readRestrictions(parser);
+                    }
                 } else if (TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS.equals(tag)) {
                     globalRestrictions = UserRestrictionsUtils.readRestrictions(parser);
                 } else if (TAG_GUEST_RESTRICTIONS.equals(tag)) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 077812b..45ca690 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
 import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY;
+import static android.app.AppOpsManager.OP_CREATE_ACCESSIBILITY_OVERLAY;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.AppOpsManager.OP_TOAST_WINDOW;
 import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
@@ -31,6 +32,7 @@
 import static android.os.Build.VERSION_CODES.O;
 import static android.os.IInputConstants.INVALID_INPUT_DEVICE_ID;
 import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
+import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.Display.STATE_OFF;
@@ -2992,12 +2994,16 @@
                     // Window manager does the checking for this.
                     outAppOp[0] = OP_TOAST_WINDOW;
                     return ADD_OKAY;
+                case TYPE_ACCESSIBILITY_OVERLAY:
+                    if (createAccessibilityOverlayAppOpEnabled()) {
+                        outAppOp[0] = OP_CREATE_ACCESSIBILITY_OVERLAY;
+                        return ADD_OKAY;
+                    }
                 case TYPE_INPUT_METHOD:
                 case TYPE_WALLPAPER:
                 case TYPE_PRESENTATION:
                 case TYPE_PRIVATE_PRESENTATION:
                 case TYPE_VOICE_INTERACTION:
-                case TYPE_ACCESSIBILITY_OVERLAY:
                 case TYPE_QS_DIALOG:
                 case TYPE_NAVIGATION_BAR_PANEL:
                     // The window manager will check these.
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 3c8e630..26f0d34 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1585,7 +1585,7 @@
      * the Activities in the Task should be finished when it finishes. Otherwise, return {@code
      * false}.
      */
-    private boolean isRelativeTaskRootActivity(ActivityRecord r, ActivityRecord taskRoot) {
+    private static boolean isRelativeTaskRootActivity(ActivityRecord r, ActivityRecord taskRoot) {
         // Not a relative root if the given Activity is not the root Activity of its TaskFragment.
         final TaskFragment taskFragment = r.getTaskFragment();
         if (r != taskFragment.getActivity(ar -> !ar.finishing || ar == r,
@@ -1598,7 +1598,7 @@
         return taskRoot.getTaskFragment().getCompanionTaskFragment() == taskFragment;
     }
 
-    private boolean isTopActivityInTaskFragment(ActivityRecord activity) {
+    private static boolean isTopActivityInTaskFragment(ActivityRecord activity) {
         return activity.getTaskFragment().topRunningActivity() == activity;
     }
 
@@ -1614,9 +1614,6 @@
     public void onBackPressed(IBinder token, IRequestFinishCallback callback) {
         final long origId = Binder.clearCallingIdentity();
         try {
-            final Intent baseActivityIntent;
-            final boolean launchedFromHome;
-            final boolean isLastRunningActivity;
             synchronized (mGlobalLock) {
                 final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
                 if (r == null) return;
@@ -1624,39 +1621,16 @@
                 final Task task = r.getTask();
                 final ActivityRecord root = task.getRootActivity(false /*ignoreRelinquishIdentity*/,
                         true /*setToBottomIfNone*/);
-                final boolean isTaskRoot = r == root;
-                if (isTaskRoot) {
-                    if (mService.mWindowOrganizerController.mTaskOrganizerController
+                if (r == root && mService.mWindowOrganizerController.mTaskOrganizerController
                         .handleInterceptBackPressedOnTaskRoot(r.getRootTask())) {
-                        // This task is handled by a task organizer that has requested the back
-                        // pressed callback.
-                        return;
-                    }
-                } else if (!isRelativeTaskRootActivity(r, root)) {
-                    // Finish the Activity if the activity is not the task root or relative root.
-                    requestCallbackFinish(callback);
+                    // This task is handled by a task organizer that has requested the back
+                    // pressed callback.
                     return;
                 }
-
-                isLastRunningActivity = isTopActivityInTaskFragment(isTaskRoot ? root : r);
-
-                final boolean isBaseActivity = root.mActivityComponent.equals(task.realActivity);
-                baseActivityIntent = isBaseActivity ? root.intent : null;
-
-                launchedFromHome = root.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME);
-            }
-
-            // If the activity was launched directly from the home screen, then we should
-            // refrain from finishing the activity and instead move it to the back to keep it in
-            // memory. The requirements for this are:
-            //   1. The activity is the last running activity in the task.
-            //   2. The current activity is the base activity for the task.
-            //   3. The activity was launched by the home process, and is one of the main entry
-            //      points for the application.
-            if (baseActivityIntent != null && isLastRunningActivity
-                    && launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent)) {
-                moveActivityTaskToBack(token, true /* nonRoot */);
-                return;
+                if (shouldMoveTaskToBack(r, root)) {
+                    moveActivityTaskToBack(token, true /* nonRoot */);
+                    return;
+                }
             }
 
             // The default option for handling the back button is to finish the Activity.
@@ -1666,6 +1640,27 @@
         }
     }
 
+    static boolean shouldMoveTaskToBack(ActivityRecord r, ActivityRecord rootActivity) {
+        if (r != rootActivity && !isRelativeTaskRootActivity(r, rootActivity)) {
+            return false;
+        }
+        final boolean isBaseActivity = rootActivity.mActivityComponent.equals(
+                r.getTask().realActivity);
+        final Intent baseActivityIntent = isBaseActivity ? rootActivity.intent : null;
+
+        // If the activity was launched directly from the home screen, then we should
+        // refrain from finishing the activity and instead move it to the back to keep it in
+        // memory. The requirements for this are:
+        //   1. The activity is the last running activity in the task.
+        //   2. The current activity is the base activity for the task.
+        //   3. The activity was launched by the home process, and is one of the main entry
+        //      points for the application.
+        return baseActivityIntent != null
+                && isTopActivityInTaskFragment(r)
+                && rootActivity.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME)
+                && ActivityRecord.isMainIntent(baseActivityIntent);
+    }
+
     @Override
     public void enableTaskLocaleOverride(IBinder token) {
         if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index d1728d6..2d37b9b 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -43,6 +43,7 @@
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -64,6 +65,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Objects;
 
 /**
@@ -141,10 +143,6 @@
         // multiple Activities in the Stack.
         Task prevTask = null;
 
-        // The previous activity we're going back to. This can be either a child of currentTask
-        // if there are more than one Activity in currentTask, or a child of prevTask, if
-        // currentActivity is the last child of currentTask.
-        ActivityRecord prevActivity;
         WindowContainer<?> removedWindowContainer = null;
         WindowState window;
 
@@ -264,14 +262,21 @@
                 return infoBuilder.build();
             }
 
+            // The previous activity we're going back to. This can be either a child of currentTask
+            // if there are more than one Activity in currentTask, or a child of prevTask, if
+            // currentActivity is the last child of currentTask.
             // We don't have an application callback, let's find the destination of the back gesture
             // The search logic should align with ActivityClientController#finishActivity
-            prevActivity = currentTask.topRunningActivity(currentActivity.token, INVALID_TASK_ID);
+            final ArrayList<ActivityRecord> prevActivities = new ArrayList<>();
+            final boolean canAnimate = getAnimatablePrevActivities(currentTask, currentActivity,
+                    prevActivities);
             final boolean isOccluded = isKeyguardOccluded(window);
-            // TODO Dialog window does not need to attach on activity, check
-            // window.mAttrs.type != TYPE_BASE_APPLICATION
-            if ((window.getParent().getChildCount() > 1
+            if (!canAnimate) {
+                backType = BackNavigationInfo.TYPE_CALLBACK;
+            } else if ((window.getParent().getChildCount() > 1
                     && window.getParent().getChildAt(0) != window)) {
+                // TODO Dialog window does not need to attach on activity, check
+                // window.mAttrs.type != TYPE_BASE_APPLICATION
                 // Are we the top window of our parent? If not, we are a window on top of the
                 // activity, we won't close the activity.
                 backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
@@ -279,9 +284,8 @@
             } else if (!currentActivity.occludesParent() || currentActivity.showWallpaper()) {
                 // skip if current activity is translucent
                 backType = BackNavigationInfo.TYPE_CALLBACK;
-                removedWindowContainer = window;
-            } else if (prevActivity != null) {
-                if (!isOccluded || prevActivity.canShowWhenLocked()) {
+            } else if (prevActivities.size() > 0) {
+                if (!isOccluded || prevActivities.get(0).canShowWhenLocked()) {
                     // We have another Activity in the same currentTask to go to
                     final WindowContainer parent = currentActivity.getParent();
                     final boolean canCustomize = parent != null
@@ -303,40 +307,45 @@
                         }
                     }
                     removedWindowContainer = currentActivity;
-                    prevTask = prevActivity.getTask();
+                    prevTask = prevActivities.get(0).getTask();
                     backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
                 } else {
+                    // keyguard locked and activities are unable to show when locked.
                     backType = BackNavigationInfo.TYPE_CALLBACK;
                 }
-            } else if (currentTask.returnsToHomeRootTask()) {
-                if (isOccluded) {
-                    backType = BackNavigationInfo.TYPE_CALLBACK;
-                } else {
-                    // Our Task should bring back to home
-                    removedWindowContainer = currentTask;
-                    prevTask = currentTask.getDisplayArea().getRootHomeTask();
-                    backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
-                    mShowWallpaper = true;
-                }
-            } else if (currentActivity.isRootOfTask()) {
+            } else {
                 // TODO(208789724): Create single source of truth for this, maybe in
                 //  RootWindowContainer
-                prevTask = currentTask.mRootWindowContainer.getTask(Task::showToCurrentUser,
-                        currentTask, false /*includeBoundary*/, true /*traverseTopToBottom*/);
-                removedWindowContainer = currentTask;
-                // If it reaches the top activity, we will check the below task from parent.
-                // If it's null or multi-window, fallback the type to TYPE_CALLBACK.
-                // or set the type to proper value when it's return to home or another task.
-                if (prevTask == null || prevTask.inMultiWindowMode()) {
+                prevTask = currentTask.mRootWindowContainer.getTask(t -> {
+                    if (t.showToCurrentUser() && !t.mChildren.isEmpty()) {
+                        final ActivityRecord ar = t.getTopNonFinishingActivity();
+                        return ar != null && ar.showToCurrentUser();
+                    }
+                    return false;
+                }, currentTask, false /*includeBoundary*/, true /*traverseTopToBottom*/);
+                final ActivityRecord tmpPre = prevTask.getTopNonFinishingActivity();
+                if (tmpPre != null) {
+                    prevActivities.add(tmpPre);
+                    findAdjacentActivityIfExist(tmpPre, prevActivities);
+                }
+                if (prevActivities.isEmpty()
+                        || (isOccluded && !prevActivities.get(0).canShowWhenLocked())) {
                     backType = BackNavigationInfo.TYPE_CALLBACK;
+                } else if (prevTask.isActivityTypeHome()) {
+                    removedWindowContainer = currentTask;
+                    backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+                    mShowWallpaper = true;
                 } else {
-                    prevActivity = prevTask.getTopNonFinishingActivity();
-                    if (prevActivity == null || (isOccluded && !prevActivity.canShowWhenLocked())) {
+                    // If it reaches the top activity, we will check the below task from parent.
+                    // If it's null or multi-window and has different parent task, fallback the type
+                    // to TYPE_CALLBACK. Or set the type to proper value when it's return to home or
+                    // another task.
+                    final Task prevParent = prevTask.getParent().asTask();
+                    final Task currParent = currentTask.getParent().asTask();
+                    if (prevTask.inMultiWindowMode() && prevParent != currParent) {
                         backType = BackNavigationInfo.TYPE_CALLBACK;
-                    } else if (prevTask.isActivityTypeHome()) {
-                        backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
-                        mShowWallpaper = true;
                     } else {
+                        removedWindowContainer = prevTask;
                         backType = BackNavigationInfo.TYPE_CROSS_TASK;
                     }
                 }
@@ -345,7 +354,8 @@
 
             ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Previous Destination is Activity:%s Task:%s "
                             + "removedContainer:%s, backType=%s",
-                    prevActivity != null ? prevActivity.mActivityComponent : null,
+                    prevActivities.size() > 0 ? TextUtils.join(";", prevActivities.stream()
+                            .map(r -> r.mActivityComponent).toArray()) : null,
                     prevTask != null ? prevTask.getName() : null,
                     removedWindowContainer,
                     BackNavigationInfo.typeToString(backType));
@@ -360,7 +370,7 @@
             if (prepareAnimation) {
                 final AnimationHandler.ScheduleAnimationBuilder builder =
                         mAnimationHandler.prepareAnimation(backType, adapter,
-                                currentTask, prevTask, currentActivity, prevActivity);
+                                currentTask, prevTask, currentActivity, prevActivities);
                 mBackAnimationInProgress = builder != null;
                 if (mBackAnimationInProgress) {
                     if (removedWindowContainer.hasCommittedReparentToAnimationLeash()
@@ -372,8 +382,8 @@
                         // Current transition is still running, we have to defer the hiding to the
                         // client process to prevent the unexpected relayout when handling the back
                         // animation.
-                        if (prevActivity != null) {
-                            prevActivity.setDeferHidingClient(true);
+                        for (int i = prevActivities.size() - 1; i >= 0; --i) {
+                            prevActivities.get(i).setDeferHidingClient(true);
                         }
                     } else {
                         scheduleAnimation(builder);
@@ -381,17 +391,91 @@
                 }
             }
             infoBuilder.setPrepareRemoteAnimation(prepareAnimation);
-        } // Release wm Lock
 
-        WindowContainer<?> finalRemovedWindowContainer = removedWindowContainer;
-        if (finalRemovedWindowContainer != null) {
-            final int finalBackType = backType;
-            RemoteCallback onBackNavigationDone = new RemoteCallback(result -> onBackNavigationDone(
-                    result, finalBackType));
-            infoBuilder.setOnBackNavigationDone(onBackNavigationDone);
+            if (removedWindowContainer != null) {
+                final int finalBackType = backType;
+                final RemoteCallback onBackNavigationDone = new RemoteCallback(result ->
+                        onBackNavigationDone(result, finalBackType));
+                infoBuilder.setOnBackNavigationDone(onBackNavigationDone);
+            } else {
+                mNavigationMonitor.stopMonitorForRemote();
+            }
+            mLastBackType = backType;
+            return infoBuilder.build();
         }
-        mLastBackType = backType;
-        return infoBuilder.build();
+    }
+
+    /**
+     * Gets previous activities from currentActivity.
+     *
+     * @return false if unable to predict what will happen
+     */
+    private static boolean getAnimatablePrevActivities(@NonNull Task currentTask,
+            @NonNull ActivityRecord currentActivity,
+            @NonNull ArrayList<ActivityRecord> outPrevActivities) {
+        if (currentActivity.mAtmService
+                .mTaskOrganizerController.shouldInterceptBackPressedOnRootTask(
+                        currentTask.getRootTask())) {
+            // The task organizer will handle back pressed, don't play animation.
+            return false;
+        }
+        final ActivityRecord root = currentTask.getRootActivity(false /*ignoreRelinquishIdentity*/,
+                true /*setToBottomIfNone*/);
+        if (root != null && ActivityClientController.shouldMoveTaskToBack(currentActivity, root)) {
+            return true;
+        }
+
+        // Searching previous
+        final ActivityRecord prevActivity = currentTask.getActivity((below) -> !below.finishing,
+                currentActivity, false /*includeBoundary*/, true /*traverseTopToBottom*/);
+        if (prevActivity == null) {
+            // No previous activity in this task, can still predict if previous task exists.
+            return true;
+        }
+        if (currentTask.getActivity((above) -> !above.finishing, currentActivity,
+                false /*includeBoundary*/, false /*traverseTopToBottom*/) != null) {
+            // another activity is above this activity, don't know what will happen
+            return false;
+        }
+
+        final TaskFragment currTF = currentActivity.getTaskFragment();
+        final TaskFragment prevTF = prevActivity.getTaskFragment();
+        if (currTF != prevTF && prevTF != null) {
+            final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment();
+            if (prevTFAdjacent != null) {
+                if (prevTFAdjacent == currTF) {
+                    // Cannot predict what will happen when app receive back key, skip animation.
+                    outPrevActivities.clear();
+                    return false;
+                } else {
+                    final ActivityRecord prevActivityAdjacent =
+                            prevTFAdjacent.getTopNonFinishingActivity();
+                    if (prevActivityAdjacent != null) {
+                        outPrevActivities.add(prevActivityAdjacent);
+                    } else {
+                        // Don't know what will happen.
+                        outPrevActivities.clear();
+                        return false;
+                    }
+                }
+            }
+        }
+        outPrevActivities.add(prevActivity);
+        return true;
+    }
+
+    private static void findAdjacentActivityIfExist(@NonNull ActivityRecord mainActivity,
+            @NonNull ArrayList<ActivityRecord> outList) {
+        final TaskFragment mainTF = mainActivity.getTaskFragment();
+        if (mainTF == null || mainTF.getAdjacentTaskFragment() == null) {
+            return;
+        }
+        final TaskFragment adjacentTF = mainTF.getAdjacentTaskFragment();
+        final ActivityRecord topActivity = adjacentTF.getTopNonFinishingActivity();
+        if (topActivity == null) {
+            return;
+        }
+        outList.add(topActivity);
     }
 
     boolean isMonitoringTransition() {
@@ -696,7 +780,7 @@
         if (!hasTarget) {
             // Skip if no target participated in current finished transition.
             Slog.w(TAG, "Finished transition didn't include the targets"
-                    + " open: " + mPendingAnimationBuilder.mOpenTarget
+                    + " open: " + Arrays.toString(mPendingAnimationBuilder.mOpenTargets)
                     + " close: " + mPendingAnimationBuilder.mCloseTarget);
             cancelPendingAnimation();
             return false;
@@ -732,7 +816,7 @@
         private final boolean mShowWindowlessSurface;
         private final WindowManagerService mWindowManagerService;
         private BackWindowAnimationAdaptor mCloseAdaptor;
-        private BackWindowAnimationAdaptor mOpenAdaptor;
+        private BackWindowAnimationAdaptorWrapper mOpenAnimAdaptor;
         private boolean mComposed;
         private boolean mWaitTransition;
         private int mSwitchType = UNKNOWN;
@@ -741,7 +825,7 @@
         // exactly match animating target. When target match, reparent the starting surface to
         // the opening target like starting window do.
         private boolean mStartingSurfaceTargetMatch;
-        private ActivityRecord mOpenActivity;
+        private ActivityRecord[] mOpenActivities;
 
         AnimationHandler(WindowManagerService wms) {
             mWindowManagerService = wms;
@@ -753,61 +837,69 @@
         private static final int TASK_SWITCH = 1;
         private static final int ACTIVITY_SWITCH = 2;
 
-        private static boolean isActivitySwitch(WindowContainer close, WindowContainer open) {
-            if (close.asActivityRecord() == null || open.asActivityRecord() == null
-                    || (close.asActivityRecord().getTask()
-                    != open.asActivityRecord().getTask())) {
+        private static boolean isActivitySwitch(@NonNull WindowContainer close,
+                @NonNull WindowContainer[] open) {
+            if (open == null || open.length == 0 || close.asActivityRecord() == null) {
                 return false;
             }
+            final Task closeTask = close.asActivityRecord().getTask();
+            for (int i = open.length - 1; i >= 0; --i) {
+                if (open[i].asActivityRecord() == null
+                        || (closeTask != open[i].asActivityRecord().getTask())) {
+                    return false;
+                }
+            }
             return true;
         }
 
-        private static boolean isTaskSwitch(WindowContainer close, WindowContainer open) {
-            if (close.asTask() == null || open.asTask() == null
-                    || (close.asTask() == open.asTask())) {
+        private static boolean isTaskSwitch(@NonNull WindowContainer close,
+                @NonNull WindowContainer[] open) {
+            if (open == null || open.length != 1 || close.asTask() == null) {
                 return false;
             }
-            return true;
+            return open[0].asTask() != null && (close.asTask() != open[0].asTask());
         }
 
-        private void initiate(WindowContainer close, WindowContainer open,
-                ActivityRecord openActivity)  {
-            WindowContainer closeTarget;
+        private void initiate(@NonNull WindowContainer close, @NonNull WindowContainer[] open,
+                @NonNull ActivityRecord[] openingActivities)  {
             if (isActivitySwitch(close, open)) {
                 mSwitchType = ACTIVITY_SWITCH;
-                closeTarget = close.asActivityRecord();
             } else if (isTaskSwitch(close, open)) {
                 mSwitchType = TASK_SWITCH;
-                // The transition target must be activity in legacy transition.
-                closeTarget = WindowManagerService.sEnableShellTransitions ? close
-                        : close.asTask().getTopNonFinishingActivity();
             } else {
                 mSwitchType = UNKNOWN;
                 return;
             }
 
-            mCloseAdaptor = createAdaptor(closeTarget, false, mSwitchType);
-            mOpenAdaptor = createAdaptor(open, true, mSwitchType);
-            mOpenActivity = openActivity;
-            if (mCloseAdaptor.mAnimationTarget == null || mOpenAdaptor.mAnimationTarget == null) {
+            mCloseAdaptor = createAdaptor(close, false, mSwitchType);
+            if (mCloseAdaptor.mAnimationTarget == null) {
                 Slog.w(TAG, "composeNewAnimations fail, skip");
                 clearBackAnimateTarget();
+                return;
             }
+
+            mOpenAnimAdaptor = new BackWindowAnimationAdaptorWrapper(true, mSwitchType, open);
+            if (!mOpenAnimAdaptor.isValid()) {
+                Slog.w(TAG, "compose animations fail, skip");
+                clearBackAnimateTarget();
+                return;
+            }
+            mOpenActivities = openingActivities;
         }
 
         private boolean composeAnimations(@NonNull WindowContainer close,
-                @NonNull WindowContainer open, ActivityRecord openActivity) {
+                @NonNull WindowContainer[] open, @NonNull ActivityRecord[] openingActivities) {
             if (mComposed || mWaitTransition) {
                 Slog.e(TAG, "Previous animation is running " + this);
                 return false;
             }
             clearBackAnimateTarget();
-            if (close == null || open == null || openActivity == null) {
+            if (close == null || open == null || open.length == 0 || open.length > 2) {
                 Slog.e(TAG, "reset animation with null target close: "
-                        + close + " open: " + open);
+                        + close + " open: " + Arrays.toString(open));
                 return false;
             }
-            initiate(close, open, openActivity);
+            initiate(close, open, openingActivities);
             if (mSwitchType == UNKNOWN) {
                 return false;
             }
@@ -816,9 +908,14 @@
             return true;
         }
 
-        RemoteAnimationTarget[] getAnimationTargets() {
-            return mComposed ? new RemoteAnimationTarget[] {
-                    mCloseAdaptor.mAnimationTarget, mOpenAdaptor.mAnimationTarget} : null;
+        @Nullable RemoteAnimationTarget[] getAnimationTargets() {
+            if (!mComposed) {
+                return null;
+            }
+            final RemoteAnimationTarget[] targets = new RemoteAnimationTarget[2];
+            targets[0] = mCloseAdaptor.mAnimationTarget;
+            targets[1] = mOpenAnimAdaptor.getOrCreateAnimationTarget();
+            return targets;
         }
 
         boolean isSupportWindowlessSurface() {
@@ -826,7 +923,7 @@
                     .isSupportWindowlessStartingSurface();
         }
 
-        boolean containTarget(ArrayList<WindowContainer> wcs, boolean open) {
+        boolean containTarget(@NonNull ArrayList<WindowContainer> wcs, boolean open) {
             for (int i = wcs.size() - 1; i >= 0; --i) {
                 if (isTarget(wcs.get(i), open)) {
                     return true;
@@ -835,22 +932,35 @@
             return wcs.isEmpty();
         }
 
-        boolean isTarget(WindowContainer wc, boolean open) {
+        boolean isTarget(@NonNull WindowContainer wc, boolean open) {
             if (!mComposed) {
                 return false;
             }
+            if (open) {
+                for (int i = mOpenAnimAdaptor.mAdaptors.length - 1; i >= 0; --i) {
+                    if (isAnimateTarget(wc, mOpenAnimAdaptor.mAdaptors[i].mTarget, mSwitchType)) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+            return isAnimateTarget(wc, mCloseAdaptor.mTarget, mSwitchType);
+        }
 
-            // WC must be ActivityRecord in legacy transition, but it also can be Task or
-            // TaskFragment when using Shell transition.
-            // Open target: Can be Task or ActivityRecord or TaskFragment
-            // Close target: Limit to the top activity for now, to reduce the chance of misjudgment.
-            final WindowContainer target = open ? mOpenAdaptor.mTarget : mCloseAdaptor.mTarget;
-            if (mSwitchType == TASK_SWITCH) {
-                return  wc == target
-                        || (wc.asTask() != null && wc.hasChild(target))
-                        || (wc.asActivityRecord() != null && target.hasChild(wc));
-            } else if (mSwitchType == ACTIVITY_SWITCH) {
-                return wc == target || (wc.asTaskFragment() != null && wc.hasChild(target));
+        private static boolean isAnimateTarget(@NonNull WindowContainer window,
+                @NonNull WindowContainer animationTarget, int switchType) {
+            if (switchType == TASK_SWITCH) {
+                // simplify home search for multiple hierarchy
+                if (window.isActivityTypeHome() && animationTarget.isActivityTypeHome()) {
+                    return true;
+                }
+                return  window == animationTarget
+                        ||  (animationTarget.asTask() != null && animationTarget.hasChild(window))
+                        || (animationTarget.asActivityRecord() != null
+                        && window.hasChild(animationTarget));
+            } else if (switchType == ACTIVITY_SWITCH) {
+                return window == animationTarget
+                        || (window.asTaskFragment() != null && window.hasChild(animationTarget));
             }
             return false;
         }
@@ -864,19 +974,25 @@
                 mCloseAdaptor.mTarget.cancelAnimation();
                 mCloseAdaptor = null;
             }
-            if (mOpenAdaptor != null) {
-                mOpenAdaptor.cleanUpWindowlessSurface(mStartingSurfaceTargetMatch);
-                mOpenAdaptor.mTarget.cancelAnimation();
-                mOpenAdaptor = null;
+            if (mOpenAnimAdaptor != null) {
+                mOpenAnimAdaptor.cleanUp(mStartingSurfaceTargetMatch);
+                mOpenAnimAdaptor = null;
             }
-            if (mOpenActivity != null && mOpenActivity.mLaunchTaskBehind) {
-                restoreLaunchBehind(mOpenActivity);
+
+            if (mOpenActivities != null) {
+                for (int i = mOpenActivities.length - 1; i >= 0; --i) {
+                    if (mOpenActivities[i].mLaunchTaskBehind) {
+                        restoreLaunchBehind(mOpenActivities[i]);
+                    }
+                }
             }
         }
 
         void markStartingSurfaceMatch() {
             mStartingSurfaceTargetMatch = true;
-            mOpenAdaptor.reparentWindowlessSurfaceToTarget();
+            for (int i = mOpenAnimAdaptor.mAdaptors.length - 1; i >= 0; --i) {
+                mOpenAnimAdaptor.mAdaptors[i].reparentWindowlessSurfaceToTarget();
+            }
         }
 
         void clearBackAnimateTarget() {
@@ -885,13 +1001,13 @@
             mWaitTransition = false;
             mStartingSurfaceTargetMatch = false;
             mSwitchType = UNKNOWN;
-            mOpenActivity = null;
+            mOpenActivities = null;
         }
 
         // The close target must in close list
         // The open target can either in close or open list
-        boolean containsBackAnimationTargets(ArrayList<WindowContainer> openApps,
-                ArrayList<WindowContainer> closeApps) {
+        boolean containsBackAnimationTargets(@NonNull ArrayList<WindowContainer> openApps,
+                @NonNull ArrayList<WindowContainer> closeApps) {
             return containTarget(closeApps, false /* open */)
                     && (containTarget(openApps, true /* open */)
                     || containTarget(openApps, false /* open */));
@@ -901,9 +1017,9 @@
         public String toString() {
             return "AnimationTargets{"
                     + " openTarget= "
-                    + (mOpenAdaptor != null ? mOpenAdaptor.mTarget : "null")
+                    + (mOpenAnimAdaptor != null ? dumpOpenAnimTargetsToString() : null)
                     + " closeTarget= "
-                    + (mCloseAdaptor != null ? mCloseAdaptor.mTarget : "null")
+                    + (mCloseAdaptor != null ? mCloseAdaptor.mTarget : null)
                     + " mSwitchType= "
                     + mSwitchType
                     + " mComposed= "
@@ -913,8 +1029,21 @@
                     + '}';
         }
 
-        private static BackWindowAnimationAdaptor createAdaptor(
-                WindowContainer target, boolean isOpen, int switchType) {
+        private String dumpOpenAnimTargetsToString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("{");
+            for (int i = 0; i < mOpenAnimAdaptor.mAdaptors.length; i++) {
+                if (i > 0) {
+                    sb.append(',');
+                }
+                sb.append(mOpenAnimAdaptor.mAdaptors[i].mTarget);
+            }
+            sb.append("}");
+            return sb.toString();
+        }
+
+        @NonNull private static BackWindowAnimationAdaptor createAdaptor(
+                @NonNull WindowContainer target, boolean isOpen, int switchType) {
             final BackWindowAnimationAdaptor adaptor =
                     new BackWindowAnimationAdaptor(target, isOpen, switchType);
             final SurfaceControl.Transaction pt = target.getPendingTransaction();
@@ -930,6 +1059,100 @@
             return adaptor;
         }
 
+        private static class BackWindowAnimationAdaptorWrapper {
+            final BackWindowAnimationAdaptor[] mAdaptors;
+            SurfaceControl.Transaction mCloseTransaction;
+
+            BackWindowAnimationAdaptorWrapper(boolean isOpen, int switchType,
+                    @NonNull WindowContainer... targets) {
+                mAdaptors = new BackWindowAnimationAdaptor[targets.length];
+                for (int i = targets.length - 1; i >= 0; --i) {
+                    mAdaptors[i] = createAdaptor(targets[i], isOpen, switchType);
+                }
+            }
+
+            boolean isValid() {
+                for (int i = mAdaptors.length - 1; i >= 0; --i) {
+                    if (mAdaptors[i].mAnimationTarget == null) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+
+            void cleanUp(boolean startingSurfaceMatch) {
+                for (int i = mAdaptors.length - 1; i >= 0; --i) {
+                    mAdaptors[i].cleanUpWindowlessSurface(startingSurfaceMatch);
+                    mAdaptors[i].mTarget.cancelAnimation();
+                }
+                if (mCloseTransaction != null) {
+                    mCloseTransaction.apply();
+                    mCloseTransaction = null;
+                }
+            }
+
+            void onAnimationFinish() {
+                final SurfaceControl.Transaction pt = mAdaptors[0].mTarget.getPendingTransaction();
+                if (mCloseTransaction != null) {
+                    pt.merge(mCloseTransaction);
+                    mCloseTransaction = null;
+                }
+                if (mAdaptors.length > 1) {
+                    for (int i = mAdaptors.length - 1; i >= 0; --i) {
+                        final WindowContainer wc = mAdaptors[i].mTarget;
+                        final WindowContainer parent = wc.getParent();
+                        if (parent != null) {
+                            pt.reparent(wc.getSurfaceControl(),
+                                    parent.getSurfaceControl());
+                        }
+                    }
+                }
+            }
+
+            @NonNull RemoteAnimationTarget getOrCreateAnimationTarget() {
+                // Special handle for opening two activities together.
+                // If we animate both activities separately, the animation area and rounded corner
+                // would also being handled separately. To make them seem like "open" together, wrap
+                // their leash with another animation leash.
+                if (mAdaptors.length > 1 && mCloseTransaction == null) {
+                    final Rect unionBounds = new Rect();
+                    for (int i = mAdaptors.length - 1; i >= 0; --i) {
+                        unionBounds.union(mAdaptors[i].mAnimationTarget.localBounds);
+                    }
+                    final WindowContainer wc = mAdaptors[0].mTarget;
+                    final Task task = wc.asActivityRecord() != null
+                            ? wc.asActivityRecord().getTask() : wc.asTask();
+                    final RemoteAnimationTarget represent = mAdaptors[0].mAnimationTarget;
+                    final SurfaceControl leashSurface = new SurfaceControl.Builder()
+                            .setName("cross-animation-leash")
+                            .setContainerLayer()
+                            .setHidden(false)
+                            .setParent(task.getSurfaceControl())
+                            .build();
+                    final SurfaceControl.Transaction pt = wc.getPendingTransaction();
+                    pt.setLayer(leashSurface, wc.getParent().getLastLayer());
+                    mCloseTransaction = new SurfaceControl.Transaction();
+                    mCloseTransaction.reparent(leashSurface, null);
+                    for (int i = mAdaptors.length - 1; i >= 0; --i) {
+                        BackWindowAnimationAdaptor adaptor = mAdaptors[i];
+                        pt.reparent(adaptor.mAnimationTarget.leash, leashSurface);
+                        pt.setPosition(adaptor.mAnimationTarget.leash,
+                                adaptor.mAnimationTarget.localBounds.left,
+                                adaptor.mAnimationTarget.localBounds.top);
+                    }
+                    return new RemoteAnimationTarget(represent.taskId, represent.mode, leashSurface,
+                            represent.isTranslucent, represent.clipRect, represent.contentInsets,
+                            represent.prefixOrderIndex,
+                            new Point(unionBounds.left, unionBounds.top),
+                            unionBounds, unionBounds, represent.windowConfiguration,
+                            true /* isNotInRecents */, null, null, represent.taskInfo,
+                            represent.allowEnterPip);
+                } else {
+                    return mAdaptors[0].mAnimationTarget;
+                }
+            }
+        }
+
         private static class BackWindowAnimationAdaptor implements AnimationAdapter {
             SurfaceControl mCapturedLeash;
             private final Rect mBounds = new Rect();
@@ -943,7 +1166,7 @@
             private int mRequestedStartingSurfaceId = INVALID_TASK_ID;
             private SurfaceControl mStartingSurface;
 
-            BackWindowAnimationAdaptor(WindowContainer target, boolean isOpen,
+            BackWindowAnimationAdaptor(@NonNull WindowContainer target, boolean isOpen,
                     int switchType) {
                 mBounds.set(target.getBounds());
                 mTarget = target;
@@ -1034,7 +1257,7 @@
                 return mAnimationTarget;
             }
 
-            void createStartingSurface() {
+            void createStartingSurface(@NonNull WindowContainer closeWindow) {
                 if (!mIsOpen) {
                     return;
                 }
@@ -1053,7 +1276,10 @@
                 final TaskSnapshot snapshot = getSnapshot(mTarget);
                 mRequestedStartingSurfaceId = openTask.mAtmService.mTaskOrganizerController
                         .addWindowlessStartingSurface(openTask, mainActivity,
-                                mAnimationTarget.leash, snapshot,
+                                // Choose configuration from closeWindow, because the configuration
+                                // of opening target may not update before resume, so the starting
+                                // surface should occlude it entirely.
+                                mAnimationTarget.leash, snapshot, closeWindow.getConfiguration(),
                                 new IWindowlessStartingSurfaceCallback.Stub() {
                             // Once the starting surface has been created in shell, it will call
                             // onSurfaceAdded to pass the created surface to core, so if a
@@ -1108,15 +1334,17 @@
 
         ScheduleAnimationBuilder prepareAnimation(int backType, BackAnimationAdapter adapter,
                 Task currentTask, Task previousTask, ActivityRecord currentActivity,
-                ActivityRecord previousActivity) {
+                ArrayList<ActivityRecord> previousActivity) {
             switch (backType) {
                 case BackNavigationInfo.TYPE_RETURN_TO_HOME:
                     return new ScheduleAnimationBuilder(backType, adapter)
                             .setIsLaunchBehind(true)
                             .setComposeTarget(currentTask, previousTask);
                 case BackNavigationInfo.TYPE_CROSS_ACTIVITY:
+                    ActivityRecord[] prevActs = new ActivityRecord[previousActivity.size()];
+                    prevActs = previousActivity.toArray(prevActs);
                     return new ScheduleAnimationBuilder(backType, adapter)
-                            .setComposeTarget(currentActivity, previousActivity)
+                            .setComposeTarget(currentActivity, prevActs)
                             .setIsLaunchBehind(false);
                 case BackNavigationInfo.TYPE_CROSS_TASK:
                     return new ScheduleAnimationBuilder(backType, adapter)
@@ -1130,7 +1358,7 @@
             final int mType;
             final BackAnimationAdapter mBackAnimationAdapter;
             WindowContainer mCloseTarget;
-            WindowContainer mOpenTarget;
+            WindowContainer[] mOpenTargets;
             boolean mIsLaunchBehind;
 
             ScheduleAnimationBuilder(int type, BackAnimationAdapter backAnimationAdapter) {
@@ -1138,9 +1366,10 @@
                 mBackAnimationAdapter = backAnimationAdapter;
             }
 
-            ScheduleAnimationBuilder setComposeTarget(WindowContainer close, WindowContainer open) {
+            ScheduleAnimationBuilder setComposeTarget(@NonNull WindowContainer close,
+                    @NonNull WindowContainer... open) {
                 mCloseTarget = close;
-                mOpenTarget = open;
+                mOpenTargets = open;
                 return this;
             }
 
@@ -1150,43 +1379,60 @@
             }
 
             boolean containTarget(@NonNull WindowContainer wc) {
-                return wc == mOpenTarget || wc == mCloseTarget
-                        || mOpenTarget.hasChild(wc) || mCloseTarget.hasChild(wc);
+                if (mOpenTargets != null) {
+                    for (int i = mOpenTargets.length - 1; i >= 0; --i) {
+                        if (wc == mOpenTargets[i] || mOpenTargets[i].hasChild(wc)) {
+                            return true;
+                        }
+                    }
+                }
+                return wc == mCloseTarget || mCloseTarget.hasChild(wc);
             }
 
             /**
              * Apply preview strategy on the opening target
+             * @param closeWindow The close window, where it's configuration should cover all
+             *                    open target(s).
              * @param openAnimationAdaptor The animator who can create starting surface.
-             * @param visibleOpenActivity  The visible activity in opening target.
+             * @param visibleOpenActivities  The visible activities in opening targets.
              */
-            private void applyPreviewStrategy(BackWindowAnimationAdaptor openAnimationAdaptor,
-                    ActivityRecord visibleOpenActivity) {
-                if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind) {
-                    openAnimationAdaptor.createStartingSurface();
-                    return;
+            private void applyPreviewStrategy(@NonNull WindowContainer closeWindow,
+                    @NonNull BackWindowAnimationAdaptor[] openAnimationAdaptor,
+                    @NonNull ActivityRecord[] visibleOpenActivities) {
+                if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind
+                        // TODO (b/274997067) Draw two snapshot in a single starting surface.
+                        // We are using TaskId as the key of
+                        // StartingSurfaceDrawer#StartingWindowRecordManager, so we cannot create
+                        // two activity snapshot with WindowlessStartingWindow.
+                        // Try to draw two snapshot within a WindowlessStartingWindow, or find
+                        // another key for StartingWindowRecordManager.
+                        && openAnimationAdaptor.length == 1) {
+                    openAnimationAdaptor[0].createStartingSurface(closeWindow);
+                } else {
+                    for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
+                        setLaunchBehind(visibleOpenActivities[i]);
+                    }
                 }
-                setLaunchBehind(visibleOpenActivity);
             }
 
-            Runnable build() {
-                if (mOpenTarget == null || mCloseTarget == null) {
+            @Nullable Runnable build() {
+                if (mOpenTargets == null || mCloseTarget == null || mOpenTargets.length == 0) {
                     return null;
                 }
-                final ActivityRecord openActivity = mOpenTarget.asTask() != null
-                                ? mOpenTarget.asTask().getTopNonFinishingActivity()
-                                : mOpenTarget.asActivityRecord() != null
-                                        ? mOpenTarget.asActivityRecord() : null;
-                if (openActivity == null) {
+                final boolean shouldLaunchBehind = mIsLaunchBehind || !isSupportWindowlessSurface();
+                final ActivityRecord[] openingActivities = getTopOpenActivities(mOpenTargets);
+
+                if (shouldLaunchBehind && openingActivities == null) {
                     Slog.e(TAG, "No opening activity");
                     return null;
                 }
 
-                if (!composeAnimations(mCloseTarget, mOpenTarget, openActivity)) {
+                if (!composeAnimations(mCloseTarget, mOpenTargets, openingActivities)) {
                     return null;
                 }
                 mCloseTarget.mTransitionController.mSnapshotController
                         .mActivitySnapshotController.clearOnBackPressedActivities();
-                applyPreviewStrategy(mOpenAdaptor, openActivity);
+                applyPreviewStrategy(mCloseTarget, mOpenAnimAdaptor.mAdaptors, openingActivities);
 
                 final IBackAnimationFinishedCallback callback = makeAnimationFinishedCallback();
                 final RemoteAnimationTarget[] targets = getAnimationTargets();
@@ -1210,6 +1456,7 @@
                                 // animation was canceled
                                 return;
                             }
+                            mOpenAnimAdaptor.onAnimationFinish();
                             if (!triggerBack) {
                                 clearBackAnimateTarget();
                             } else {
@@ -1223,6 +1470,41 @@
         }
     }
 
+    /**
+     * Finds next opening activity(ies) based on open targets, which could be:
+     * 1. If the open window is Task, then the open activity can either be an activity, or
+     * two activities inside two TaskFragments
+     * 2. If the open window is Activity, then the open window can be an activity, or two
+     * adjacent TaskFragments below it.
+     */
+    @Nullable
+    private static ActivityRecord[] getTopOpenActivities(
+            @NonNull WindowContainer[] openWindows) {
+        ActivityRecord[] openActivities = null;
+        final WindowContainer mainTarget = openWindows[0];
+        if (mainTarget.asTask() != null) {
+            final ArrayList<ActivityRecord> inTaskActivities = new ArrayList<>();
+            final Task task = mainTarget.asTask();
+            final ActivityRecord tmpPreActivity = task.getTopNonFinishingActivity();
+            if (tmpPreActivity != null) {
+                inTaskActivities.add(tmpPreActivity);
+                findAdjacentActivityIfExist(tmpPreActivity, inTaskActivities);
+            }
+
+            openActivities = new ActivityRecord[inTaskActivities.size()];
+            for (int i = inTaskActivities.size() - 1; i >= 0; --i) {
+                openActivities[i] = inTaskActivities.get(i);
+            }
+        } else if (mainTarget.asActivityRecord() != null) {
+            final int size = openWindows.length;
+            openActivities = new ActivityRecord[size];
+            for (int i = size - 1; i >= 0; --i) {
+                openActivities[i] = openWindows[i].asActivityRecord();
+            }
+        }
+        return openActivities;
+    }
+
     private static void setLaunchBehind(@NonNull ActivityRecord activity) {
         if (!activity.isVisibleRequested()) {
             activity.setVisibility(true);
@@ -1311,7 +1593,7 @@
     static TaskSnapshot getSnapshot(@NonNull WindowContainer w) {
         if (w.asTask() != null) {
             final Task task = w.asTask();
-            return  task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
+            return task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
                     task.mTaskId, task.mUserId, false /* restoreFromDisk */,
                     false /* isLowResolution */);
         }
@@ -1340,8 +1622,10 @@
         proto.write(ANIMATION_IN_PROGRESS, mBackAnimationInProgress);
         proto.write(LAST_BACK_TYPE, mLastBackType);
         proto.write(SHOW_WALLPAPER, mShowWallpaper);
-        if (mAnimationHandler.mOpenActivity != null) {
-            mAnimationHandler.mOpenActivity.writeNameToProto(proto, MAIN_OPEN_ACTIVITY);
+        if (mAnimationHandler.mOpenAnimAdaptor != null
+                && mAnimationHandler.mOpenAnimAdaptor.mAdaptors.length > 0) {
+            mAnimationHandler.mOpenActivities[0].writeNameToProto(
+                    proto, MAIN_OPEN_ACTIVITY);
         } else {
             proto.write(MAIN_OPEN_ACTIVITY, "");
         }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 4a467df..b1c1fd6 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1681,8 +1681,8 @@
             return false;
         }
 
-        if (!StorageManager.isUserKeyUnlocked(mCurrentUser)) {
-            // Can't launch home on secondary display areas if device is still locked.
+        if (!StorageManager.isCeStorageUnlocked(mCurrentUser)) {
+            // Can't launch home on secondary display areas if CE storage is still locked.
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 34ae370..e7a1cf1 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -49,6 +49,7 @@
 import android.view.WindowManager;
 import android.window.ITaskFragmentOrganizer;
 import android.window.ITaskFragmentOrganizerController;
+import android.window.RemoteTransition;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOperation;
 import android.window.TaskFragmentParentInfo;
@@ -566,7 +567,8 @@
         // Keep the calling identity to avoid unsecure change.
         synchronized (mGlobalLock) {
             if (isValidTransaction(wct)) {
-                applyTransaction(wct, transitionType, shouldApplyIndependently);
+                applyTransaction(
+                        wct, transitionType, shouldApplyIndependently, null /* remoteTransition */);
             }
             // Even if the transaction is empty, we still need to invoke #onTransactionFinished
             // unless the organizer has been unregistered.
@@ -587,14 +589,15 @@
 
     @Override
     public void applyTransaction(@NonNull WindowContainerTransaction wct,
-            @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
+            @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently,
+            @Nullable RemoteTransition remoteTransition) {
         // Keep the calling identity to avoid unsecure change.
         synchronized (mGlobalLock) {
             if (!isValidTransaction(wct)) {
                 return;
             }
             mWindowOrganizerController.applyTaskFragmentTransactionLocked(wct, transitionType,
-                    shouldApplyIndependently);
+                    shouldApplyIndependently, remoteTransition);
         }
     }
 
@@ -839,6 +842,7 @@
             Slog.e(TAG, "Caller organizer=" + organizer + " is no longer registered");
             return false;
         }
+
         return true;
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 41e49b9..12392a6 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -35,6 +35,7 @@
 import android.app.WindowConfiguration;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Binder;
 import android.os.IBinder;
@@ -733,7 +734,8 @@
      *         the task was removed from hierarchy.
      */
     int addWindowlessStartingSurface(Task task, ActivityRecord activity, SurfaceControl root,
-            TaskSnapshot taskSnapshot, IWindowlessStartingSurfaceCallback callback) {
+            TaskSnapshot taskSnapshot, Configuration configuration,
+            IWindowlessStartingSurfaceCallback callback) {
         final Task rootTask = task.getRootTask();
         if (rootTask == null) {
             return INVALID_TASK_ID;
@@ -743,6 +745,7 @@
             return INVALID_TASK_ID;
         }
         final StartingWindowInfo info = task.getStartingWindowInfo(activity);
+        info.taskInfo.configuration.setTo(configuration);
         info.taskInfo.taskDescription = activity.taskDescription;
         info.taskSnapshot = taskSnapshot;
         info.windowlessStartingSurfaceCallback = callback;
@@ -1195,8 +1198,7 @@
     }
 
     public boolean handleInterceptBackPressedOnTaskRoot(Task task) {
-        if (task == null || !task.isOrganized()
-                || !mInterceptBackPressedOnRootTasks.contains(task.mTaskId)) {
+        if (!shouldInterceptBackPressedOnRootTask(task)) {
             return false;
         }
         final TaskOrganizerPendingEventsQueue pendingEventsQueue =
@@ -1229,6 +1231,11 @@
         return true;
     }
 
+    boolean shouldInterceptBackPressedOnRootTask(Task task) {
+        return task != null && task.isOrganized()
+                && mInterceptBackPressedOnRootTasks.contains(task.mTaskId);
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.print(prefix); pw.println("TaskOrganizerController:");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a8b9417..3497353 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -103,6 +103,7 @@
 import android.window.ITransitionPlayer;
 import android.window.IWindowContainerTransactionCallback;
 import android.window.IWindowOrganizerController;
+import android.window.RemoteTransition;
 import android.window.TaskFragmentAnimationParams;
 import android.window.TaskFragmentCreationParams;
 import android.window.TaskFragmentOperation;
@@ -464,12 +465,20 @@
      *                                  transition, which will be queued until the sync engine is
      *                                  free if there is any other active sync. If {@code false},
      *                                  the {@code wct} will be directly applied to the active sync.
+     * @param remoteTransition {@link RemoteTransition} to apply for the transaction. Only available
+     *                                                 for system organizers.
      */
     void applyTaskFragmentTransactionLocked(@NonNull WindowContainerTransaction wct,
-            @WindowManager.TransitionType int type, boolean shouldApplyIndependently) {
+            @WindowManager.TransitionType int type, boolean shouldApplyIndependently,
+            @Nullable RemoteTransition remoteTransition) {
         enforceTaskFragmentOrganizerPermission("applyTaskFragmentTransaction()",
                 Objects.requireNonNull(wct.getTaskFragmentOrganizer()),
                 Objects.requireNonNull(wct));
+        if (remoteTransition != null && !mTaskFragmentOrganizerController.isSystemOrganizer(
+                wct.getTaskFragmentOrganizer().asBinder())) {
+            throw new SecurityException(
+                    "Only a system organizer is allowed to use remote transition!");
+        }
         final CallerInfo caller = new CallerInfo();
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -512,7 +521,7 @@
                     return;
                 }
                 mTransitionController.requestStartTransition(transition, null /* startTask */,
-                        null /* remoteTransition */, null /* displayChange */);
+                        remoteTransition, null /* displayChange */);
                 transition.setAllReady();
             };
             mTransitionController.startCollectOrQueue(transition, doApply);
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index a4adf58..627461a 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -862,37 +862,41 @@
             Slog.i(TAG, "isEnabledCredentialProviderService with componentName: "
                     + componentName.flattenToString());
 
-            // TODO(253157366): Check additional set of services.
             final int userId = UserHandle.getCallingUserId();
             final int callingUid = Binder.getCallingUid();
             enforceCallingPackage(callingPackage, callingUid);
-            synchronized (mLock) {
-                final List<CredentialManagerServiceImpl> services =
-                        getServiceListForUserLocked(userId);
-                for (CredentialManagerServiceImpl s : services) {
-                    final ComponentName serviceComponentName = s.getServiceComponentName();
 
-                    if (serviceComponentName.equals(componentName)) {
-                        if (!s.getServicePackageName().equals(callingPackage)) {
-                            // The component name and the package name do not match.
-                            MetricUtilities.logApiCalledSimpleV2(
-                                    ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
-                                    ApiStatus.FAILURE, callingUid);
-                            Slog.w(
-                                    TAG,
-                                    "isEnabledCredentialProviderService: Component name does "
-                                            + "not match package name.");
-                            return false;
-                        }
-                        MetricUtilities.logApiCalledSimpleV2(
-                                ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
-                                ApiStatus.SUCCESS, callingUid);
-                        return true;
-                    }
-                }
+            if (componentName == null) {
+                Slog.w(TAG, "isEnabledCredentialProviderService componentName is null");
+                // If the component name was not specified then throw an error and
+                // record a failure because the request failed due to invalid input.
+                MetricUtilities.logApiCalledSimpleV2(
+                      ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
+                      ApiStatus.FAILURE, callingUid);
+                return false;
             }
 
-            return false;
+            if (!componentName.getPackageName().equals(callingPackage)) {
+                Slog.w(TAG, "isEnabledCredentialProviderService component name"
+                        + " does not match requested component");
+                // If the requested component name package name does not match
+                // the calling package then throw an error and record a failure
+                // metric (because the request failed due to invalid input).
+                MetricUtilities.logApiCalledSimpleV2(
+                      ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
+                      ApiStatus.FAILURE, callingUid);
+                throw new IllegalArgumentException("provided component name does not match"
+                        + " does not match requesting component");
+            }
+
+            final Set<ComponentName> enabledProviders = getEnabledProvidersForUser(userId);
+            MetricUtilities.logApiCalledSimpleV2(
+                ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
+                ApiStatus.SUCCESS, callingUid);
+            if (enabledProviders == null) {
+                return false;
+            }
+            return enabledProviders.contains(componentName);
         }
 
         @Override
diff --git a/services/proguard.flags b/services/proguard.flags
index e11e613..261bb7c 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -47,6 +47,11 @@
 -keep,allowoptimization,allowaccessmodification class com.android.net.module.util.* { *; }
 -keep,allowoptimization,allowaccessmodification public class com.android.server.net.IpConfigStore { *; }
 -keep,allowoptimization,allowaccessmodification public class com.android.server.net.BaseNetworkObserver { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.display.feature.DisplayManagerFlags { *; }
+-keep,allowoptimization,allowaccessmodification class android.app.admin.flags.FeatureFlagsImpl { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.input.NativeInputManagerService$NativeImpl { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.ThreadPriorityBooster { *; }
+-keep,allowaccessmodification class android.app.admin.flags.Flags { *; }
 
 # Referenced via CarServiceHelperService in car-frameworks-service (avoid removing)
 -keep public class com.android.server.utils.Slogf { *; }
@@ -99,9 +104,6 @@
 -keep,allowoptimization,allowaccessmodification class com.android.server.input.InputManagerService {
   <methods>;
 }
--keep,allowoptimization,allowaccessmodification class com.android.server.input.NativeInputManagerService$NativeImpl {
-  <methods>;
-}
 -keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbHostManager {
   *** usbDeviceRemoved(...);
   *** usbDeviceAdded(...);
diff --git a/services/tests/servicestests/res/xml/user_100_v9.xml b/services/tests/servicestests/res/xml/user_100_v9.xml
new file mode 100644
index 0000000..03c08ed
--- /dev/null
+++ b/services/tests/servicestests/res/xml/user_100_v9.xml
@@ -0,0 +1,20 @@
+<user id="100"
+    serialNumber="0"
+    flags="3091"
+    type="android.os.usertype.full.SYSTEM"
+    created="0"
+    lastLoggedIn="0"
+    lastLoggedInFingerprint="0"
+    profileBadge="0">
+  <restrictions no_oem_unlock="true" />
+  <device_policy_local_restrictions>
+    <restrictions_user user_id="0">
+      <restrictions no_camera="true" />
+    </restrictions_user>
+    <restrictions_user user_id="100">
+      <restrictions no_camera="true" />
+      <restrictions no_install_unknown_sources="true" />
+    </restrictions_user>
+  </device_policy_local_restrictions>
+  <ignorePrepareStorageErrors>false</ignorePrepareStorageErrors>
+</user>
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index b9e45ba..82efdd3 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -535,6 +535,78 @@
 
     @SmallTest
     @Test
+    public void testOnClientChange_magnificationTripleTapEnabled_requestConnection() {
+        when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
+        final AccessibilityUserState userState = mA11yms.mUserStates.get(
+                mA11yms.getCurrentUserIdLocked());
+        userState.setMagnificationCapabilitiesLocked(
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+        userState.setMagnificationSingleFingerTripleTapEnabledLocked(true);
+
+        // Invokes client change to trigger onUserStateChanged.
+        mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+        verify(mMockWindowMagnificationMgr).requestConnection(true);
+    }
+
+    @SmallTest
+    @Test
+    public void testOnClientChange_magnificationTripleTapDisabled_requestDisconnection() {
+        when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
+        final AccessibilityUserState userState = mA11yms.mUserStates.get(
+                mA11yms.getCurrentUserIdLocked());
+        userState.setMagnificationCapabilitiesLocked(
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+        //userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
+        userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
+
+        // Invokes client change to trigger onUserStateChanged.
+        mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+        verify(mMockWindowMagnificationMgr).requestConnection(false);
+    }
+
+    @SmallTest
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+    public void testOnClientChange_magnificationTwoFingerTripleTapEnabled_requestConnection() {
+        when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
+        final AccessibilityUserState userState = mA11yms.mUserStates.get(
+                mA11yms.getCurrentUserIdLocked());
+        userState.setMagnificationCapabilitiesLocked(
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+        userState.setMagnificationTwoFingerTripleTapEnabledLocked(true);
+
+        // Invokes client change to trigger onUserStateChanged.
+        mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+        verify(mMockWindowMagnificationMgr).requestConnection(true);
+    }
+
+    @SmallTest
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+    public void testOnClientChange_magnificationTwoFingerTripleTapDisabled_requestDisconnection() {
+        when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
+        final AccessibilityUserState userState = mA11yms.mUserStates.get(
+                mA11yms.getCurrentUserIdLocked());
+        userState.setMagnificationCapabilitiesLocked(
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+        //userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
+        userState.setMagnificationTwoFingerTripleTapEnabledLocked(false);
+
+        // Invokes client change to trigger onUserStateChanged.
+        mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+        verify(mMockWindowMagnificationMgr).requestConnection(false);
+    }
+
+    @SmallTest
+    @Test
     public void testOnClientChange_boundServiceCanControlMagnification_requestConnection() {
         when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
 
@@ -547,6 +619,64 @@
         verify(mMockWindowMagnificationMgr).requestConnection(true);
     }
 
+    @SmallTest
+    @Test
+    public void testOnClientChange_magnificationTripleTapDisabled_removeMagnificationButton() {
+        final AccessibilityUserState userState = mA11yms.mUserStates.get(
+                mA11yms.getCurrentUserIdLocked());
+        userState.setMagnificationCapabilitiesLocked(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
+
+        // Invokes client change to trigger onUserStateChanged.
+        mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+        verify(mMockWindowMagnificationMgr, atLeastOnce()).removeMagnificationButton(anyInt());
+    }
+
+    @SmallTest
+    @Test
+    public void testOnClientChange_magnificationTripleTapEnabled_keepMagnificationButton() {
+        final AccessibilityUserState userState = mA11yms.mUserStates.get(
+                mA11yms.getCurrentUserIdLocked());
+        userState.setMagnificationCapabilitiesLocked(ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+        userState.setMagnificationSingleFingerTripleTapEnabledLocked(true);
+
+        // Invokes client change to trigger onUserStateChanged.
+        mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+        verify(mMockWindowMagnificationMgr, never()).removeMagnificationButton(anyInt());
+    }
+
+    @SmallTest
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+    public void onClientChange_magnificationTwoFingerTripleTapDisabled_removeMagnificationButton() {
+        final AccessibilityUserState userState = mA11yms.mUserStates.get(
+                mA11yms.getCurrentUserIdLocked());
+        userState.setMagnificationCapabilitiesLocked(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        userState.setMagnificationTwoFingerTripleTapEnabledLocked(false);
+
+        // Invokes client change to trigger onUserStateChanged.
+        mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+        verify(mMockWindowMagnificationMgr, atLeastOnce()).removeMagnificationButton(anyInt());
+    }
+
+    @SmallTest
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+    public void onClientChange_magnificationTwoFingerTripleTapEnabled_keepMagnificationButton() {
+        final AccessibilityUserState userState = mA11yms.mUserStates.get(
+                mA11yms.getCurrentUserIdLocked());
+        userState.setMagnificationCapabilitiesLocked(ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+        userState.setMagnificationTwoFingerTripleTapEnabledLocked(true);
+
+        // Invokes client change to trigger onUserStateChanged.
+        mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+        verify(mMockWindowMagnificationMgr, never()).removeMagnificationButton(anyInt());
+    }
+
     @Test
     public void testUnbindIme_whenServiceUnbinds() {
         setupAccessibilityServiceConnection(AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index 253592c..d1b2e8e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -43,6 +43,7 @@
 import android.app.PropertyInvalidatedCache;
 import android.content.pm.UserInfo;
 import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.res.Resources;
 import android.multiuser.Flags;
 import android.os.Looper;
 import android.os.Parcel;
@@ -50,21 +51,26 @@
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 import android.text.TextUtils;
+import android.util.Xml;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.frameworks.servicestests.R;
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerService.UserData;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.DataOutputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 
 /**
@@ -77,6 +83,7 @@
 @MediumTest
 public class UserManagerServiceUserInfoTest {
     private UserManagerService mUserManagerService;
+    private Resources mResources;
 
     @Before
     public void setup() {
@@ -96,6 +103,8 @@
         assertEquals("Multiple users so this test can't run.", 1, users.size());
         assertEquals("Only user present isn't the system user.",
                 UserHandle.USER_SYSTEM, users.get(0).id);
+
+        mResources = InstrumentationRegistry.getTargetContext().getResources();
     }
 
     @Test
@@ -109,7 +118,7 @@
         byte[] bytes = baos.toByteArray();
 
         UserData read = mUserManagerService.readUserLP(
-                data.info.id, new ByteArrayInputStream(bytes));
+                data.info.id, new ByteArrayInputStream(bytes), 0);
 
         assertUserInfoEquals(data.info, read.info, /* parcelCopy= */ false);
     }
@@ -146,11 +155,13 @@
         // Clear the restrictions to see if they are properly read in from the user file.
         setUserRestrictions(data.info.id, globalRestriction, localRestriction, false);
 
+        final int userVersion = 10;
         //read the secondary and SYSTEM user file to fetch local/global device policy restrictions.
-        mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(secondaryUserBytes));
+        mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(secondaryUserBytes),
+                userVersion);
         if (Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) {
             mUserManagerService.readUserLP(UserHandle.USER_SYSTEM,
-                    new ByteArrayInputStream(systemUserBytes));
+                    new ByteArrayInputStream(systemUserBytes), userVersion);
         }
 
         assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(globalRestriction));
@@ -303,6 +314,45 @@
         assertTrue(mUserManagerService.isUserOfType(106, USER_TYPE_FULL_DEMO));
     }
 
+    /** Tests readUserLP upgrading from version 9 to 10+. */
+    @Test
+    public void testUserRestrictionsUpgradeFromV9() throws Exception {
+        final String[] localRestrictions = new String[] {
+            UserManager.DISALLOW_CAMERA,
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+        };
+
+        final int userId = 100;
+        UserData data = new UserData();
+        data.info = createUser(userId, FLAG_FULL, "A type");
+
+        mUserManagerService.putUserInfo(data.info);
+
+        for (String restriction : localRestrictions) {
+            assertFalse(mUserManagerService.hasBaseUserRestriction(restriction, userId));
+            assertFalse(mUserManagerService.hasUserRestriction(restriction, userId));
+        }
+
+        // Convert the xml resource to the system storage xml format.
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        DataOutputStream os = new DataOutputStream(baos);
+        XmlPullParser in = mResources.getXml(R.xml.user_100_v9);
+        XmlSerializer out = Xml.newBinarySerializer();
+        out.setOutput(os, StandardCharsets.UTF_8.name());
+        Xml.copy(in, out);
+        byte[] userBytes = baos.toByteArray();
+        baos.reset();
+
+        final int userVersion = 9;
+        mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(userBytes),
+                userVersion);
+
+        for (String restriction : localRestrictions) {
+            assertFalse(mUserManagerService.hasBaseUserRestriction(restriction, userId));
+            assertTrue(mUserManagerService.hasUserRestriction(restriction, userId));
+        }
+    }
+
     /** Creates a UserInfo with the given flags and userType. */
     private UserInfo createUser(@UserIdInt int userId, @UserInfoFlag int flags, String userType) {
         return new UserInfo(userId, "A Name", "A path", flags, userType);
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index dd7dec0..7b1fa03 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -76,6 +76,7 @@
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 
+import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -601,30 +602,33 @@
         final Task task = createTask(mDefaultDisplay);
         final ActivityRecord bottomActivity = createActivityRecord(task);
         final ActivityRecord homeActivity = mRootHomeTask.getTopNonFinishingActivity();
-
+        final ArrayList<ActivityRecord> openActivities = new ArrayList<>();
+        openActivities.add(homeActivity);
         final BackNavigationController.AnimationHandler.ScheduleAnimationBuilder toHomeBuilder =
                 animationHandler.prepareAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME,
-                        mBackAnimationAdapter, task, mRootHomeTask, bottomActivity, homeActivity);
+                        mBackAnimationAdapter, task, mRootHomeTask, bottomActivity, openActivities);
         assertTrue(toHomeBuilder.mIsLaunchBehind);
         toHomeBuilder.build();
-        verify(mAtm.mTaskOrganizerController, never())
-                .addWindowlessStartingSurface(any(), any(), any(), any(), any());
+        verify(mAtm.mTaskOrganizerController, never()).addWindowlessStartingSurface(
+                any(), any(), any(), any(), any(), any());
         animationHandler.clearBackAnimateTarget();
+        openActivities.clear();
 
         // Back to ACTIVITY and TASK have the same logic, just with different target.
         final ActivityRecord topActivity = createActivityRecord(task);
+        openActivities.add(bottomActivity);
         final BackNavigationController.AnimationHandler.ScheduleAnimationBuilder toActivityBuilder =
                 animationHandler.prepareAnimation(
                         BackNavigationInfo.TYPE_CROSS_ACTIVITY, mBackAnimationAdapter, task, task,
-                        topActivity, bottomActivity);
+                        topActivity, openActivities);
         assertFalse(toActivityBuilder.mIsLaunchBehind);
         toActivityBuilder.build();
         if (preferWindowlessSurface) {
-            verify(mAtm.mTaskOrganizerController)
-                    .addWindowlessStartingSurface(any(), any(), any(), any(), any());
+            verify(mAtm.mTaskOrganizerController).addWindowlessStartingSurface(
+                    any(), any(), any(), any(), any(), any());
         } else {
-            verify(mAtm.mTaskOrganizerController, never())
-                    .addWindowlessStartingSurface(any(), any(), any(), any(), any());
+            verify(mAtm.mTaskOrganizerController, never()).addWindowlessStartingSurface(
+                    any(), any(), any(), any(), any(),  any());
         }
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index c241033..eb78906 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -872,7 +872,7 @@
                 new TestDisplayContent.Builder(mAtm, 1000, 1500)
                         .setSystemDecorations(true).build();
 
-        // Use invalid user id to let StorageManager.isUserKeyUnlocked() return false.
+        // Use invalid user id to let StorageManager.isCeStorageUnlocked() return false.
         final int currentUser = mRootWindowContainer.mCurrentUser;
         mRootWindowContainer.mCurrentUser = -1;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index c57b051..8a90f12 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -86,7 +86,9 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.RemoteAnimationDefinition;
 import android.view.SurfaceControl;
+import android.window.IRemoteTransition;
 import android.window.ITaskFragmentOrganizer;
+import android.window.RemoteTransition;
 import android.window.TaskFragmentAnimationParams;
 import android.window.TaskFragmentCreationParams;
 import android.window.TaskFragmentInfo;
@@ -546,6 +548,35 @@
     }
 
     @Test
+    public void testApplyTransaction_disallowRemoteTransitionForNonSystemOrganizer() {
+        mTransaction.setRelativeBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
+        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+                "Test:TaskFragmentOrganizer" /* processName */);
+
+        // Throw exception if the transaction has remote transition and is not requested by system
+        // organizer
+        assertThrows(SecurityException.class, () ->
+                mController.applyTransaction(mTransaction, TASK_FRAGMENT_TRANSIT_CHANGE,
+                        true /* shouldApplyIndependently */,
+                        new RemoteTransition(mock(IRemoteTransition.class))));
+    }
+
+    @Test
+    public void testApplyTransaction_allowRemoteTransitionForSystemOrganizer() {
+        mController.unregisterOrganizer(mIOrganizer);
+        mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */);
+
+        mTransaction.setRelativeBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
+        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+                "Test:TaskFragmentOrganizer" /* processName */);
+
+        // Remote transition is allowed for system organizer
+        mController.applyTransaction(mTransaction, TASK_FRAGMENT_TRANSIT_CHANGE,
+                true /* shouldApplyIndependently */,
+                new RemoteTransition(mock(IRemoteTransition.class)));
+    }
+
+    @Test
     public void testApplyTransaction_enforceConfigurationChangeOnOrganizedTaskFragment() {
         // Throw exception if the transaction is trying to change a window that is not organized by
         // the organizer.
@@ -1801,13 +1832,13 @@
     private void assertApplyTransactionDisallowed(WindowContainerTransaction t) {
         assertThrows(SecurityException.class, () ->
                 mController.applyTransaction(t, TASK_FRAGMENT_TRANSIT_CHANGE,
-                        false /* shouldApplyIndependently */));
+                        false /* shouldApplyIndependently */, null /* remoteTransition */));
     }
 
     /** Asserts that applying the given transaction will not throw any exception. */
     private void assertApplyTransactionAllowed(WindowContainerTransaction t) {
         mController.applyTransaction(t, TASK_FRAGMENT_TRANSIT_CHANGE,
-                false /* shouldApplyIndependently */);
+                false /* shouldApplyIndependently */, null /* remoteTransition */);
     }
 
     /** Asserts that there will be a transaction for TaskFragment appeared. */
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 699580a..2c39173 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -614,7 +614,7 @@
         t.setForceTranslucent(taskFragment.mRemoteToken.toWindowContainerToken(), true);
         mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
                 t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
-                false /* shouldApplyIndependently */);
+                false /* shouldApplyIndependently */, null /* remoteTransition */);
 
         // Should be not visible and not focusable after the transaction.
         assertFalse(taskFragment.shouldBeVisible(null));
@@ -628,7 +628,7 @@
         t.setForceTranslucent(taskFragment.mRemoteToken.toWindowContainerToken(), false);
         mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
                 t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
-                false /* shouldApplyIndependently */);
+                false /* shouldApplyIndependently */, null /* remoteTransition */);
 
         // Should be visible and focusable after the transaction.
         assertTrue(taskFragment.shouldBeVisible(null));
@@ -680,7 +680,7 @@
         assertThrows(SecurityException.class, () ->
                 mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
                         t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
-                        false /* shouldApplyIndependently */)
+                        false /* shouldApplyIndependently */, null /* remoteTransition */)
         );
     }
 
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 1f32c97..55fecfc 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -19,6 +19,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -54,6 +55,7 @@
 import com.android.internal.telecom.ClientTransactionalServiceRepository;
 import com.android.internal.telecom.ClientTransactionalServiceWrapper;
 import com.android.internal.telecom.ITelecomService;
+import com.android.server.telecom.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -412,6 +414,14 @@
             "android.telecom.extra.CALL_CREATED_TIME_MILLIS";
 
     /**
+     * The extra for call log uri that was used to mark missed calls as read when dialer gets the
+     * notification on reboot.
+     */
+    @FlaggedApi(Flags.FLAG_ADD_CALL_URI_FOR_MISSED_CALLS)
+    public static final String EXTRA_CALL_LOG_URI =
+            "android.telecom.extra.CALL_LOG_URI";
+
+    /**
      * Optional extra for incoming containing a long which specifies the time the
      * call was answered by user. This value is in milliseconds.
      * @hide
@@ -2361,6 +2371,11 @@
      * <p>
      * <b>Note</b>: {@link android.app.Notification.CallStyle} notifications should be posted after
      * the call is placed in order for the notification to be non-dismissible.
+     * <p><b>Note</b>: Call Forwarding MMI codes can only be dialed by applications that are
+     * configured as the user defined default dialer or system dialer role. If a call containing a
+     * call forwarding MMI code is placed by an application that is not in one of these roles, the
+     * dialer will be launched with a UI showing the MMI code already populated so that the user can
+     * confirm the action before the call is placed.
      * @param address The address to make the call to.
      * @param extras Bundle of extras to use with the call.
      */
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 042b2a3..4250bd1 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9692,6 +9692,7 @@
      *
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
+     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
      */
     public static final String
             KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index c0d6b30..e9ea5a7 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17483,9 +17483,8 @@
      * {@link CarrierConfigManager
      * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
      * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SLICING_ADDITIONAL_ERROR_CODES)
     public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16;
 
     /**
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
index a20266a..28eab8f 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
@@ -20,7 +20,6 @@
 import com.android.tools.lint.client.api.Vendor
 import com.android.tools.lint.detector.api.CURRENT_API
 import com.google.android.lint.aidl.EnforcePermissionDetector
-import com.google.android.lint.aidl.EnforcePermissionHelperDetector
 import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector
 import com.google.auto.service.AutoService
 
@@ -30,7 +29,8 @@
     override val issues = listOf(
             EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
             EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
-            EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+            EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+            EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION,
             SimpleManualPermissionEnforcementDetector.ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT,
     )
 
@@ -45,4 +45,4 @@
             feedbackUrl = "http://b/issues/new?component=315013",
             contact = "repsonsible-apis@google.com"
     )
-}
\ No newline at end of file
+}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index 3a95df9..dcd94f1 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -30,31 +30,34 @@
 import com.android.tools.lint.detector.api.Scope
 import com.android.tools.lint.detector.api.Severity
 import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.google.android.lint.findCallExpression
 import com.intellij.psi.PsiAnnotation
 import com.intellij.psi.PsiArrayInitializerMemberValue
 import com.intellij.psi.PsiClass
 import com.intellij.psi.PsiElement
 import com.intellij.psi.PsiMethod
-import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UDeclarationsExpression
 import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
 import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.toUElement
+import org.jetbrains.uast.skipParenthesizedExprDown
 
 import java.util.EnumSet
 
 /**
- * Lint Detector that ensures that any method overriding a method annotated
- * with @EnforcePermission is also annotated with the exact same annotation.
- * The intent is to surface the effective permission checks to the service
- * implementations.
+ * Lint Detector that ensures consistency when using the @EnforcePermission
+ * annotation. Multiple verifications are implemented:
  *
- * This is done with 2 mechanisms:
  *  1. Visit any annotation usage, to ensure that any derived class will have
- *     the correct annotation on each methods. This is for the top to bottom
- *     propagation.
- *  2. Visit any annotation, to ensure that if a method is annotated, it has
+ *     the correct annotation on each methods. Even if the subclass does not
+ *     have the annotation, visitAnnotationUsage will be called which allows us
+ *     to capture the issue.
+ *  2. Visit any method, to ensure that if a method is annotated, it has
  *     its ancestor also annotated. This is to avoid having an annotation on a
  *     Java method without the corresponding annotation on the AIDL interface.
+ *  3. When annotated, ensures that the first instruction is to call the helper
+ *     method (or the parent helper).
  */
 class EnforcePermissionDetector : Detector(), SourceCodeScanner {
 
@@ -62,9 +65,8 @@
         return listOf(ANNOTATION_ENFORCE_PERMISSION)
     }
 
-    override fun getApplicableUastTypes(): List<Class<out UElement>> {
-        return listOf(UAnnotation::class.java)
-    }
+    override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+            listOf(UMethod::class.java)
 
     private fun annotationValueGetChildren(elem: PsiElement): Array<PsiElement> {
         if (elem is PsiArrayInitializerMemberValue)
@@ -129,11 +131,6 @@
         overriddenMethod: PsiMethod,
         checkEquivalence: Boolean = true
     ) {
-        // If method is not from a Stub subclass, this method shouldn't use @EP at all.
-        // This is handled by EnforcePermissionHelperDetector.
-        if (!isContainedInSubclassOfStub(context, overridingMethod.toUElement() as? UMethod)) {
-            return
-        }
         val overridingAnnotation = overridingMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
         val overriddenAnnotation = overriddenMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
         val location = context.getLocation(element)
@@ -169,40 +166,102 @@
     ) {
         if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE &&
             annotationInfo.origin == AnnotationOrigin.METHOD) {
+            /* Ignore implementations that are not a sub-class of Stub (i.e., Proxy). */
+            val uMethod = element as? UMethod ?: return
+            if (!isContainedInSubclassOfStub(context, uMethod)) {
+                return
+            }
             val overridingMethod = element.sourcePsi as PsiMethod
             val overriddenMethod = usageInfo.referenced as PsiMethod
             compareMethods(context, element, overridingMethod, overriddenMethod)
         }
     }
 
-    override fun createUastHandler(context: JavaContext): UElementHandler {
-        return object : UElementHandler() {
-            override fun visitAnnotation(node: UAnnotation) {
-                if (node.qualifiedName != ANNOTATION_ENFORCE_PERMISSION) {
-                    return
-                }
-                val method = node.uastParent as? UMethod ?: return
-                val overridingMethod = method as PsiMethod
-                val parents = overridingMethod.findSuperMethods()
-                for (overriddenMethod in parents) {
-                    // The equivalence check can be skipped, if both methods are
-                    // annotated, it will be verified by visitAnnotationUsage.
-                    compareMethods(context, method, overridingMethod,
-                        overriddenMethod, checkEquivalence = false)
-                }
+    override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
+
+    private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
+        override fun visitMethod(node: UMethod) {
+            if (context.evaluator.isAbstract(node)) return
+            if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
+
+            if (!isContainedInSubclassOfStub(context, node)) {
+                context.report(
+                    ISSUE_MISUSING_ENFORCE_PERMISSION,
+                    node,
+                    context.getLocation(node),
+                    "The class of ${node.name} does not inherit from an AIDL generated Stub class"
+                )
+                return
+            }
+
+            /* Check that we are connected to the super class */
+            val overridingMethod = node as PsiMethod
+            val parents = overridingMethod.findSuperMethods()
+            for (overriddenMethod in parents) {
+                // The equivalence check can be skipped, if both methods are
+                // annotated, it will be verified by visitAnnotationUsage.
+                compareMethods(context, node, overridingMethod,
+                    overriddenMethod, checkEquivalence = false)
+            }
+
+            /* Check that the helper is called as a first instruction */
+            val targetExpression = getHelperMethodCallSourceString(node)
+            val message =
+                "Method must start with $targetExpression or super.${node.name}(), if applicable"
+
+            val firstExpression = (node.uastBody as? UBlockExpression)
+                    ?.expressions?.firstOrNull()
+
+            if (firstExpression == null) {
+                context.report(
+                    ISSUE_ENFORCE_PERMISSION_HELPER,
+                    context.getLocation(node),
+                    message,
+                )
+                return
+            }
+
+            val firstExpressionSource = firstExpression.skipParenthesizedExprDown()
+              .asSourceString()
+              .filterNot(Char::isWhitespace)
+
+            if (firstExpressionSource != targetExpression &&
+                  firstExpressionSource != "super.$targetExpression") {
+                // calling super.<methodName>() is also legal
+                val directSuper = context.evaluator.getSuperMethod(node)
+                val firstCall = findCallExpression(firstExpression)?.resolve()
+                if (directSuper != null && firstCall == directSuper) return
+
+                val locationTarget = getLocationTarget(firstExpression)
+                val expressionLocation = context.getLocation(locationTarget)
+
+                context.report(
+                    ISSUE_ENFORCE_PERMISSION_HELPER,
+                    context.getLocation(node),
+                    message,
+                    getHelperMethodFix(node, expressionLocation),
+                )
             }
         }
     }
 
     companion object {
+
+        private const val HELPER_SUFFIX = "_enforcePermission"
+
         val EXPLANATION = """
-            The @EnforcePermission annotation is used to indicate that the underlying binder code
-            has already verified the caller's permissions before calling the appropriate method. The
-            verification code is usually generated by the AIDL compiler, which also takes care of
-            annotating the generated Java code.
+            The @EnforcePermission annotation is used to delegate the verification of the caller's
+            permissions to a generated AIDL method.
 
             In order to surface that information to platform developers, the same annotation must be
             used on the implementation class or methods.
+
+            The @EnforcePermission annotation can only be used on methods whose class extends from
+            the Stub class generated by the AIDL compiler. When @EnforcePermission is applied, the
+            AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX.
+
+            yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can
+            either call it directly, or call it indirectly via super.yourMethodName().
             """
 
         val ISSUE_MISSING_ENFORCE_PERMISSION: Issue = Issue.create(
@@ -230,5 +289,44 @@
                     EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
             )
         )
+
+        val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create(
+            id = "MissingEnforcePermissionHelper",
+            briefDescription = """Missing permission-enforcing method call in AIDL method
+                |annotated with @EnforcePermission""".trimMargin(),
+            explanation = EXPLANATION,
+            category = Category.SECURITY,
+            priority = 6,
+            severity = Severity.ERROR,
+            implementation = Implementation(
+                    EnforcePermissionDetector::class.java,
+                    EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+            )
+        )
+
+        val ISSUE_MISUSING_ENFORCE_PERMISSION: Issue = Issue.create(
+            id = "MisusingEnforcePermissionAnnotation",
+            briefDescription = "@EnforcePermission cannot be used here",
+            explanation = EXPLANATION,
+            category = Category.SECURITY,
+            priority = 6,
+            severity = Severity.ERROR,
+            implementation = Implementation(
+                    EnforcePermissionDetector::class.java,
+                    EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+            )
+        )
+
+        /**
+         * handles an edge case with UDeclarationsExpression, where sourcePsi is null,
+         * resulting in an incorrect Location if used directly
+         */
+        private fun getLocationTarget(firstExpression: UExpression): PsiElement? {
+            if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi
+            if (firstExpression is UDeclarationsExpression) {
+                return firstExpression.declarations.firstOrNull()?.sourcePsi
+            }
+            return null
+        }
     }
 }
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
deleted file mode 100644
index 758de4d..0000000
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.lint.aidl
-
-import com.android.tools.lint.client.api.UElementHandler
-import com.android.tools.lint.detector.api.Category
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Implementation
-import com.android.tools.lint.detector.api.Issue
-import com.android.tools.lint.detector.api.JavaContext
-import com.android.tools.lint.detector.api.Scope
-import com.android.tools.lint.detector.api.Severity
-import com.android.tools.lint.detector.api.SourceCodeScanner
-import com.google.android.lint.findCallExpression
-import com.intellij.psi.PsiElement
-import org.jetbrains.uast.UBlockExpression
-import org.jetbrains.uast.UDeclarationsExpression
-import org.jetbrains.uast.UElement
-import org.jetbrains.uast.UExpression
-import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.skipParenthesizedExprDown
-
-import java.util.EnumSet
-
-class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
-    override fun getApplicableUastTypes(): List<Class<out UElement?>> =
-            listOf(UMethod::class.java)
-
-    override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
-
-    private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
-        override fun visitMethod(node: UMethod) {
-            if (context.evaluator.isAbstract(node)) return
-            if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
-
-            if (!isContainedInSubclassOfStub(context, node)) {
-                context.report(
-                    ISSUE_MISUSING_ENFORCE_PERMISSION,
-                    node,
-                    context.getLocation(node),
-                    "The class of ${node.name} does not inherit from an AIDL generated Stub class"
-                )
-                return
-            }
-
-            val targetExpression = getHelperMethodCallSourceString(node)
-            val message =
-                "Method must start with $targetExpression or super.${node.name}(), if applicable"
-
-            val firstExpression = (node.uastBody as? UBlockExpression)
-                    ?.expressions?.firstOrNull()
-
-            if (firstExpression == null) {
-                context.report(
-                    ISSUE_ENFORCE_PERMISSION_HELPER,
-                    context.getLocation(node),
-                    message,
-                )
-                return
-            }
-
-            val firstExpressionSource = firstExpression.skipParenthesizedExprDown()
-              .asSourceString()
-              .filterNot(Char::isWhitespace)
-
-            if (firstExpressionSource != targetExpression &&
-                  firstExpressionSource != "super.$targetExpression") {
-                // calling super.<methodName>() is also legal
-                val directSuper = context.evaluator.getSuperMethod(node)
-                val firstCall = findCallExpression(firstExpression)?.resolve()
-                if (directSuper != null && firstCall == directSuper) return
-
-                val locationTarget = getLocationTarget(firstExpression)
-                val expressionLocation = context.getLocation(locationTarget)
-
-                context.report(
-                    ISSUE_ENFORCE_PERMISSION_HELPER,
-                    context.getLocation(node),
-                    message,
-                    getHelperMethodFix(node, expressionLocation),
-                )
-            }
-        }
-    }
-
-    companion object {
-        private const val HELPER_SUFFIX = "_enforcePermission"
-
-        private const val EXPLANATION = """
-            The @EnforcePermission annotation can only be used on methods whose class extends from
-            the Stub class generated by the AIDL compiler. When @EnforcePermission is applied, the
-            AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX.
-
-            yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can
-            either call it directly, or call it indirectly via super.yourMethodName().
-            """
-
-        val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create(
-                id = "MissingEnforcePermissionHelper",
-                briefDescription = """Missing permission-enforcing method call in AIDL method
-                    |annotated with @EnforcePermission""".trimMargin(),
-                explanation = EXPLANATION,
-                category = Category.SECURITY,
-                priority = 6,
-                severity = Severity.ERROR,
-                implementation = Implementation(
-                        EnforcePermissionHelperDetector::class.java,
-                        EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
-                )
-        )
-
-        val ISSUE_MISUSING_ENFORCE_PERMISSION: Issue = Issue.create(
-                id = "MisusingEnforcePermissionAnnotation",
-                briefDescription = "@EnforcePermission cannot be used here",
-                explanation = EXPLANATION,
-                category = Category.SECURITY,
-                priority = 6,
-                severity = Severity.ERROR,
-                implementation = Implementation(
-                        EnforcePermissionHelperDetector::class.java,
-                        EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
-                )
-        )
-
-        /**
-         * handles an edge case with UDeclarationsExpression, where sourcePsi is null,
-         * resulting in an incorrect Location if used directly
-         */
-        private fun getLocationTarget(firstExpression: UExpression): PsiElement? {
-            if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi
-            if (firstExpression is UDeclarationsExpression) {
-                return firstExpression.declarations.firstOrNull()?.sourcePsi
-            }
-            return null
-        }
-    }
-}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt
index 5a63bb4..3ef02f8 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt
@@ -25,10 +25,10 @@
 
 @Suppress("UnstableApiUsage")
 class EnforcePermissionHelperDetectorCodegenTest : LintDetectorTest() {
-    override fun getDetector(): Detector = EnforcePermissionHelperDetector()
+    override fun getDetector(): Detector = EnforcePermissionDetector()
 
     override fun getIssues(): List<Issue> = listOf(
-            EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER
+            EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER
     )
 
     override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
index 10a6e1d..64e2bfb 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
@@ -20,10 +20,10 @@
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 
 class EnforcePermissionHelperDetectorTest : LintDetectorTest() {
-    override fun getDetector() = EnforcePermissionHelperDetector()
+    override fun getDetector() = EnforcePermissionDetector()
     override fun getIssues() = listOf(
-        EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
-        EnforcePermissionHelperDetector.ISSUE_MISUSING_ENFORCE_PERMISSION
+        EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+        EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION
     )
 
     override fun lint(): TestLintTask = super.lint().allowMissingSdk()