Merge "Simplify dock setup state setting" into tm-qpr-dev
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index f49cdbf..4710322 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1777,7 +1777,10 @@
          * {@link Build.VERSION_CODES#S}, but starting from Android version
          * {@link Build.VERSION_CODES#TIRAMISU}, expedited jobs for the foreground app are
          * guaranteed to be started before {@link JobScheduler#schedule(JobInfo)} returns (assuming
-         * all requested constraints are satisfied), similar to foreground services.
+         * all requested constraints are satisfied), similar to foreground services. However, this
+         * start guarantee means there is a higher chance of overlapping executions, as noted in
+         * {@link JobService}, so be sure to handle that properly if you intend to reschedule the
+         * job while it's actively running.
          *
          * @see JobInfo#isExpedited()
          */
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 1f4ef04..388bbf1 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -107,7 +107,9 @@
     /**
      * Schedule a job to be executed.  Will replace any currently scheduled job with the same
      * ID with the new information in the {@link JobInfo}.  If a job with the given ID is currently
-     * running, it will be stopped.
+     * running, it will be stopped. Note that in some cases, the newly scheduled job may be started
+     * before the previously running job has been fully stopped. See {@link JobService} for
+     * additional details.
      *
      * <p class="caution"><strong>Note:</strong> Scheduling a job can have a high cost, even if it's
      * rescheduling the same job and the job didn't execute, especially on platform versions before
@@ -131,7 +133,7 @@
      * job.  If a job with the same ID is already scheduled, it will be replaced with the
      * new {@link JobInfo}, but any previously enqueued work will remain and be dispatched the
      * next time it runs.  If a job with the same ID is already running, the new work will be
-     * enqueued for it.
+     * enqueued for it without stopping the job.
      *
      * <p>The work you enqueue is later retrieved through
      * {@link JobParameters#dequeueWork() JobParameters.dequeueWork}.  Be sure to see there
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index e5b0742..7ed4b62 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -31,6 +31,17 @@
  * in blocking any future callbacks from the JobManager - specifically
  * {@link #onStopJob(android.app.job.JobParameters)}, which is meant to inform you that the
  * scheduling requirements are no longer being met.</p>
+ *
+ * As a subclass of {@link Service}, there will only be one active instance of any JobService
+ * subclasses, regardless of job ID. This means that if you schedule multiple jobs with different
+ * job IDs but using the same JobService class, that JobService may receive multiple calls to
+ * {@link #onStartJob(JobParameters)} and {@link #onStopJob(JobParameters)}, with each call being
+ * for the separate jobs.
+ *
+ * <p class="note">Note that if you cancel and reschedule an already executing job,
+ * there may be a small period of time where {@link #onStartJob(JobParameters)} has been called for
+ * the newly scheduled job instance before {@link #onStopJob(JobParameters)} has been called or
+ * fully processed for the old job.</p>
  */
 public abstract class JobService extends Service {
     private static final String TAG = "JobService";
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index c86353c..afe36b5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -542,6 +542,22 @@
         return mRunningJobs.contains(job);
     }
 
+    /**
+     * Returns true if a job that is "similar" to the provided job is currently running.
+     * "Similar" in this context means any job that the {@link JobStore} would consider equivalent
+     * and replace one with the other.
+     */
+    @GuardedBy("mLock")
+    private boolean isSimilarJobRunningLocked(JobStatus job) {
+        for (int i = mRunningJobs.size() - 1; i >= 0; --i) {
+            JobStatus js = mRunningJobs.valueAt(i);
+            if (job.getUid() == js.getUid() && job.getJobId() == js.getJobId()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /** Return {@code true} if the state was updated. */
     @GuardedBy("mLock")
     private boolean refreshSystemStateLocked() {
@@ -699,6 +715,21 @@
                 continue;
             }
 
+            final boolean isTopEj = nextPending.shouldTreatAsExpeditedJob()
+                    && nextPending.lastEvaluatedBias == JobInfo.BIAS_TOP_APP;
+            // Avoid overlapping job execution as much as possible.
+            if (!isTopEj && isSimilarJobRunningLocked(nextPending)) {
+                if (DEBUG) {
+                    Slog.w(TAG, "Delaying execution of job because of similarly running one: "
+                            + nextPending);
+                }
+                // It would be nice to let the JobService running the other similar job know about
+                // this new job so that it doesn't unbind from the JobService and we can call
+                // onStartJob as soon as the older job finishes.
+                // TODO: optimize the job reschedule flow to reduce service binding churn
+                continue;
+            }
+
             // Find an available slot for nextPending. The context should be one of the following:
             // 1. Unused
             // 2. Its job should have used up its minimum execution guarantee so it
@@ -707,8 +738,6 @@
             ContextAssignment selectedContext = null;
             final int allWorkTypes = getJobWorkTypes(nextPending);
             final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending);
-            final boolean isTopEj = nextPending.shouldTreatAsExpeditedJob()
-                    && nextPending.lastEvaluatedBias == JobInfo.BIAS_TOP_APP;
             final boolean isInOverage = projectedRunningCount > STANDARD_CONCURRENCY_LIMIT;
             boolean startingJob = false;
             if (idle.size() > 0) {
@@ -1177,6 +1206,15 @@
                     continue;
                 }
 
+                // Avoid overlapping job execution as much as possible.
+                if (isSimilarJobRunningLocked(nextPending)) {
+                    if (DEBUG) {
+                        Slog.w(TAG, "Avoiding execution of job because of similarly running one: "
+                                + nextPending);
+                    }
+                    continue;
+                }
+
                 if (worker.getPreferredUid() != nextPending.getUid()) {
                     if (backupJob == null && !isPkgConcurrencyLimitedLocked(nextPending)) {
                         int allWorkTypes = getJobWorkTypes(nextPending);
@@ -1260,6 +1298,15 @@
                     continue;
                 }
 
+                // Avoid overlapping job execution as much as possible.
+                if (isSimilarJobRunningLocked(nextPending)) {
+                    if (DEBUG) {
+                        Slog.w(TAG, "Avoiding execution of job because of similarly running one: "
+                                + nextPending);
+                    }
+                    continue;
+                }
+
                 if (isPkgConcurrencyLimitedLocked(nextPending)) {
                     continue;
                 }
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 358c327..cd70e88 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1208,12 +1208,22 @@
             // This may throw a SecurityException.
             jobStatus.prepareLocked();
 
+            final boolean canExecuteImmediately;
             if (toCancel != null) {
                 // Implicitly replaces the existing job record with the new instance
-                cancelJobImplLocked(toCancel, jobStatus, JobParameters.STOP_REASON_CANCELLED_BY_APP,
-                        JobParameters.INTERNAL_STOP_REASON_CANCELED, "job rescheduled by app");
+                final boolean wasJobExecuting = cancelJobImplLocked(toCancel, jobStatus,
+                        JobParameters.STOP_REASON_CANCELLED_BY_APP,
+                        JobParameters.INTERNAL_STOP_REASON_CANCELED,
+                        "job rescheduled by app");
+                // Avoid overlapping job executions. Don't push for immediate execution if an old
+                // job with the same ID was running, but let TOP EJs start immediately.
+                canExecuteImmediately = !wasJobExecuting
+                        || (jobStatus.isRequestedExpeditedJob()
+                        && mUidBiasOverride.get(jobStatus.getSourceUid(), JobInfo.BIAS_DEFAULT)
+                        == JobInfo.BIAS_TOP_APP);
             } else {
                 startTrackingJobLocked(jobStatus, null);
+                canExecuteImmediately = true;
             }
 
             if (work != null) {
@@ -1256,7 +1266,12 @@
                 // list and try to run it.
                 mJobPackageTracker.notePending(jobStatus);
                 mPendingJobQueue.add(jobStatus);
-                maybeRunPendingJobsLocked();
+                if (canExecuteImmediately) {
+                    // Don't ask the JobConcurrencyManager to try to run the job immediately. The
+                    // JobServiceContext will ask the JobConcurrencyManager for another job once
+                    // it finishes cleaning up the old job.
+                    maybeRunPendingJobsLocked();
+                }
             } else {
                 evaluateControllerStatesLocked(jobStatus);
             }
@@ -1377,8 +1392,10 @@
      * is null, the cancelled job is removed outright from the system.  If
      * {@code incomingJob} is non-null, it replaces {@code cancelled} in the store of
      * currently scheduled jobs.
+     *
+     * @return true if the cancelled job was running
      */
-    private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob,
+    private boolean cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob,
             @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
         if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
         cancelled.unprepareLocked();
@@ -1389,7 +1406,7 @@
         }
         mChangedJobList.remove(cancelled);
         // Cancel if running.
-        mConcurrencyManager.stopJobOnServiceContextLocked(
+        boolean wasRunning = mConcurrencyManager.stopJobOnServiceContextLocked(
                 cancelled, reason, internalReasonCode, debugReason);
         // If this is a replacement, bring in the new version of the job
         if (incomingJob != null) {
@@ -1397,6 +1414,7 @@
             startTrackingJobLocked(incomingJob, cancelled);
         }
         reportActiveLocked();
+        return wasRunning;
     }
 
     void updateUidState(int uid, int procState) {
@@ -1755,7 +1773,7 @@
             // same job ID), we remove it from the JobStore and tell the JobServiceContext to stop
             // running the job. Once the job stops running, we then call this method again.
             // TODO: rework code so we don't intentionally call this method twice for the same job
-            Slog.w(TAG, "Job didn't exist in JobStore");
+            Slog.w(TAG, "Job didn't exist in JobStore: " + jobStatus.toShortString());
         }
         if (mReadyToRock) {
             for (int i = 0; i < mControllers.size(); i++) {
@@ -3813,6 +3831,7 @@
                     // Double indent for readability
                     pw.increaseIndent();
                     pw.increaseIndent();
+                    pw.println(job.toShortString());
                     job.dump(pw, true, nowElapsed);
                     pw.decreaseIndent();
                     pw.decreaseIndent();
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index 80f3fea..c90291e 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -979,6 +979,7 @@
             dumpBucketExpiryTimes(idpw, appUsageHistory, totalElapsedTime);
             idpw.print(" lastJob=");
             TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw);
+            idpw.print(" lastInformedBucket=" + appUsageHistory.lastInformedBucket);
             if (appUsageHistory.lastRestrictAttemptElapsedTime > 0) {
                 idpw.print(" lastRestrictAttempt=");
                 TimeUtils.formatDuration(
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index c3d6b73..1891e06 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1155,6 +1155,12 @@
 
         final int appId = getAppId(packageName);
         if (appId < 0) return;
+        final int minBucket = getAppMinBucket(packageName, appId, userId);
+        if (idle && minBucket < AppIdleHistory.IDLE_BUCKET_CUTOFF) {
+            Slog.e(TAG, "Tried to force an app to be idle when its min bucket is "
+                    + standbyBucketToString(minBucket));
+            return;
+        }
         final long elapsedRealtime = mInjector.elapsedRealtime();
 
         final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
@@ -1166,12 +1172,10 @@
         final boolean stillIdle = isAppIdleFiltered(packageName, appId,
                 userId, elapsedRealtime);
         // Inform listeners if necessary
+        maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket,
+                REASON_MAIN_FORCED_BY_USER, false);
         if (previouslyIdle != stillIdle) {
-            maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket,
-                    REASON_MAIN_FORCED_BY_USER, false);
-            if (!stillIdle) {
-                notifyBatteryStats(packageName, userId, idle);
-            }
+            notifyBatteryStats(packageName, userId, stillIdle);
         }
     }
 
@@ -1934,6 +1938,8 @@
             }
             mAppIdleHistory.setAppStandbyBucket(
                     packageName, userId, elapsedRealtime, newBucket, newReason);
+            maybeInformListeners(packageName, userId, elapsedRealtime, newBucket,
+                    newReason, false);
         }
     }
 
@@ -2490,6 +2496,8 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_INFORM_LISTENERS:
+                    // TODO(230875908): Properly notify BatteryStats when apps change from active to
+                    // idle, and vice versa
                     StandbyUpdateRecord r = (StandbyUpdateRecord) msg.obj;
                     informListeners(r.packageName, r.userId, r.bucket, r.reason,
                             r.isUserInteraction);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 3f44889..439dff0 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -919,7 +919,7 @@
     field public int id;
     field public String lastLoggedInFingerprint;
     field public long lastLoggedInTime;
-    field public String name;
+    field @Nullable public String name;
     field public boolean partial;
     field public boolean preCreated;
     field public int profileBadge;
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index a8ae191..32207af 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -110,19 +110,6 @@
             "options": [
                 {
                     "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                // TODO(b/225076204): Remove the following four test cases after fixing the test fail.
-                {
-                    "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
-                },
-                {
-                    "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromExternalSource_success"
-                },
-                {
-                    "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
-                },
-                {
-                    "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromMic_success"
                 }
             ],
             "file_patterns": ["(/|^)VoiceInteract[^/]*"]
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 0805684..c8033fa 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -863,12 +863,6 @@
                     PREFIX + "REMOVE_AND_UNINSTALL_DEVICE_ADMIN";
 
             /**
-             * Title for selecting device admin apps
-             */
-            public static final String SELECT_DEVICE_ADMIN_APPS =
-                    PREFIX + "SELECT_DEVICE_ADMIN_APPS";
-
-            /**
              * Message when there are no available device admin apps to display
              */
             public static final String NO_DEVICE_ADMINS = PREFIX + "NO_DEVICE_ADMINS";
@@ -910,11 +904,6 @@
                     PREFIX + "ACTIVE_DEVICE_ADMIN_WARNING";
 
             /**
-             * Title for screen to set a profile owner
-             */
-            public static final String SET_PROFILE_OWNER_TITLE = PREFIX + "SET_PROFILE_OWNER_TITLE";
-
-            /**
              * Simplified title for dialog to set a profile owner
              */
             public static final String SET_PROFILE_OWNER_DIALOG_TITLE =
@@ -1173,7 +1162,8 @@
             /**
              * Header for items under the personal user
              */
-            public static final String PERSONAL_CATEGORY_HEADER = PREFIX + "CATEGORY_PERSONAL";
+            public static final String PERSONAL_CATEGORY_HEADER =
+                    PREFIX + "PERSONAL_CATEGORY_HEADER";
 
             /**
              * Text to indicate work notification content will be shown on the lockscreen.
@@ -1196,8 +1186,7 @@
             /**
              * Text for toggle to enable auto-sycing work data
              */
-            public static final String AUTO_SYNC_WORK_DATA = PREFIX
-                    + "AUTO_SYNC_WORK_DATA";
+            public static final String AUTO_SYNC_WORK_DATA = PREFIX + "AUTO_SYNC_WORK_DATA";
 
             /**
              * Summary for "More security settings" section when a work profile is on the device.
@@ -1213,10 +1202,17 @@
                     + "LOCK_SETTINGS_NEW_PROFILE_LOCK_TITLE";
 
             /**
+             * Title for screen asking the user to update the type of screen lock (such as a
+             * pattern, PIN, or password) that they need to enter to use their work apps
+             */
+            public static final String LOCK_SETTINGS_UPDATE_PROFILE_LOCK_TITLE = PREFIX
+                    + "LOCK_SETTINGS_UPDATE_PROFILE_LOCK_TITLE";
+
+            /**
              * Title for section listing information that can be seen by organization
              */
             public static final String INFORMATION_SEEN_BY_ORGANIZATION_TITLE = PREFIX
-                    + "information_seen_by_organization_title";
+                    + "INFORMATION_SEEN_BY_ORGANIZATION_TITLE";
 
             /**
              * Title for section listing changes made by the organization.
@@ -1229,6 +1225,30 @@
              */
             public static final String ENTERPRISE_PRIVACY_FOOTER =
                     PREFIX + "ENTERPRISE_PRIVACY_FOOTER";
+
+            /**
+             * Title for spell checker settings for work.
+             */
+            public static final String SPELL_CHECKER_FOR_WORK =
+                    PREFIX + "SPELL_CHECKER_FOR_WORK";
+
+            /**
+             * Title for personal dictionary for work settings.
+             */
+            public static final String PERSONAL_DICTIONARY_FOR_WORK =
+                    PREFIX + "PERSONAL_DICTIONARY_FOR_WORK";
+
+            /**
+             * Summary for switch preference to indicate it is disabled by the admin
+             */
+            public static final String DISABLED_BY_ADMIN_SWITCH_SUMMARY =
+                    PREFIX + "DISABLED_BY_ADMIN_SWITCH_SUMMARY";
+
+            /**
+             * Summary for switch preference to indicate it is enabled by the admin
+             */
+            public static final String ENABLED_BY_ADMIN_SWITCH_SUMMARY =
+                    PREFIX + "ENABLED_BY_ADMIN_SWITCH_SUMMARY";
         }
 
         /**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4d56c1d..907db7d 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -513,10 +513,23 @@
      * restart.  There is no guarantee this will be respected, as the system
      * tries to balance such requests from one app vs. the importance of
      * keeping other apps around.
+     *
+     * @deprecated Repurposed to {@link #BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE}.
      */
+    @Deprecated
     public static final int BIND_VISIBLE = 0x10000000;
 
     /**
+     * @hide Flag for {@link #bindService}: Treat the binding as hosting a foreground service
+     * and also visible to the user. That is, the app hosting the service will get its process state
+     * bumped to the {@link android.app.ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE},
+     * and it's considered as visible to the user, thus less likely to be expunged from memory
+     * on low memory situations. This is intented for use by processes with the process state
+     * better than the {@link android.app.ActivityManager#PROCESS_STATE_TOP}.
+     */
+    public static final int BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE = 0x10000000;
+
+    /**
      * @hide
      * Flag for {@link #bindService}: Consider this binding to be causing the target
      * process to be showing UI, so it will be do a UI_HIDDEN memory trim when it goes
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 816460b..9baa6ba 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -187,7 +188,7 @@
     @UnsupportedAppUsage
     public int serialNumber;
     @UnsupportedAppUsage
-    public String name;
+    public @Nullable String name;
     @UnsupportedAppUsage
     public String iconPath;
     @UnsupportedAppUsage
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index b05e6d1..a90eb88 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1058,7 +1058,7 @@
      * must select one unique size from this metadata to use (e.g., preview and recording streams
      * must have the same size). Otherwise, the high speed capture session creation will fail.</p>
      * <p>The min and max fps will be multiple times of 30fps.</p>
-     * <p>High speed video streaming extends significant performance pressue to camera hardware,
+     * <p>High speed video streaming extends significant performance pressure to camera hardware,
      * to achieve efficient high speed streaming, the camera device may have to aggregate
      * multiple frames together and send to camera device for processing where the request
      * controls are same for all the frames in this batch. Max batch size indicates
@@ -1143,7 +1143,7 @@
      * <p>Range of boosts for {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost} supported
      * by this camera device.</p>
      * <p>Devices support post RAW sensitivity boost  will advertise
-     * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost} key for controling
+     * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost} key for controlling
      * post RAW sensitivity boost.</p>
      * <p>This key will be <code>null</code> for devices that do not support any RAW format
      * outputs. For devices that do support RAW format outputs, this key will always
@@ -1323,7 +1323,7 @@
      * <p>Maximum flashlight brightness level.</p>
      * <p>If this value is greater than 1, then the device supports controlling the
      * flashlight brightness level via
-     * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}.
+     * {@link android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel }.
      * If this value is equal to 1, flashlight brightness control is not supported.
      * The value for this key will be null for devices with no flash unit.</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
@@ -1335,7 +1335,7 @@
 
     /**
      * <p>Default flashlight brightness level to be set via
-     * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}.</p>
+     * {@link android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel }.</p>
      * <p>If flash unit is available this will be greater than or equal to 1 and less
      * or equal to <code>{@link CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL android.flash.info.strengthMaximumLevel}</code>.</p>
      * <p>Setting flashlight brightness above the default level
@@ -1376,7 +1376,7 @@
      * camera device.</p>
      * <p>This list will include at least one non-zero resolution, plus <code>(0,0)</code> for indicating no
      * thumbnail should be generated.</p>
-     * <p>Below condiditions will be satisfied for this size list:</p>
+     * <p>Below conditions will be satisfied for this size list:</p>
      * <ul>
      * <li>The sizes will be sorted by increasing pixel area (width x height).
      * If several resolutions have the same area, they will be sorted by increasing width.</li>
@@ -1982,7 +1982,7 @@
      * the camera device. Using more streams simultaneously may require more hardware and
      * CPU resources that will consume more power. The image format for an output stream can
      * be any supported format provided by android.scaler.availableStreamConfigurations.
-     * The formats defined in android.scaler.availableStreamConfigurations can be catergorized
+     * The formats defined in android.scaler.availableStreamConfigurations can be categorized
      * into the 3 stream types as below:</p>
      * <ul>
      * <li>Processed (but stalling): any non-RAW format with a stallDurations &gt; 0.
@@ -2324,7 +2324,7 @@
      * but clients should be aware and expect delays during their application.
      * An example usage scenario could look like this:</p>
      * <ul>
-     * <li>The camera client starts by quering the session parameter key list via
+     * <li>The camera client starts by querying the session parameter key list via
      *   {@link android.hardware.camera2.CameraCharacteristics#getAvailableSessionKeys }.</li>
      * <li>Before triggering the capture session create sequence, a capture request
      *   must be built via
@@ -2379,7 +2379,7 @@
      * {@link android.hardware.camera2.CameraCharacteristics#getKeys } that require camera clients
      * to acquire the {@link android.Manifest.permission#CAMERA } permission before calling
      * {@link android.hardware.camera2.CameraManager#getCameraCharacteristics }. If the
-     * permission is not held by the camera client, then the values of the repsective properties
+     * permission is not held by the camera client, then the values of the respective properties
      * will not be present in {@link android.hardware.camera2.CameraCharacteristics }.</p>
      * <p>This key is available on all devices.</p>
      * @hide
@@ -2759,7 +2759,7 @@
      * </table>
      * <p>For applications targeting SDK version 31 or newer, if the mobile device declares to be
      * media performance class 12 or higher by setting
-     * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger,
+     * {@link android.os.Build.VERSION_CODES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger,
      * the primary camera devices (first rear/front camera in the camera ID list) will not
      * support JPEG sizes smaller than 1080p. If the application configures a JPEG stream
      * smaller than 1080p, the camera device will round up the JPEG image size to at least
@@ -2833,7 +2833,7 @@
      * </table>
      * <p>For applications targeting SDK version 31 or newer, if the mobile device doesn't declare
      * to be media performance class 12 or better by setting
-     * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger,
+     * {@link android.os.Build.VERSION_CODES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger,
      * or if the camera device isn't a primary rear/front camera, the minimum required output
      * stream configurations are the same as for applications targeting SDK version older than
      * 31.</p>
@@ -3485,14 +3485,16 @@
      * to output different resolution images depending on the current active physical camera or
      * pixel mode. With multi-resolution input streams, the camera device can reprocess images
      * of different resolutions from different physical cameras or sensor pixel modes.</p>
-     * <p>When set to TRUE:
-     * * For a logical multi-camera, the camera framework derives
+     * <p>When set to TRUE:</p>
+     * <ul>
+     * <li>For a logical multi-camera, the camera framework derives
      * {@link CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP android.scaler.multiResolutionStreamConfigurationMap} by combining the
      * android.scaler.physicalCameraMultiResolutionStreamConfigurations from its physical
-     * cameras.
-     * * For an ultra-high resolution sensor camera, the camera framework directly copies
+     * cameras.</li>
+     * <li>For an ultra-high resolution sensor camera, the camera framework directly copies
      * the value of android.scaler.physicalCameraMultiResolutionStreamConfigurations to
-     * {@link CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP android.scaler.multiResolutionStreamConfigurationMap}.</p>
+     * {@link CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP android.scaler.multiResolutionStreamConfigurationMap}.</li>
+     * </ul>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      * <p><b>Limited capability</b> -
      * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
@@ -3513,7 +3515,7 @@
      * capture, video record for encoding the camera output for the purpose of future playback,
      * and video call for live realtime video conferencing.</p>
      * <p>With this flag, the camera device can optimize the image processing pipeline
-     * parameters, such as tuning, sensor mode, and ISP settings, indepedent of
+     * parameters, such as tuning, sensor mode, and ISP settings, independent of
      * the properties of the immediate camera output surface. For example, if the output
      * surface is a SurfaceTexture, the stream use case flag can be used to indicate whether
      * the camera frames eventually go to display, video encoder,
@@ -3535,7 +3537,7 @@
      * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE }
      * capability is documented in the camera device
      * {@link android.hardware.camera2.CameraDevice#createCaptureSession guideline}. The
-     * application is strongly recommended to use one of the guaranteed stream combintations.
+     * application is strongly recommended to use one of the guaranteed stream combinations.
      * If the application creates a session with a stream combination not in the guaranteed
      * list, or with mixed DEFAULT and non-DEFAULT use cases within the same session,
      * the camera device may ignore some stream use cases due to hardware constraints
@@ -5209,7 +5211,7 @@
      * EXTERIOR_* value.</p>
      * <p>If a camera has INTERIOR_OTHER or EXTERIOR_OTHER, or more than one camera is at the
      * same location and facing the same direction, their static metadata will list the
-     * following entries, so that applications can determain their lenses' exact facing
+     * following entries, so that applications can determine their lenses' exact facing
      * directions:</p>
      * <ul>
      * <li>{@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference}</li>
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 40565b0..eb8c73a 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -409,7 +409,7 @@
 
     /**
      * <p>The value of {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} is relative to the origin of the
-     * automotive sensor coodinate system, which is at the center of the rear axle.</p>
+     * automotive sensor coordinate system, which is at the center of the rear axle.</p>
      *
      * @see CameraCharacteristics#LENS_POSE_TRANSLATION
      * @see CameraCharacteristics#LENS_POSE_REFERENCE
@@ -683,7 +683,7 @@
      * captured at the same rate as the maximum-size YUV_420_888 resolution is.</p>
      * <p>If the device supports the PRIVATE_REPROCESSING capability, then the same guarantees
      * as for the YUV_420_888 format also apply to the {@link android.graphics.ImageFormat#PRIVATE } format.</p>
-     * <p>In addition, the {@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} field is guaranted to have a value between 0
+     * <p>In addition, the {@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} field is guaranteed to have a value between 0
      * and 4, inclusive. {@link CameraCharacteristics#CONTROL_AE_LOCK_AVAILABLE android.control.aeLockAvailable} and {@link CameraCharacteristics#CONTROL_AWB_LOCK_AVAILABLE android.control.awbLockAvailable}
      * are also guaranteed to be <code>true</code> so burst capture with these two locks ON yields
      * consistent image output.</p>
@@ -843,7 +843,7 @@
      * {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoSizes }.</li>
      * <li>The FPS ranges are selected from {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoFpsRanges }.</li>
      * </ul>
-     * <p>When above conditions are NOT satistied,
+     * <p>When above conditions are NOT satisfied,
      * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession }
      * will fail.</p>
      * <p>Switching to a FPS range that has different maximum FPS may trigger some camera device
@@ -986,7 +986,7 @@
      * non-active physical cameras. For example, if the logical camera has a wide-ultrawide
      * configuration where the wide lens is the default, when the crop region is set to the
      * logical camera's active array size, (and the zoom ratio set to 1.0 starting from
-     * Android 11), a physical stream for the ultrawide camera may prefer outputing images
+     * Android 11), a physical stream for the ultrawide camera may prefer outputting images
      * with larger field-of-view than that of the wide camera for better stereo matching
      * margin or more robust motion tracking. At the same time, the physical non-RAW streams'
      * field of view must not be smaller than the requested crop region and zoom ratio, as
@@ -1175,21 +1175,23 @@
      * when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }),
      * the <code>RAW_SENSOR</code> stream will have a regular bayer pattern.</p>
-     * <p>This capability requires the camera device to support the following :
-     * * The {@link android.hardware.camera2.params.StreamConfigurationMap } mentioned below
+     * <p>This capability requires the camera device to support the following :</p>
+     * <ul>
+     * <li>The {@link android.hardware.camera2.params.StreamConfigurationMap } mentioned below
      *   refers to the one, described by
-     *   <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code>.
-     * * One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.
-     * * {@link android.graphics.ImageFormat#RAW_SENSOR } is supported as an output/input
+     *   <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code>.</li>
+     * <li>One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.</li>
+     * <li>{@link android.graphics.ImageFormat#RAW_SENSOR } is supported as an output/input
      *   format, that is, {@link android.graphics.ImageFormat#RAW_SENSOR } is included in the
-     *   lists of formats returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }.
-     * * {@link android.hardware.camera2.params.StreamConfigurationMap#getValidOutputFormatsForInput }
-     *   returns non-empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }.
-     * * Each size returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputSizes getInputSizes(ImageFormat.RAW_SENSOR)} is also included in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes getOutputSizes(ImageFormat.RAW_SENSOR)}
-     * * Using {@link android.graphics.ImageFormat#RAW_SENSOR } does not cause a frame rate
-     *   drop relative to the sensor's maximum capture rate (at that resolution).
-     * * No CaptureRequest controls will be applicable when a request has an input target
-     *   with {@link android.graphics.ImageFormat#RAW_SENSOR } format.</p>
+     *   lists of formats returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }.</li>
+     * <li>{@link android.hardware.camera2.params.StreamConfigurationMap#getValidOutputFormatsForInput }
+     *   returns non-empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }.</li>
+     * <li>Each size returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputSizes getInputSizes(ImageFormat.RAW_SENSOR)} is also included in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes getOutputSizes(ImageFormat.RAW_SENSOR)}</li>
+     * <li>Using {@link android.graphics.ImageFormat#RAW_SENSOR } does not cause a frame rate
+     *   drop relative to the sensor's maximum capture rate (at that resolution).</li>
+     * <li>No CaptureRequest controls will be applicable when a request has an input target
+     *   with {@link android.graphics.ImageFormat#RAW_SENSOR } format.</li>
+     * </ul>
      *
      * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
@@ -1205,16 +1207,18 @@
      * {@link android.hardware.camera2.params.DynamicRangeProfiles#getSupportedProfiles }.
      * They can be configured as part of the capture session initialization via
      * {@link android.hardware.camera2.params.OutputConfiguration#setDynamicRangeProfile }.
-     * Cameras that enable this capability must also support the following:
-     * * Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }
-     * * All mandatory stream combinations for this specific capability as per
-     *   documentation {@link android.hardware.camera2.CameraDevice#createCaptureSession }
-     * * In case the device is not able to capture some combination of supported
+     * Cameras that enable this capability must also support the following:</p>
+     * <ul>
+     * <li>Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }</li>
+     * <li>All mandatory stream combinations for this specific capability as per
+     *   documentation {@link android.hardware.camera2.CameraDevice#createCaptureSession }</li>
+     * <li>In case the device is not able to capture some combination of supported
      *   standard 8-bit and/or 10-bit dynamic range profiles within the same capture request,
      *   then those constraints must be listed in
-     *   {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints }
-     * * Recommended dynamic range profile listed in
-     *   {@link android.hardware.camera2.CameraCharacteristics#REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE }.</p>
+     *   {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints }</li>
+     * <li>Recommended dynamic range profile listed in
+     *   {@link android.hardware.camera2.CameraCharacteristics#REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE }.</li>
+     * </ul>
      * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
      */
     public static final int REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT = 18;
@@ -1224,22 +1228,26 @@
      * {@link android.hardware.camera2.params.OutputConfiguration#setStreamUseCase }
      * so that the device can optimize camera pipeline parameters such as tuning, sensor
      * mode, or ISP settings for a specific user scenario.
-     * Some sample usages of this capability are:
-     * * Distinguish high quality YUV captures from a regular YUV stream where
-     *   the image quality may not be as good as the JPEG stream, or
-     * * Use one stream to serve multiple purposes: viewfinder, video recording and
+     * Some sample usages of this capability are:</p>
+     * <ul>
+     * <li>Distinguish high quality YUV captures from a regular YUV stream where
+     *   the image quality may not be as good as the JPEG stream, or</li>
+     * <li>Use one stream to serve multiple purposes: viewfinder, video recording and
      *   still capture. This is common with applications that wish to apply edits equally
-     *   to preview, saved images, and saved videos.</p>
+     *   to preview, saved images, and saved videos.</li>
+     * </ul>
      * <p>This capability requires the camera device to support the following
-     * stream use cases:
-     * * DEFAULT for backward compatibility where the application doesn't set
-     *   a stream use case
-     * * PREVIEW for live viewfinder and in-app image analysis
-     * * STILL_CAPTURE for still photo capture
-     * * VIDEO_RECORD for recording video clips
-     * * PREVIEW_VIDEO_STILL for one single stream used for viewfinder, video
-     *   recording, and still capture.
-     * * VIDEO_CALL for long running video calls</p>
+     * stream use cases:</p>
+     * <ul>
+     * <li>DEFAULT for backward compatibility where the application doesn't set
+     *   a stream use case</li>
+     * <li>PREVIEW for live viewfinder and in-app image analysis</li>
+     * <li>STILL_CAPTURE for still photo capture</li>
+     * <li>VIDEO_RECORD for recording video clips</li>
+     * <li>PREVIEW_VIDEO_STILL for one single stream used for viewfinder, video
+     *   recording, and still capture.</li>
+     * <li>VIDEO_CALL for long running video calls</li>
+     * </ul>
      * <p>{@link android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES }
      * lists all of the supported stream use cases.</p>
      * <p>Refer to {@link android.hardware.camera2.CameraDevice#createCaptureSession } for the
@@ -1391,10 +1399,10 @@
      * <p>Live stream shown to the user.</p>
      * <p>Optimized for performance and usability as a viewfinder, but not necessarily for
      * image quality. The output is not meant to be persisted as saved images or video.</p>
-     * <p>No stall if android.control.<em> are set to FAST; may have stall if android.control.</em>
-     * are set to HIGH_QUALITY. This use case has the same behavior as the default
-     * SurfaceView and SurfaceTexture targets. Additionally, this use case can be used for
-     * in-app image analysis.</p>
+     * <p>No stall if android.control.* are set to FAST. There may be stall if
+     * they are set to HIGH_QUALITY. This use case has the same behavior as the
+     * default SurfaceView and SurfaceTexture targets. Additionally, this use case can be
+     * used for in-app image analysis.</p>
      * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES
      */
     public static final int SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW = 0x1;
@@ -1441,7 +1449,7 @@
     public static final int SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL = 0x4;
 
     /**
-     * <p>Long-running video call optimized for both power efficienty and video quality.</p>
+     * <p>Long-running video call optimized for both power efficiency and video quality.</p>
      * <p>The camera sensor may run in a lower-resolution mode to reduce power consumption
      * at the cost of some image and digital zoom quality. Unlike VIDEO_RECORD, VIDEO_CALL
      * outputs are expected to work in dark conditions, so are usually accompanied with
@@ -2946,10 +2954,10 @@
      * android.control.availableHighSpeedVideoConfigurations.</li>
      * <li>No processed non-stalling or raw streams are configured.</li>
      * </ul>
-     * <p>When above conditions are NOT satistied, the controls of this mode and
+     * <p>When above conditions are NOT satisfied, the controls of this mode and
      * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange} will be ignored by the camera device,
      * the camera device will fall back to {@link CaptureRequest#CONTROL_MODE android.control.mode} <code>==</code> AUTO,
-     * and the returned capture result metadata will give the fps range choosen
+     * and the returned capture result metadata will give the fps range chosen
      * by the camera device.</p>
      * <p>Switching into or out of this mode may trigger some camera ISP/sensor
      * reconfigurations, which may introduce extra latency. It is recommended that
@@ -3034,7 +3042,7 @@
      * if the {@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange} gives range of [100, 1600],
      * the camera device auto-exposure routine tuning process may limit the actual
      * exposure sensitivity range to [100, 1200] to ensure that the noise level isn't
-     * exessive in order to preserve the image quality. Under this situation, the image under
+     * excessive in order to preserve the image quality. Under this situation, the image under
      * low light may be under-exposed when the sensor max exposure time (bounded by the
      * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange} when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is one of the
      * ON_* modes) and effective max sensitivity are reached. This scene mode allows the
@@ -3631,7 +3639,7 @@
     public static final int TONEMAP_MODE_HIGH_QUALITY = 2;
 
     /**
-     * <p>Use the gamma value specified in {@link CaptureRequest#TONEMAP_GAMMA android.tonemap.gamma} to peform
+     * <p>Use the gamma value specified in {@link CaptureRequest#TONEMAP_GAMMA android.tonemap.gamma} to perform
      * tonemapping.</p>
      * <p>All color enhancement and tonemapping must be disabled, except
      * for applying the tonemapping curve specified by {@link CaptureRequest#TONEMAP_GAMMA android.tonemap.gamma}.</p>
@@ -3644,7 +3652,7 @@
 
     /**
      * <p>Use the preset tonemapping curve specified in
-     * {@link CaptureRequest#TONEMAP_PRESET_CURVE android.tonemap.presetCurve} to peform tonemapping.</p>
+     * {@link CaptureRequest#TONEMAP_PRESET_CURVE android.tonemap.presetCurve} to perform tonemapping.</p>
      * <p>All color enhancement and tonemapping must be disabled, except
      * for applying the tonemapping curve specified by
      * {@link CaptureRequest#TONEMAP_PRESET_CURVE android.tonemap.presetCurve}.</p>
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 15e59e0..c5cf0f6 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1767,7 +1767,7 @@
      * routine is enabled, overriding the application's selected
      * {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and
      * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}. Note that when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}
-     * is OFF, the behavior of AWB is device dependent. It is recommened to
+     * is OFF, the behavior of AWB is device dependent. It is recommended to
      * also set AWB mode to OFF or lock AWB by using {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} before
      * setting AE mode to OFF.</p>
      * <p>When set to the OFF mode, the camera device's auto-white balance
@@ -1917,13 +1917,15 @@
      * strategy.</p>
      * <p>This control (except for MANUAL) is only effective if
      * <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p>
-     * <p>All intents are supported by all devices, except that:
-     *   * ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
-     * PRIVATE_REPROCESSING or YUV_REPROCESSING.
-     *   * MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
-     * MANUAL_SENSOR.
-     *   * MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
-     * MOTION_TRACKING.</p>
+     * <p>All intents are supported by all devices, except that:</p>
+     * <ul>
+     * <li>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+     * PRIVATE_REPROCESSING or YUV_REPROCESSING.</li>
+     * <li>MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+     * MANUAL_SENSOR.</li>
+     * <li>MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+     * MOTION_TRACKING.</li>
+     * </ul>
      * <p><b>Possible values:</b></p>
      * <ul>
      *   <li>{@link #CONTROL_CAPTURE_INTENT_CUSTOM CUSTOM}</li>
@@ -2680,7 +2682,7 @@
      *   and keep jpeg and thumbnail image data unrotated.</li>
      * <li>Rotate the jpeg and thumbnail image data and not set
      *   {@link android.media.ExifInterface#TAG_ORIENTATION EXIF orientation flag}. In this
-     *   case, LIMITED or FULL hardware level devices will report rotated thumnail size in
+     *   case, LIMITED or FULL hardware level devices will report rotated thumbnail size in
      *   capture result, so the width and height will be interchanged if 90 or 270 degree
      *   orientation is requested. LEGACY device will always report unrotated thumbnail
      *   size.</li>
@@ -3806,9 +3808,11 @@
     /**
      * <p>Tonemapping curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
      * GAMMA_VALUE</p>
-     * <p>The tonemap curve will be defined the following formula:
-     * * OUT = pow(IN, 1.0 / gamma)
-     * where IN and OUT is the input pixel value scaled to range [0.0, 1.0],
+     * <p>The tonemap curve will be defined the following formula:</p>
+     * <ul>
+     * <li>OUT = pow(IN, 1.0 / gamma)</li>
+     * </ul>
+     * <p>where IN and OUT is the input pixel value scaled to range [0.0, 1.0],
      * pow is the power function and gamma is the gamma value specified by this
      * key.</p>
      * <p>The same curve will be applied to all color channels. The camera device
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 1faec5b..3e1deb2 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -1173,7 +1173,7 @@
      * <td align="center">Any state (excluding LOCKED)</td>
      * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
      * <td align="center">CONVERGED</td>
-     * <td align="center">Converged after a precapture sequenceis canceled, transient states are skipped by camera device.</td>
+     * <td align="center">Converged after a precapture sequences canceled, transient states are skipped by camera device.</td>
      * </tr>
      * <tr>
      * <td align="center">CONVERGED</td>
@@ -1847,7 +1847,7 @@
      * routine is enabled, overriding the application's selected
      * {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and
      * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}. Note that when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}
-     * is OFF, the behavior of AWB is device dependent. It is recommened to
+     * is OFF, the behavior of AWB is device dependent. It is recommended to
      * also set AWB mode to OFF or lock AWB by using {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} before
      * setting AE mode to OFF.</p>
      * <p>When set to the OFF mode, the camera device's auto-white balance
@@ -1997,13 +1997,15 @@
      * strategy.</p>
      * <p>This control (except for MANUAL) is only effective if
      * <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p>
-     * <p>All intents are supported by all devices, except that:
-     *   * ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
-     * PRIVATE_REPROCESSING or YUV_REPROCESSING.
-     *   * MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
-     * MANUAL_SENSOR.
-     *   * MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
-     * MOTION_TRACKING.</p>
+     * <p>All intents are supported by all devices, except that:</p>
+     * <ul>
+     * <li>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+     * PRIVATE_REPROCESSING or YUV_REPROCESSING.</li>
+     * <li>MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+     * MANUAL_SENSOR.</li>
+     * <li>MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+     * MOTION_TRACKING.</li>
+     * </ul>
      * <p><b>Possible values:</b></p>
      * <ul>
      *   <li>{@link #CONTROL_CAPTURE_INTENT_CUSTOM CUSTOM}</li>
@@ -2929,7 +2931,7 @@
      *   and keep jpeg and thumbnail image data unrotated.</li>
      * <li>Rotate the jpeg and thumbnail image data and not set
      *   {@link android.media.ExifInterface#TAG_ORIENTATION EXIF orientation flag}. In this
-     *   case, LIMITED or FULL hardware level devices will report rotated thumnail size in
+     *   case, LIMITED or FULL hardware level devices will report rotated thumbnail size in
      *   capture result, so the width and height will be interchanged if 90 or 270 degree
      *   orientation is requested. LEGACY device will always report unrotated thumbnail
      *   size.</li>
@@ -3149,7 +3151,7 @@
      * <p>When the state is STATIONARY, the lens parameters are not changing. This could be
      * either because the parameters are all fixed, or because the lens has had enough
      * time to reach the most recently-requested values.
-     * If all these lens parameters are not changable for a camera device, as listed below:</p>
+     * If all these lens parameters are not changeable for a camera device, as listed below:</p>
      * <ul>
      * <li>Fixed focus (<code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} == 0</code>), which means
      * {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance} parameter will always be 0.</li>
@@ -4009,7 +4011,7 @@
      * noise model used here is:</p>
      * <p>N(x) = sqrt(Sx + O)</p>
      * <p>Where x represents the recorded signal of a CFA channel normalized to
-     * the range [0, 1], and S and O are the noise model coeffiecients for
+     * the range [0, 1], and S and O are the noise model coefficients for
      * that channel.</p>
      * <p>A more detailed description of the noise model can be found in the
      * Adobe DNG specification for the NoiseProfile tag.</p>
@@ -4054,7 +4056,7 @@
      * <li>1.20 &lt;= R &gt;= 1.03 will require some software
      * correction to avoid demosaic errors (3-20% divergence).</li>
      * <li>R &gt; 1.20 will require strong software correction to produce
-     * a usuable image (&gt;20% divergence).</li>
+     * a usable image (&gt;20% divergence).</li>
      * </ul>
      * <p>Starting from Android Q, this key will not be present for a MONOCHROME camera, even if
      * the camera device has RAW capability.</p>
@@ -4274,7 +4276,7 @@
     /**
      * <p>Whether <code>RAW</code> images requested have their bayer pattern as described by
      * {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.</p>
-     * <p>This key will only be present in devices advertisting the
+     * <p>This key will only be present in devices advertising the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
      * capability which also advertise <code>REMOSAIC_REPROCESSING</code> capability. On all other devices
      * RAW targets will have a regular bayer pattern.</p>
@@ -5128,9 +5130,11 @@
     /**
      * <p>Tonemapping curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
      * GAMMA_VALUE</p>
-     * <p>The tonemap curve will be defined the following formula:
-     * * OUT = pow(IN, 1.0 / gamma)
-     * where IN and OUT is the input pixel value scaled to range [0.0, 1.0],
+     * <p>The tonemap curve will be defined the following formula:</p>
+     * <ul>
+     * <li>OUT = pow(IN, 1.0 / gamma)</li>
+     * </ul>
+     * <p>where IN and OUT is the input pixel value scaled to range [0.0, 1.0],
      * pow is the power function and gamma is the gamma value specified by this
      * key.</p>
      * <p>The same curve will be applied to all color channels. The camera device
diff --git a/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
index 14ed689..34c8336 100644
--- a/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
+++ b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
@@ -277,7 +277,7 @@
      * profile.</p>
      *
      * @return non-modifiable set of dynamic range profiles
-     * @throws IllegalArgumentException - If the profile argument is not
+     * @throws IllegalArgumentException   If the profile argument is not
      *                                    within the list returned by
      *                                    getSupportedProfiles()
      *
@@ -303,7 +303,7 @@
      *
      * @return true if the given profile is not suitable for latency sensitive use cases, false
      *         otherwise
-     * @throws IllegalArgumentException - If the profile argument is not
+     * @throws IllegalArgumentException   If the profile argument is not
      *                                    within the list returned by
      *                                    getSupportedProfiles()
      *
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
index 4bf9a74..f701ec3 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
@@ -39,6 +39,7 @@
      * See {@link FingerprintSensorProperties.SensorType}.
      */
     public final @FingerprintSensorProperties.SensorType int sensorType;
+    public final boolean halControlsIllumination;
 
     private final List<SensorLocationInternal> mSensorLocations;
 
@@ -46,6 +47,7 @@
             @SensorProperties.Strength int strength, int maxEnrollmentsPerUser,
             @NonNull List<ComponentInfoInternal> componentInfo,
             @FingerprintSensorProperties.SensorType int sensorType,
+            boolean halControlsIllumination,
             boolean resetLockoutRequiresHardwareAuthToken,
             @NonNull List<SensorLocationInternal> sensorLocations) {
         // IBiometricsFingerprint@2.1 handles lockout in the framework, so the challenge is not
@@ -55,6 +57,7 @@
         super(sensorId, strength, maxEnrollmentsPerUser, componentInfo,
             resetLockoutRequiresHardwareAuthToken, false /* resetLockoutRequiresChallenge */);
         this.sensorType = sensorType;
+        this.halControlsIllumination = halControlsIllumination;
         this.mSensorLocations = List.copyOf(sensorLocations);
     }
 
@@ -68,14 +71,15 @@
             boolean resetLockoutRequiresHardwareAuthToken) {
         // TODO(b/179175438): Value should be provided from the HAL
         this(sensorId, strength, maxEnrollmentsPerUser, componentInfo, sensorType,
-                resetLockoutRequiresHardwareAuthToken, List.of(new SensorLocationInternal(
-                        "" /* displayId */,  540 /* sensorLocationX */, 1636 /* sensorLocationY */,
-                        130 /* sensorRadius */)));
+                false /* halControlsIllumination */, resetLockoutRequiresHardwareAuthToken,
+                List.of(new SensorLocationInternal("" /* displayId */, 540 /* sensorLocationX */,
+                        1636 /* sensorLocationY */, 130 /* sensorRadius */)));
     }
 
     protected FingerprintSensorPropertiesInternal(Parcel in) {
         super(in);
         sensorType = in.readInt();
+        halControlsIllumination = in.readBoolean();
         mSensorLocations = in.createTypedArrayList(SensorLocationInternal.CREATOR);
     }
 
@@ -101,6 +105,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         dest.writeInt(sensorType);
+        dest.writeBoolean(halControlsIllumination);
         dest.writeTypedList(mSensorLocations);
     }
 
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 570d533..1a23020 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2188,7 +2188,10 @@
             }
         } else {
             UserInfo userInfo = getUserInfo(mUserId);
-            return userInfo == null ? "" : userInfo.name;
+            if (userInfo != null && userInfo.name != null) {
+                return userInfo.name;
+            }
+            return "";
         }
     }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 89d7cff..f988985 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7232,13 +7232,6 @@
          */
         public static final String LOCATION_SHOW_SYSTEM_OPS = "locationShowSystemOps";
 
-
-        /**
-         * Whether or not an indicator experiment has started.
-         * @hide
-         */
-        public static final String LOCATION_INDICATOR_EXPERIMENT_STARTED =
-                "locationIndicatorExperimentStarted";
         /**
          * A flag containing settings used for biometric weak
          * @hide
diff --git a/core/java/android/service/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java
index cf4e6a6..79bfa35 100644
--- a/core/java/android/service/dreams/DreamActivity.java
+++ b/core/java/android/service/dreams/DreamActivity.java
@@ -21,8 +21,6 @@
 import android.os.Bundle;
 import android.text.TextUtils;
 
-import com.android.internal.R;
-
 /**
  * The Activity used by the {@link DreamService} to draw screensaver content
  * on the screen. This activity runs in dream application's process, but is started by a
@@ -65,17 +63,4 @@
             callback.onActivityCreated(this);
         }
     }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        overridePendingTransition(R.anim.dream_activity_open_enter,
-                                  R.anim.dream_activity_open_exit);
-    }
-
-    @Override
-    public void finishAndRemoveTask() {
-        super.finishAndRemoveTask();
-        overridePendingTransition(0, R.anim.dream_activity_close_exit);
-    }
 }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index c83869c..ee49d7bf 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -251,18 +251,6 @@
      */
     void refreshScreenCaptureDisabled();
 
-    // These can only be called with the SET_ORIENTATION permission.
-    /**
-     * Update the current screen rotation based on the current state of
-     * the world.
-     * @param alwaysSendConfiguration Flag to force a new configuration to
-     * be evaluated.  This can be used when there are other parameters in
-     * configuration that are changing.
-     * @param forceRelayout If true, the window manager will always do a relayout
-     * of its windows even if the rotation hasn't changed.
-     */
-    void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout);
-
     /**
      * Retrieve the current orientation of the primary screen.
      * @return Constant as per {@link android.view.Surface.Rotation}.
@@ -443,11 +431,6 @@
     boolean isSafeModeEnabled();
 
     /**
-     * Enables the screen if all conditions are met.
-     */
-    void enableScreenIfNeeded();
-
-    /**
      * Clears the frame statistics for a given window.
      *
      * @param token The window token.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 183ee27..4e20953 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4196,26 +4196,6 @@
         });
     }
 
-    @Nullable
-    private void registerFrameDrawingCallbackForBlur() {
-        if (!isHardwareEnabled()) {
-            return;
-        }
-        final boolean hasBlurUpdates = mBlurRegionAggregator.hasUpdates();
-        final boolean needsCallbackForBlur = hasBlurUpdates || mBlurRegionAggregator.hasRegions();
-
-        if (!needsCallbackForBlur) {
-            return;
-        }
-
-        final BackgroundBlurDrawable.BlurRegion[] blurRegionsForFrame =
-                mBlurRegionAggregator.getBlurRegionsCopyForRT();
-
-        // The callback will run on the render thread.
-        registerRtFrameCallback((frame) -> mBlurRegionAggregator
-                .dispatchBlurTransactionIfNeeded(frame, blurRegionsForFrame, hasBlurUpdates));
-    }
-
     private void registerCallbackForPendingTransactions() {
         registerRtFrameCallback(new FrameDrawingCallback() {
             @Override
@@ -4253,7 +4233,6 @@
         mIsDrawing = true;
         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
 
-        registerFrameDrawingCallbackForBlur();
         addFrameCommitCallbackIfNeeded();
 
         boolean usingAsyncReport = isHardwareEnabled() && mSyncBufferCallback != null;
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 5bc340b..aa8e2ab 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -341,6 +341,18 @@
     int TRANSIT_OLD_TASK_FRAGMENT_CHANGE = 30;
 
     /**
+     * A dream activity is being opened.
+     * @hide
+     */
+    int TRANSIT_OLD_DREAM_ACTIVITY_OPEN = 31;
+
+    /**
+     * A dream activity is being closed.
+     * @hide
+     */
+    int TRANSIT_OLD_DREAM_ACTIVITY_CLOSE = 32;
+
+    /**
      * @hide
      */
     @IntDef(prefix = { "TRANSIT_OLD_" }, value = {
@@ -368,7 +380,9 @@
             TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE,
             TRANSIT_OLD_TASK_FRAGMENT_OPEN,
             TRANSIT_OLD_TASK_FRAGMENT_CLOSE,
-            TRANSIT_OLD_TASK_FRAGMENT_CHANGE
+            TRANSIT_OLD_TASK_FRAGMENT_CHANGE,
+            TRANSIT_OLD_DREAM_ACTIVITY_OPEN,
+            TRANSIT_OLD_DREAM_ACTIVITY_CLOSE
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface TransitionOldType {}
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index 48211a8..1404694 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -16,6 +16,8 @@
 
 package android.window;
 
+import static android.view.WindowManager.transitTypeToString;
+
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration;
@@ -366,7 +368,7 @@
         // String fieldNameToString() { ... }
 
         return "TransitionRequestInfo { " +
-                "type = " + mType + ", " +
+                "type = " + transitTypeToString(mType) + ", " +
                 "triggerTask = " + mTriggerTask + ", " +
                 "remoteTransition = " + mRemoteTransition + ", " +
                 "displayChange = " + mDisplayChange +
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index f0a685e..3fee914 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -245,7 +245,7 @@
                     SystemUiDeviceConfigFlags.IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP,
                     DEFAULT_IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP);
 
-    private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 250;
+    private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 125;
 
     @VisibleForTesting
     int mListViewUpdateDelayMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
diff --git a/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
index 402d7fe..4e1ecc2 100644
--- a/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
+++ b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
@@ -36,6 +36,7 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -232,9 +233,12 @@
         private final ArraySet<BackgroundBlurDrawable> mDrawables = new ArraySet();
         @GuardedBy("mRtLock")
         private final LongSparseArray<ArraySet<Runnable>> mFrameRtUpdates = new LongSparseArray();
+        private long mLastFrameNumber = 0;
+        private BlurRegion[] mLastFrameBlurRegions = null;
         private final ViewRootImpl mViewRoot;
         private BlurRegion[] mTmpBlurRegionsForFrame = new BlurRegion[0];
         private boolean mHasUiUpdates;
+        private ViewTreeObserver.OnPreDrawListener mOnPreDrawListener;
 
         public Aggregator(ViewRootImpl viewRoot) {
             mViewRoot = viewRoot;
@@ -277,6 +281,38 @@
                     Log.d(TAG, "Remove " + drawable);
                 }
             }
+
+            if (mOnPreDrawListener == null && mViewRoot.getView() != null
+                    && hasRegions()) {
+                registerPreDrawListener();
+            }
+        }
+
+        private void registerPreDrawListener() {
+            mOnPreDrawListener = () -> {
+                final boolean hasUiUpdates = hasUpdates();
+
+                if (hasUiUpdates || hasRegions()) {
+                    final BlurRegion[] blurRegionsForNextFrame = getBlurRegionsCopyForRT();
+
+                    mViewRoot.registerRtFrameCallback(frame -> {
+                        synchronized (mRtLock) {
+                            mLastFrameNumber = frame;
+                            mLastFrameBlurRegions = blurRegionsForNextFrame;
+                            handleDispatchBlurTransactionLocked(
+                                    frame, blurRegionsForNextFrame, hasUiUpdates);
+                        }
+                    });
+                }
+                if (!hasRegions() && mViewRoot.getView() != null) {
+                    mViewRoot.getView().getViewTreeObserver()
+                            .removeOnPreDrawListener(mOnPreDrawListener);
+                    mOnPreDrawListener = null;
+                }
+                return true;
+            };
+
+            mViewRoot.getView().getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener);
         }
 
         // Called from a thread pool
@@ -290,7 +326,14 @@
                     mFrameRtUpdates.put(frameNumber, frameRtUpdates);
                 }
                 frameRtUpdates.add(update);
+
+                if (mLastFrameNumber == frameNumber) {
+                    // The transaction for this frame has already been sent, so we have to manually
+                    // trigger sending a transaction here in order to apply this position update
+                    handleDispatchBlurTransactionLocked(frameNumber, mLastFrameBlurRegions, true);
+                }
             }
+
         }
 
         /**
@@ -329,29 +372,27 @@
         /**
          * Called on RenderThread.
          *
-         * @return all blur regions if there are any ui or position updates for this frame,
-         *         null otherwise
+         * @return true if it is necessary to send an update to Sf this frame
          */
+        @GuardedBy("mRtLock")
         @VisibleForTesting
-        public float[][] getBlurRegionsToDispatchToSf(long frameNumber,
-                BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame) {
-            synchronized (mRtLock) {
-                if (!hasUiUpdatesForFrame && (mFrameRtUpdates.size() == 0
-                            || mFrameRtUpdates.keyAt(0) > frameNumber)) {
-                    return null;
-                }
+        public float[][] getBlurRegionsForFrameLocked(long frameNumber,
+                BlurRegion[] blurRegionsForFrame, boolean forceUpdate) {
+            if (!forceUpdate && (mFrameRtUpdates.size() == 0
+                        || mFrameRtUpdates.keyAt(0) > frameNumber)) {
+                return null;
+            }
 
-                // mFrameRtUpdates holds position updates coming from a thread pool span from
-                // RenderThread. At this point, all position updates for frame frameNumber should
-                // have been added to mFrameRtUpdates.
-                // Here, we apply all updates for frames <= frameNumber in case some previous update
-                // has been missed. This also protects mFrameRtUpdates from memory leaks.
-                while (mFrameRtUpdates.size() != 0 && mFrameRtUpdates.keyAt(0) <= frameNumber) {
-                    final ArraySet<Runnable> frameUpdates = mFrameRtUpdates.valueAt(0);
-                    mFrameRtUpdates.removeAt(0);
-                    for (int i = 0; i < frameUpdates.size(); i++) {
-                        frameUpdates.valueAt(i).run();
-                    }
+            // mFrameRtUpdates holds position updates coming from a thread pool span from
+            // RenderThread. At this point, all position updates for frame frameNumber should
+            // have been added to mFrameRtUpdates.
+            // Here, we apply all updates for frames <= frameNumber in case some previous update
+            // has been missed. This also protects mFrameRtUpdates from memory leaks.
+            while (mFrameRtUpdates.size() != 0 && mFrameRtUpdates.keyAt(0) <= frameNumber) {
+                final ArraySet<Runnable> frameUpdates = mFrameRtUpdates.valueAt(0);
+                mFrameRtUpdates.removeAt(0);
+                for (int i = 0; i < frameUpdates.size(); i++) {
+                    frameUpdates.valueAt(i).run();
                 }
             }
 
@@ -370,13 +411,13 @@
         }
 
         /**
-         * Called on RenderThread in FrameDrawingCallback.
-         * Dispatch all blur regions if there are any ui or position updates.
+         * Dispatch all blur regions if there are any ui or position updates for that frame.
          */
-        public void dispatchBlurTransactionIfNeeded(long frameNumber,
-                BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame) {
-            final float[][] blurRegionsArray = getBlurRegionsToDispatchToSf(frameNumber,
-                    blurRegionsForFrame, hasUiUpdatesForFrame);
+        @GuardedBy("mRtLock")
+        private void handleDispatchBlurTransactionLocked(long frameNumber, BlurRegion[] blurRegions,
+                boolean forceUpdate) {
+            float[][] blurRegionsArray =
+                    getBlurRegionsForFrameLocked(frameNumber, blurRegions, forceUpdate);
             if (blurRegionsArray != null) {
                 mViewRoot.dispatchBlurRegions(blurRegionsArray, frameNumber);
             }
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index fd8534d..7fd3a38 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -25,6 +25,7 @@
 import static android.view.WindowManager.TRANSIT_OLD_NONE;
 import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_UNSET;
 import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
 import static android.view.WindowManager.TRANSIT_OPEN;
@@ -261,11 +262,17 @@
         }
         return null;
     }
-
-    /** Load animation by attribute Id from a specific AnimationStyle resource. */
+    /**
+     * Load animation by attribute Id from a specific AnimationStyle resource.
+     *
+     * @param translucent {@code true} if we're sure that the animation is applied on a translucent
+     *                    window container, {@code false} otherwise.
+     * @param transit {@link TransitionOldType} for the app transition of this animation, or
+     *                {@link TransitionOldType#TRANSIT_OLD_UNSET} if app transition type is unknown.
+     */
     @Nullable
-    public Animation loadAnimationAttr(String packageName, int animStyleResId, int animAttr,
-            boolean translucent) {
+    private Animation loadAnimationAttr(String packageName, int animStyleResId, int animAttr,
+            boolean translucent, @TransitionOldType int transit) {
         if (animStyleResId == 0) {
             return null;
         }
@@ -281,6 +288,8 @@
         }
         if (translucent) {
             resId = updateToTranslucentAnimIfNeeded(resId);
+        } else if (transit != TRANSIT_OLD_UNSET) {
+            resId = updateToTranslucentAnimIfNeeded(resId, transit);
         }
         if (ResourceId.isValid(resId)) {
             return loadAnimationSafely(context, resId, mTag);
@@ -288,6 +297,15 @@
         return null;
     }
 
+
+    /** Load animation by attribute Id from a specific AnimationStyle resource. */
+    @Nullable
+    public Animation loadAnimationAttr(String packageName, int animStyleResId, int animAttr,
+            boolean translucent) {
+        return loadAnimationAttr(packageName, animStyleResId, animAttr, translucent,
+                TRANSIT_OLD_UNSET);
+    }
+
     /** Load animation by attribute Id from android package. */
     @Nullable
     public Animation loadDefaultAnimationAttr(int animAttr, boolean translucent) {
@@ -295,6 +313,13 @@
                 translucent);
     }
 
+    /** Load animation by attribute Id from android package. */
+    @Nullable
+    public Animation loadDefaultAnimationAttr(int animAttr, @TransitionOldType int transit) {
+        return loadAnimationAttr(DEFAULT_PACKAGE, mDefaultWindowAnimationStyleResId, animAttr,
+                false /* translucent */, transit);
+    }
+
     @Nullable
     private AttributeCache.Entry getCachedAnimations(LayoutParams lp) {
         if (mDebug) {
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 46b4630..ef8f2db 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -88,7 +88,7 @@
             in int notificationLocation, boolean modifiedBeforeSending);
     void onNotificationSettingsViewed(String key);
     void onNotificationBubbleChanged(String key, boolean isBubble, int flags);
-    void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed, boolean isBubbleSuppressed);
+    void onBubbleMetadataFlagChanged(String key, int flags);
     void hideCurrentInputMethodForBubbles();
     void grantInlineReplyUriPermission(String key, in Uri uri, in UserHandle user, String packageName);
     oneway void clearInlineReplyUriPermissions(String key);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d32acaf..0f328b0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7056,6 +7056,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
 
+        <service android:name="com.android.server.notification.ReviewNotificationPermissionsJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE">
+        </service>
+
         <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
             android:exported="false">
             <intent-filter>
diff --git a/core/res/OWNERS b/core/res/OWNERS
index 95d2712..c54638a 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -45,3 +45,4 @@
 
 # Telephony
 per-file res/values/config_telephony.xml = file:/platform/frameworks/opt/telephony:/OWNERS
+per-file res/xml/sms_short_codes.xml = file:/platform/frameworks/opt/telephony:/OWNERS
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d2f31b0..515ea50 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2422,6 +2422,15 @@
         <!--  When closing the current activity, this is the animation that is
               run on the current activity (which is exiting the screen). -->
         <attr name="activityCloseExitAnimation" format="reference" />
+        <!--  When closing a dream activity, this is the animation that is
+              run on the dream activity (which is exiting the screen). -->
+        <attr name="dreamActivityCloseExitAnimation" format="reference" />
+        <!--  When opening a dream activity, this is the animation that is
+              run on the dream activity (which is entering the screen). -->
+        <attr name="dreamActivityOpenEnterAnimation" format="reference" />
+        <!--  When opening a dream activity, this is the animation that is
+              run on the old activity (which is exiting the screen). -->
+        <attr name="dreamActivityOpenExitAnimation" format="reference" />
         <!--  When opening an activity in a new task, this is the animation that is
               run on the activity of the new task (which is entering the screen). -->
         <attr name="taskOpenEnterAnimation" format="reference" />
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index c07404b..0e3ac9b 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -85,6 +85,9 @@
         <item name="activityOpenExitAnimation">@anim/activity_open_exit</item>
         <item name="activityCloseEnterAnimation">@anim/activity_close_enter</item>
         <item name="activityCloseExitAnimation">@anim/activity_close_exit</item>
+        <item name="dreamActivityCloseExitAnimation">@anim/dream_activity_close_exit</item>
+        <item name="dreamActivityOpenEnterAnimation">@anim/dream_activity_open_enter</item>
+        <item name="dreamActivityOpenExitAnimation">@anim/dream_activity_open_exit</item>
         <item name="taskOpenEnterAnimation">@anim/task_open_enter</item>
         <item name="taskOpenExitAnimation">@anim/task_open_exit</item>
         <item name="launchTaskBehindTargetAnimation">@anim/launch_task_behind_target</item>
diff --git a/core/tests/coretests/src/android/view/BlurAggregatorTest.java b/core/tests/coretests/src/android/view/BlurAggregatorTest.java
index b01f2755..ded925e5 100644
--- a/core/tests/coretests/src/android/view/BlurAggregatorTest.java
+++ b/core/tests/coretests/src/android/view/BlurAggregatorTest.java
@@ -65,7 +65,7 @@
         drawable.setBlurRadius(TEST_BLUR_RADIUS);
         final boolean hasUpdates = mAggregator.hasUpdates();
         final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
-        mAggregator.getBlurRegionsToDispatchToSf(TEST_FRAME_NUMBER, blurRegions, hasUpdates);
+        mAggregator.getBlurRegionsForFrameLocked(TEST_FRAME_NUMBER, blurRegions, hasUpdates);
         return drawable;
     }
 
@@ -154,7 +154,7 @@
         assertEquals(1, blurRegions.length);
 
         mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
-        mAggregator.getBlurRegionsToDispatchToSf(TEST_FRAME_NUMBER, blurRegions,
+        mAggregator.getBlurRegionsForFrameLocked(TEST_FRAME_NUMBER, blurRegions,
                 mAggregator.hasUpdates());
         assertEquals(1, blurRegions[0].rect.left);
         assertEquals(2, blurRegions[0].rect.top);
@@ -169,7 +169,7 @@
         final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
         assertEquals(1, blurRegions.length);
 
-        float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+        float[][] blurRegionsForSf = mAggregator.getBlurRegionsForFrameLocked(
                 TEST_FRAME_NUMBER, blurRegions, hasUpdates);
         assertNull(blurRegionsForSf);
     }
@@ -182,7 +182,7 @@
         final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
         assertEquals(1, blurRegions.length);
 
-        float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+        float[][] blurRegionsForSf = mAggregator.getBlurRegionsForFrameLocked(
                 TEST_FRAME_NUMBER, blurRegions, hasUpdates);
         assertNotNull(blurRegionsForSf);
         assertEquals(1, blurRegionsForSf.length);
@@ -197,7 +197,7 @@
         assertEquals(1, blurRegions.length);
 
         mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
-        float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+        float[][] blurRegionsForSf = mAggregator.getBlurRegionsForFrameLocked(
                 TEST_FRAME_NUMBER, blurRegions, hasUpdates);
         assertNotNull(blurRegionsForSf);
         assertEquals(1, blurRegionsForSf.length);
@@ -216,7 +216,7 @@
         assertEquals(1, blurRegions.length);
 
         mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
-        float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+        float[][] blurRegionsForSf = mAggregator.getBlurRegionsForFrameLocked(
                 TEST_FRAME_NUMBER + 1, blurRegions, hasUpdates);
         assertNotNull(blurRegionsForSf);
         assertEquals(1, blurRegionsForSf.length);
@@ -237,19 +237,19 @@
         assertEquals(2, blurRegions.length);
 
         // Check that an update in one of the drawables triggers a dispatch of all blur regions
-        float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+        float[][] blurRegionsForSf = mAggregator.getBlurRegionsForFrameLocked(
                 TEST_FRAME_NUMBER, blurRegions, hasUpdates);
         assertNotNull(blurRegionsForSf);
         assertEquals(2, blurRegionsForSf.length);
 
         // Check that the Aggregator deleted all position updates for frame TEST_FRAME_NUMBER
-        blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+        blurRegionsForSf = mAggregator.getBlurRegionsForFrameLocked(
                 TEST_FRAME_NUMBER, blurRegions, /* hasUiUpdates= */ false);
         assertNull(blurRegionsForSf);
 
         // Check that a position update triggers a dispatch of all blur regions
         drawable2.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
-        blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+        blurRegionsForSf = mAggregator.getBlurRegionsForFrameLocked(
                 TEST_FRAME_NUMBER + 1, blurRegions, hasUpdates);
         assertNotNull(blurRegionsForSf);
         assertEquals(2, blurRegionsForSf.length);
@@ -292,7 +292,7 @@
         mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
         mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER + 1, 5, 6, 7, 8);
 
-        final float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+        final float[][] blurRegionsForSf = mAggregator.getBlurRegionsForFrameLocked(
                 TEST_FRAME_NUMBER, blurRegions, /* hasUiUpdates= */ false);
         assertNotNull(blurRegionsForSf);
         assertEquals(1, blurRegionsForSf.length);
@@ -303,7 +303,7 @@
         assertEquals(3f, blurRegionsForSf[0][4]);
         assertEquals(4f, blurRegionsForSf[0][5]);
 
-        final float[][] blurRegionsForSfForNextFrame = mAggregator.getBlurRegionsToDispatchToSf(
+        final float[][] blurRegionsForSfForNextFrame = mAggregator.getBlurRegionsForFrameLocked(
                 TEST_FRAME_NUMBER + 1, blurRegions, /* hasUiUpdates= */ false);
         assertNotNull(blurRegionsForSfForNextFrame);
         assertEquals(1, blurRegionsForSfForNextFrame.length);
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 07a3429..9a9ace6 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2065,6 +2065,12 @@
       "group": "WM_DEBUG_IME",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "-57572004": {
+      "message": "applyAnimation: anim=%s animAttr=0x%x transit=%s isEntrance=%b  canCustomizeAppTransition=%b Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+      "at": "com\/android\/server\/wm\/AppTransition.java"
+    },
     "-55185509": {
       "message": "setFocusedTask: taskId=%d touchedActivity=%s",
       "level": "DEBUG",
@@ -4171,6 +4177,12 @@
       "group": "WM_DEBUG_ANIM",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "2079410261": {
+      "message": "applyAnimation:  override requested, but it is prohibited by policy.",
+      "level": "ERROR",
+      "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+      "at": "com\/android\/server\/wm\/AppTransition.java"
+    },
     "2083556954": {
       "message": "Set mOrientationChanging of %s",
       "level": "VERBOSE",
@@ -4224,12 +4236,6 @@
       "level": "VERBOSE",
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotation.java"
-    },
-    "2137411379": {
-      "message": "applyAnimation: anim=%s animAttr=0x%x transit=%s isEntrance=%b Callers=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
-      "at": "com\/android\/server\/wm\/AppTransition.java"
     }
   },
   "groups": {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 82f8a131..faada1a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -777,22 +777,14 @@
         return null;
     }
 
-    private void updateCallbackIfNecessary() {
-        updateCallbackIfNecessary(true /* deferCallbackUntilAllActivitiesCreated */);
-    }
-
     /**
      * Notifies listeners about changes to split states if necessary.
-     *
-     * @param deferCallbackUntilAllActivitiesCreated boolean to indicate whether the split info
-     *                                               callback should be deferred until all the
-     *                                               organized activities have been created.
      */
-    private void updateCallbackIfNecessary(boolean deferCallbackUntilAllActivitiesCreated) {
+    private void updateCallbackIfNecessary() {
         if (mEmbeddingCallback == null) {
             return;
         }
-        if (deferCallbackUntilAllActivitiesCreated && !allActivitiesCreated()) {
+        if (!allActivitiesCreated()) {
             return;
         }
         List<SplitInfo> currentSplitStates = getActiveSplitStates();
@@ -848,9 +840,7 @@
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
             final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
             for (TaskFragmentContainer container : containers) {
-                if (container.getInfo() == null
-                        || container.getInfo().getActivities().size()
-                        != container.collectActivities().size()) {
+                if (!container.taskInfoActivityCountMatchesCreated()) {
                     return false;
                 }
             }
@@ -1035,11 +1025,8 @@
                             && container.getTaskFragmentToken().equals(initialTaskFragmentToken)) {
                         // The onTaskFragmentInfoChanged callback containing this activity has not
                         // reached the client yet, so add the activity to the pending appeared
-                        // activities and send a split info callback to the client before
-                        // {@link Activity#onCreate} is called.
+                        // activities.
                         container.addPendingAppearedActivity(activity);
-                        updateCallbackIfNecessary(
-                                false /* deferCallbackUntilAllActivitiesCreated */);
                         return;
                     }
                 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 35981d3..26bbcbb 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -145,6 +145,18 @@
         return allActivities;
     }
 
+    /**
+     * Checks if the count of activities from the same process in task fragment info corresponds to
+     * the ones created and available on the client side.
+     */
+    boolean taskInfoActivityCountMatchesCreated() {
+        if (mInfo == null) {
+            return false;
+        }
+        return mPendingAppearedActivities.isEmpty()
+                && mInfo.getActivities().size() == collectActivities().size();
+    }
+
     ActivityStack toActivityStack() {
         return new ActivityStack(collectActivities(), isEmpty());
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 227494c..31fc6a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -71,7 +71,7 @@
     private long mLastAccessed;
 
     @Nullable
-    private Bubbles.SuppressionChangedListener mSuppressionListener;
+    private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener;
 
     /** Whether the bubble should show a dot for the notification indicating updated content. */
     private boolean mShowBubbleUpdateDot = true;
@@ -192,13 +192,13 @@
 
     @VisibleForTesting(visibility = PRIVATE)
     public Bubble(@NonNull final BubbleEntry entry,
-            @Nullable final Bubbles.SuppressionChangedListener listener,
+            @Nullable final Bubbles.BubbleMetadataFlagListener listener,
             final Bubbles.PendingIntentCanceledListener intentCancelListener,
             Executor mainExecutor) {
         mKey = entry.getKey();
         mGroupKey = entry.getGroupKey();
         mLocusId = entry.getLocusId();
-        mSuppressionListener = listener;
+        mBubbleMetadataFlagListener = listener;
         mIntentCancelListener = intent -> {
             if (mIntent != null) {
                 mIntent.unregisterCancelListener(mIntentCancelListener);
@@ -606,8 +606,8 @@
             mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
         }
 
-        if (showInShade() != prevShowInShade && mSuppressionListener != null) {
-            mSuppressionListener.onBubbleNotificationSuppressionChange(this);
+        if (showInShade() != prevShowInShade && mBubbleMetadataFlagListener != null) {
+            mBubbleMetadataFlagListener.onBubbleMetadataFlagChanged(this);
         }
     }
 
@@ -626,8 +626,8 @@
         } else {
             mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
         }
-        if (prevSuppressed != suppressBubble && mSuppressionListener != null) {
-            mSuppressionListener.onBubbleNotificationSuppressionChange(this);
+        if (prevSuppressed != suppressBubble && mBubbleMetadataFlagListener != null) {
+            mBubbleMetadataFlagListener.onBubbleMetadataFlagChanged(this);
         }
     }
 
@@ -771,12 +771,17 @@
         return isEnabled(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
     }
 
-    void setShouldAutoExpand(boolean shouldAutoExpand) {
+    @VisibleForTesting
+    public void setShouldAutoExpand(boolean shouldAutoExpand) {
+        boolean prevAutoExpand = shouldAutoExpand();
         if (shouldAutoExpand) {
             enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
         } else {
             disable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
         }
+        if (prevAutoExpand != shouldAutoExpand && mBubbleMetadataFlagListener != null) {
+            mBubbleMetadataFlagListener.onBubbleMetadataFlagChanged(this);
+        }
     }
 
     public void setIsBubble(final boolean isBubble) {
@@ -799,6 +804,10 @@
         return (mFlags & option) != 0;
     }
 
+    public int getFlags() {
+        return mFlags;
+    }
+
     @Override
     public String toString() {
         return "Bubble{" + mKey + '}';
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 806c395..f407bdc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -323,7 +323,7 @@
 
     public void initialize() {
         mBubbleData.setListener(mBubbleDataListener);
-        mBubbleData.setSuppressionChangedListener(this::onBubbleNotificationSuppressionChanged);
+        mBubbleData.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged);
 
         mBubbleData.setPendingIntentCancelledListener(bubble -> {
             if (bubble.getBubbleIntent() == null) {
@@ -554,11 +554,10 @@
     }
 
     @VisibleForTesting
-    public void onBubbleNotificationSuppressionChanged(Bubble bubble) {
+    public void onBubbleMetadataFlagChanged(Bubble bubble) {
         // Make sure NoMan knows suppression state so that anyone querying it can tell.
         try {
-            mBarService.onBubbleNotificationSuppressionChanged(bubble.getKey(),
-                    !bubble.showInShade(), bubble.isSuppressed());
+            mBarService.onBubbleMetadataFlagChanged(bubble.getKey(), bubble.getFlags());
         } catch (RemoteException e) {
             // Bad things have happened
         }
@@ -1038,7 +1037,15 @@
             }
         } else {
             Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);
-            inflateAndAdd(bubble, suppressFlyout, showInShade);
+            if (notif.shouldSuppressNotificationList()) {
+                // If we're suppressing notifs for DND, we don't want the bubbles to randomly
+                // expand when DND turns off so flip the flag.
+                if (bubble.shouldAutoExpand()) {
+                    bubble.setShouldAutoExpand(false);
+                }
+            } else {
+                inflateAndAdd(bubble, suppressFlyout, showInShade);
+            }
         }
     }
 
@@ -1070,7 +1077,8 @@
         }
     }
 
-    private void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) {
+    @VisibleForTesting
+    public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) {
         // shouldBubbleUp checks canBubble & for bubble metadata
         boolean shouldBubble = shouldBubbleUp && canLaunchInTaskView(mContext, entry);
         if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
@@ -1096,7 +1104,8 @@
         }
     }
 
-    private void onRankingUpdated(RankingMap rankingMap,
+    @VisibleForTesting
+    public void onRankingUpdated(RankingMap rankingMap,
             HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) {
         if (mTmpRanking == null) {
             mTmpRanking = new NotificationListenerService.Ranking();
@@ -1107,19 +1116,22 @@
             Pair<BubbleEntry, Boolean> entryData = entryDataByKey.get(key);
             BubbleEntry entry = entryData.first;
             boolean shouldBubbleUp = entryData.second;
-
             if (entry != null && !isCurrentProfile(
                     entry.getStatusBarNotification().getUser().getIdentifier())) {
                 return;
             }
-
+            if (entry != null && (entry.shouldSuppressNotificationList()
+                    || entry.getRanking().isSuspended())) {
+                shouldBubbleUp = false;
+            }
             rankingMap.getRanking(key, mTmpRanking);
-            boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
-            if (isActiveBubble && !mTmpRanking.canBubble()) {
+            boolean isActiveOrInOverflow = mBubbleData.hasAnyBubbleWithKey(key);
+            boolean isActive = mBubbleData.hasBubbleInStackWithKey(key);
+            if (isActiveOrInOverflow && !mTmpRanking.canBubble()) {
                 // If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason.
                 // This means that the app or channel's ability to bubble has been revoked.
                 mBubbleData.dismissBubbleWithKey(key, DISMISS_BLOCKED);
-            } else if (isActiveBubble && (!shouldBubbleUp || entry.getRanking().isSuspended())) {
+            } else if (isActiveOrInOverflow && !shouldBubbleUp) {
                 // If this entry is allowed to bubble, but cannot currently bubble up or is
                 // suspended, dismiss it. This happens when DND is enabled and configured to hide
                 // bubbles, or focus mode is enabled and the app is designated as distracting.
@@ -1127,9 +1139,9 @@
                 // notification, so that the bubble will be re-created if shouldBubbleUp returns
                 // true.
                 mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP);
-            } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) {
+            } else if (entry != null && mTmpRanking.isBubble() && !isActive) {
                 entry.setFlagBubble(true);
-                onEntryUpdated(entry, shouldBubbleUp && !entry.getRanking().isSuspended());
+                onEntryUpdated(entry, shouldBubbleUp);
             }
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index c98c0e6..e4a0fd0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -159,7 +159,7 @@
     private Listener mListener;
 
     @Nullable
-    private Bubbles.SuppressionChangedListener mSuppressionListener;
+    private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener;
     private Bubbles.PendingIntentCanceledListener mCancelledListener;
 
     /**
@@ -190,9 +190,8 @@
         mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow);
     }
 
-    public void setSuppressionChangedListener(
-            Bubbles.SuppressionChangedListener listener) {
-        mSuppressionListener = listener;
+    public void setSuppressionChangedListener(Bubbles.BubbleMetadataFlagListener listener) {
+        mBubbleMetadataFlagListener = listener;
     }
 
     public void setPendingIntentCancelledListener(
@@ -311,7 +310,7 @@
                 bubbleToReturn = mPendingBubbles.get(key);
             } else if (entry != null) {
                 // New bubble
-                bubbleToReturn = new Bubble(entry, mSuppressionListener, mCancelledListener,
+                bubbleToReturn = new Bubble(entry, mBubbleMetadataFlagListener, mCancelledListener,
                         mMainExecutor);
             } else {
                 // Persisted bubble being promoted
@@ -1058,6 +1057,22 @@
         return null;
     }
 
+    /**
+     * Get a pending bubble with given notification <code>key</code>
+     *
+     * @param key notification key
+     * @return bubble that matches or null
+     */
+    @VisibleForTesting(visibility = PRIVATE)
+    public Bubble getPendingBubbleWithKey(String key) {
+        for (Bubble b : mPendingBubbles.values()) {
+            if (b.getKey().equals(key)) {
+                return b;
+            }
+        }
+        return null;
+    }
+
     @VisibleForTesting(visibility = PRIVATE)
     void setTimeSource(TimeSource timeSource) {
         mTimeSource = timeSource;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 2b2a2f7..c7db8d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -263,10 +263,10 @@
         void onBubbleExpandChanged(boolean isExpanding, String key);
     }
 
-    /** Listener to be notified when the flags for notification or bubble suppression changes.*/
-    interface SuppressionChangedListener {
-        /** Called when the notification suppression state of a bubble changes. */
-        void onBubbleNotificationSuppressionChange(Bubble bubble);
+    /** Listener to be notified when the flags on BubbleMetadata have changed. */
+    interface BubbleMetadataFlagListener {
+        /** Called when the flags on BubbleMetadata have changed for the provided bubble. */
+        void onBubbleMetadataFlagChanged(Bubble bubble);
     }
 
     /** Listener to be notified when a pending intent has been canceled for a bubble. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 709be6b..ed13782 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -65,7 +65,6 @@
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
 import com.android.wm.shell.pip.phone.PipAppOpsListener;
 import com.android.wm.shell.pip.phone.PipController;
-import com.android.wm.shell.pip.phone.PipKeepClearAlgorithm;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.recents.RecentTasksController;
@@ -213,8 +212,7 @@
     @Provides
     static Optional<Pip> providePip(Context context, DisplayController displayController,
             PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
-            PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
+            PipBoundsState pipBoundsState, PipMediaController pipMediaController,
             PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
             PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
             WindowManagerShellWrapper windowManagerShellWrapper,
@@ -223,8 +221,8 @@
             Optional<OneHandedController> oneHandedController,
             @ShellMainThread ShellExecutor mainExecutor) {
         return Optional.ofNullable(PipController.create(context, displayController,
-                pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState,
-                pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+                pipAppOpsListener, pipBoundsAlgorithm, pipBoundsState,
+                pipMediaController, phonePipMenuController, pipTaskOrganizer,
                 pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
                 taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor));
     }
@@ -243,12 +241,6 @@
 
     @WMSingleton
     @Provides
-    static PipKeepClearAlgorithm providePipKeepClearAlgorithm() {
-        return new PipKeepClearAlgorithm();
-    }
-
-    @WMSingleton
-    @Provides
     static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
             PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
         return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index ab3e38c..baa1507 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -109,9 +109,7 @@
     private PipAppOpsListener mAppOpsListener;
     private PipMediaController mMediaController;
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
-    private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
     private PipBoundsState mPipBoundsState;
-    private PipMotionHelper mPipMotionHelper;
     private PipTouchHandler mTouchHandler;
     private PipTransitionController mPipTransitionController;
     private TaskStackListenerImpl mTaskStackListener;
@@ -247,10 +245,6 @@
                         Set<Rect> unrestricted) {
                     if (mPipBoundsState.getDisplayId() == displayId) {
                         mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
-                        mPipMotionHelper.moveToBounds(mPipKeepClearAlgorithm.adjust(
-                                mPipBoundsState.getBounds(),
-                                mPipBoundsState.getRestrictedKeepClearAreas(),
-                                mPipBoundsState.getUnrestrictedKeepClearAreas()));
                     }
                 }
             };
@@ -289,8 +283,7 @@
     @Nullable
     public static Pip create(Context context, DisplayController displayController,
             PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
-            PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
+            PipBoundsState pipBoundsState, PipMediaController pipMediaController,
             PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
             PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
             WindowManagerShellWrapper windowManagerShellWrapper,
@@ -305,7 +298,7 @@
         }
 
         return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
-                pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController,
+                pipBoundsState, pipMediaController,
                 phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController,
                 windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
                 oneHandedController, mainExecutor)
@@ -316,9 +309,7 @@
             DisplayController displayController,
             PipAppOpsListener pipAppOpsListener,
             PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipKeepClearAlgorithm pipKeepClearAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
-            PipMotionHelper pipMotionHelper,
             PipMediaController pipMediaController,
             PhonePipMenuController phonePipMenuController,
             PipTaskOrganizer pipTaskOrganizer,
@@ -341,9 +332,7 @@
         mWindowManagerShellWrapper = windowManagerShellWrapper;
         mDisplayController = displayController;
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
-        mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
         mPipBoundsState = pipBoundsState;
-        mPipMotionHelper = pipMotionHelper;
         mPipTaskOrganizer = pipTaskOrganizer;
         mMainExecutor = mainExecutor;
         mMediaController = pipMediaController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java
deleted file mode 100644
index a83258f..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip.phone;
-
-import android.graphics.Rect;
-
-import java.util.Set;
-
-/**
- * Calculates the adjusted position that does not occlude keep clear areas.
- */
-public class PipKeepClearAlgorithm {
-
-    /** Returns a new {@code Rect} that does not occlude the provided keep clear areas. */
-    public Rect adjust(Rect defaultBounds, Set<Rect> restrictedKeepClearAreas,
-            Set<Rect> unrestrictedKeepClearAreas) {
-        if (restrictedKeepClearAreas.isEmpty()) {
-            return defaultBounds;
-        }
-        // TODO(b/183746978): implement the adjustment algorithm
-        // naively check if areas intersect, an if so move PiP upwards
-        Rect outBounds = new Rect(defaultBounds);
-        for (Rect r : restrictedKeepClearAreas) {
-            if (r.intersect(outBounds)) {
-                outBounds.offset(0, r.top - outBounds.bottom);
-            }
-        }
-        return outBounds;
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl
deleted file mode 100644
index 45f6d3c..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.window.RemoteTransition;
-
-import com.android.wm.shell.stagesplit.ISplitScreenListener;
-
-/**
- * Interface that is exposed to remote callers to manipulate the splitscreen feature.
- */
-interface ISplitScreen {
-
-    /**
-     * Registers a split screen listener.
-     */
-    oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1;
-
-    /**
-     * Unregisters a split screen listener.
-     */
-    oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
-
-    /**
-     * Hides the side-stage if it is currently visible.
-     */
-    oneway void setSideStageVisibility(boolean visible) = 3;
-
-    /**
-     * Removes a task from the side stage.
-     */
-    oneway void removeFromSideStage(int taskId) = 4;
-
-    /**
-     * Removes the split-screen stages and leaving indicated task to top. Passing INVALID_TASK_ID
-     * to indicate leaving no top task after leaving split-screen.
-     */
-    oneway void exitSplitScreen(int toTopTaskId) = 5;
-
-    /**
-     * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible.
-     */
-    oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6;
-
-    /**
-     * Starts a task in a stage.
-     */
-    oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7;
-
-    /**
-     * Starts a shortcut in a stage.
-     */
-    oneway void startShortcut(String packageName, String shortcutId, int stage, int position,
-            in Bundle options, in UserHandle user) = 8;
-
-    /**
-     * Starts an activity in a stage.
-     */
-    oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage,
-            int position, in Bundle options) = 9;
-
-    /**
-     * Starts tasks simultaneously in one transition.
-     */
-    oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
-            in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10;
-
-    /**
-     * Version of startTasks using legacy transition system.
-     */
-     oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
-                            int sideTaskId, in Bundle sideOptions, int sidePosition,
-                            in RemoteAnimationAdapter adapter) = 11;
-
-    /**
-     * Blocking call that notifies and gets additional split-screen targets when entering
-     * recents (for example: the dividerBar).
-     * @param cancel is true if leaving recents back to split (eg. the gesture was cancelled).
-     * @param appTargets apps that will be re-parented to display area
-     */
-    RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
-                                                   in RemoteAnimationTarget[] appTargets) = 12;
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl
deleted file mode 100644
index 46e4299..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-/**
- * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
- */
-oneway interface ISplitScreenListener {
-
-    /**
-     * Called when the stage position changes.
-     */
-    void onStagePositionChanged(int stage, int position);
-
-    /**
-     * Called when a task changes stages.
-     */
-    void onTaskStageChanged(int taskId, int stage, boolean visible);
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java
deleted file mode 100644
index 83855be..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java
+++ /dev/null
@@ -1,104 +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.wm.shell.stagesplit;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.view.SurfaceSession;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-/**
- * Main stage for split-screen mode. When split-screen is active all standard activity types launch
- * on the main stage, except for task that are explicitly pinned to the {@link SideStage}.
- * @see StageCoordinator
- */
-class MainStage extends StageTaskListener {
-    private static final String TAG = MainStage.class.getSimpleName();
-
-    private boolean mIsActive = false;
-
-    MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
-            StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession,
-            @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
-        super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
-                stageTaskUnfoldController);
-    }
-
-    boolean isActive() {
-        return mIsActive;
-    }
-
-    void activate(Rect rootBounds, WindowContainerTransaction wct) {
-        if (mIsActive) return;
-
-        final WindowContainerToken rootToken = mRootTaskInfo.token;
-        wct.setBounds(rootToken, rootBounds)
-                .setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW)
-                .setLaunchRoot(
-                        rootToken,
-                        CONTROLLED_WINDOWING_MODES,
-                        CONTROLLED_ACTIVITY_TYPES)
-                .reparentTasks(
-                        null /* currentParent */,
-                        rootToken,
-                        CONTROLLED_WINDOWING_MODES,
-                        CONTROLLED_ACTIVITY_TYPES,
-                        true /* onTop */)
-                // Moving the root task to top after the child tasks were re-parented , or the root
-                // task cannot be visible and focused.
-                .reorder(rootToken, true /* onTop */);
-
-        mIsActive = true;
-    }
-
-    void deactivate(WindowContainerTransaction wct) {
-        deactivate(wct, false /* toTop */);
-    }
-
-    void deactivate(WindowContainerTransaction wct, boolean toTop) {
-        if (!mIsActive) return;
-        mIsActive = false;
-
-        if (mRootTaskInfo == null) return;
-        final WindowContainerToken rootToken = mRootTaskInfo.token;
-        wct.setLaunchRoot(
-                        rootToken,
-                        null,
-                        null)
-                .reparentTasks(
-                        rootToken,
-                        null /* newParent */,
-                        CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
-                        CONTROLLED_ACTIVITY_TYPES,
-                        toTop)
-                // We want this re-order to the bottom regardless since we are re-parenting
-                // all its tasks.
-                .reorder(rootToken, false /* onTop */);
-    }
-
-    void updateConfiguration(int windowingMode, Rect bounds, WindowContainerTransaction wct) {
-        wct.setBounds(mRootTaskInfo.token, bounds)
-                .setWindowingMode(mRootTaskInfo.token, windowingMode);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS
deleted file mode 100644
index 264e88f..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# WM shell sub-modules stagesplit owner
-chenghsiuchang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java
deleted file mode 100644
index 8fbad52..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.Binder;
-import android.view.IWindow;
-import android.view.InsetsSource;
-import android.view.InsetsState;
-import android.view.LayoutInflater;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.WindowlessWindowManager;
-import android.widget.FrameLayout;
-
-import com.android.wm.shell.R;
-
-/**
- * Handles drawing outline of the bounds of provided root surface. The outline will be drown with
- * the consideration of display insets like status bar, navigation bar and display cutout.
- */
-class OutlineManager extends WindowlessWindowManager {
-    private static final String WINDOW_NAME = "SplitOutlineLayer";
-    private final Context mContext;
-    private final Rect mRootBounds = new Rect();
-    private final Rect mTempRect = new Rect();
-    private final Rect mLastOutlineBounds = new Rect();
-    private final InsetsState mInsetsState = new InsetsState();
-    private final int mExpandedTaskBarHeight;
-    private OutlineView mOutlineView;
-    private SurfaceControlViewHost mViewHost;
-    private SurfaceControl mHostLeash;
-    private SurfaceControl mLeash;
-
-    OutlineManager(Context context, Configuration configuration) {
-        super(configuration, null /* rootSurface */, null /* hostInputToken */);
-        mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
-                null /* options */);
-        mExpandedTaskBarHeight = mContext.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.taskbar_frame_height);
-    }
-
-    @Override
-    protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
-        b.setParent(mHostLeash);
-    }
-
-    void inflate(SurfaceControl rootLeash, Rect rootBounds) {
-        if (mLeash != null || mViewHost != null) return;
-
-        mHostLeash = rootLeash;
-        mRootBounds.set(rootBounds);
-        mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
-
-        final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(mContext)
-                .inflate(R.layout.split_outline, null);
-        mOutlineView = rootLayout.findViewById(R.id.split_outline);
-
-        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
-                0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
-                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
-        lp.width = mRootBounds.width();
-        lp.height = mRootBounds.height();
-        lp.token = new Binder();
-        lp.setTitle(WINDOW_NAME);
-        lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
-        // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
-        //  TRUSTED_OVERLAY for windowless window without input channel.
-        mViewHost.setView(rootLayout, lp);
-        mLeash = getSurfaceControl(mViewHost.getWindowToken());
-
-        drawOutline();
-    }
-
-    void release() {
-        if (mViewHost != null) {
-            mViewHost.release();
-            mViewHost = null;
-        }
-        mRootBounds.setEmpty();
-        mLastOutlineBounds.setEmpty();
-        mOutlineView = null;
-        mHostLeash = null;
-        mLeash = null;
-    }
-
-    @Nullable
-    SurfaceControl getOutlineLeash() {
-        return mLeash;
-    }
-
-    void setVisibility(boolean visible) {
-        if (mOutlineView != null) {
-            mOutlineView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
-        }
-    }
-
-    void setRootBounds(Rect rootBounds) {
-        if (mViewHost == null || mViewHost.getView() == null) {
-            return;
-        }
-
-        if (!mRootBounds.equals(rootBounds)) {
-            WindowManager.LayoutParams lp =
-                    (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
-            lp.width = rootBounds.width();
-            lp.height = rootBounds.height();
-            mViewHost.relayout(lp);
-            mRootBounds.set(rootBounds);
-            drawOutline();
-        }
-    }
-
-    void onInsetsChanged(InsetsState insetsState) {
-        if (!mInsetsState.equals(insetsState)) {
-            mInsetsState.set(insetsState);
-            drawOutline();
-        }
-    }
-
-    private void computeOutlineBounds(Rect rootBounds, InsetsState insetsState, Rect outBounds) {
-        outBounds.set(rootBounds);
-        final InsetsSource taskBarInsetsSource =
-                insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
-        // Only insets the divider bar with task bar when it's expanded so that the rounded corners
-        // will be drawn against task bar.
-        if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
-            outBounds.inset(taskBarInsetsSource.calculateVisibleInsets(outBounds));
-        }
-
-        // Offset the coordinate from screen based to surface based.
-        outBounds.offset(-rootBounds.left, -rootBounds.top);
-    }
-
-    void drawOutline() {
-        if (mOutlineView == null) {
-            return;
-        }
-
-        computeOutlineBounds(mRootBounds, mInsetsState, mTempRect);
-        if (mTempRect.equals(mLastOutlineBounds)) {
-            return;
-        }
-
-        ViewGroup.MarginLayoutParams lp =
-                (ViewGroup.MarginLayoutParams) mOutlineView.getLayoutParams();
-        lp.leftMargin = mTempRect.left;
-        lp.topMargin = mTempRect.top;
-        lp.width = mTempRect.width();
-        lp.height = mTempRect.height();
-        mOutlineView.setLayoutParams(lp);
-        mLastOutlineBounds.set(mTempRect);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java
deleted file mode 100644
index 92b1381..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
-import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
-import static android.view.RoundedCorner.POSITION_TOP_LEFT;
-import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.util.AttributeSet;
-import android.view.RoundedCorner;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.R;
-
-/** View for drawing split outline. */
-public class OutlineView extends View {
-    private final Paint mPaint = new Paint();
-    private final Path mPath = new Path();
-    private final float[] mRadii = new float[8];
-
-    public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-        mPaint.setStyle(Paint.Style.STROKE);
-        mPaint.setStrokeWidth(
-                getResources().getDimension(R.dimen.accessibility_focus_highlight_stroke_width));
-        mPaint.setColor(getResources().getColor(R.color.system_accent1_100, null));
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        // TODO(b/200850654): match the screen corners with the actual display decor.
-        mRadii[0] = mRadii[1] = getCornerRadius(POSITION_TOP_LEFT);
-        mRadii[2] = mRadii[3] = getCornerRadius(POSITION_TOP_RIGHT);
-        mRadii[4] = mRadii[5] = getCornerRadius(POSITION_BOTTOM_RIGHT);
-        mRadii[6] = mRadii[7] = getCornerRadius(POSITION_BOTTOM_LEFT);
-    }
-
-    private int getCornerRadius(@RoundedCorner.Position int position) {
-        final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position);
-        return roundedCorner == null ? 0 : roundedCorner.getRadius();
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        if (changed) {
-            mPath.reset();
-            mPath.addRoundRect(0, 0, getWidth(), getHeight(), mRadii, Path.Direction.CW);
-        }
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        canvas.drawPath(mPath, mPaint);
-    }
-
-    @Override
-    public boolean hasOverlappingRendering() {
-        return false;
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java
deleted file mode 100644
index 55c4f3a..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java
+++ /dev/null
@@ -1,144 +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.wm.shell.stagesplit;
-
-import android.annotation.CallSuper;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.graphics.Rect;
-import android.view.InsetsSourceControl;
-import android.view.InsetsState;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-/**
- * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
- * here. All other task are launch in the {@link MainStage}.
- *
- * @see StageCoordinator
- */
-class SideStage extends StageTaskListener implements
-        DisplayInsetsController.OnInsetsChangedListener {
-    private static final String TAG = SideStage.class.getSimpleName();
-    private final Context mContext;
-    private OutlineManager mOutlineManager;
-
-    SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
-            StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession,
-            @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
-        super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
-                stageTaskUnfoldController);
-        mContext = context;
-    }
-
-    void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,
-            WindowContainerTransaction wct) {
-        final WindowContainerToken rootToken = mRootTaskInfo.token;
-        wct.setBounds(rootToken, rootBounds)
-                .reparent(task.token, rootToken, true /* onTop*/)
-                // Moving the root task to top after the child tasks were reparented , or the root
-                // task cannot be visible and focused.
-                .reorder(rootToken, true /* onTop */);
-    }
-
-    boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
-        // No matter if the root task is empty or not, moving the root to bottom because it no
-        // longer preserves visible child task.
-        wct.reorder(mRootTaskInfo.token, false /* onTop */);
-        if (mChildrenTaskInfo.size() == 0) return false;
-        wct.reparentTasks(
-                mRootTaskInfo.token,
-                null /* newParent */,
-                CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
-                CONTROLLED_ACTIVITY_TYPES,
-                toTop);
-        return true;
-    }
-
-    boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
-        final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
-        if (task == null) return false;
-        wct.reparent(task.token, newParent, false /* onTop */);
-        return true;
-    }
-
-    @Nullable
-    public SurfaceControl getOutlineLeash() {
-        return mOutlineManager.getOutlineLeash();
-    }
-
-    @Override
-    @CallSuper
-    public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
-        super.onTaskAppeared(taskInfo, leash);
-        if (isRootTask(taskInfo)) {
-            mOutlineManager = new OutlineManager(mContext, taskInfo.configuration);
-            enableOutline(true);
-        }
-    }
-
-    @Override
-    @CallSuper
-    public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
-        super.onTaskInfoChanged(taskInfo);
-        if (isRootTask(taskInfo)) {
-            mOutlineManager.setRootBounds(taskInfo.configuration.windowConfiguration.getBounds());
-        }
-    }
-
-    private boolean isRootTask(ActivityManager.RunningTaskInfo taskInfo) {
-        return mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId;
-    }
-
-    void enableOutline(boolean enable) {
-        if (mOutlineManager == null) {
-            return;
-        }
-
-        if (enable) {
-            if (mRootTaskInfo != null) {
-                mOutlineManager.inflate(mRootLeash,
-                        mRootTaskInfo.configuration.windowConfiguration.getBounds());
-            }
-        } else {
-            mOutlineManager.release();
-        }
-    }
-
-    void setOutlineVisibility(boolean visible) {
-        mOutlineManager.setVisibility(visible);
-    }
-
-    @Override
-    public void insetsChanged(InsetsState insetsState) {
-        mOutlineManager.onInsetsChanged(insetsState);
-    }
-
-    @Override
-    public void insetsControlChanged(InsetsState insetsState,
-            InsetsSourceControl[] activeControls) {
-        insetsChanged(insetsState);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java
deleted file mode 100644
index c5d2312..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java
+++ /dev/null
@@ -1,99 +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.wm.shell.stagesplit;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
-
-import java.util.concurrent.Executor;
-
-/**
- * Interface to engage split-screen feature.
- * TODO: Figure out which of these are actually needed outside of the Shell
- */
-@ExternalThread
-public interface SplitScreen {
-    /**
-     * Stage type isn't specified normally meaning to use what ever the default is.
-     * E.g. exit split-screen and launch the app in fullscreen.
-     */
-    int STAGE_TYPE_UNDEFINED = -1;
-    /**
-     * The main stage type.
-     * @see MainStage
-     */
-    int STAGE_TYPE_MAIN = 0;
-
-    /**
-     * The side stage type.
-     * @see SideStage
-     */
-    int STAGE_TYPE_SIDE = 1;
-
-    @IntDef(prefix = { "STAGE_TYPE_" }, value = {
-            STAGE_TYPE_UNDEFINED,
-            STAGE_TYPE_MAIN,
-            STAGE_TYPE_SIDE
-    })
-    @interface StageType {}
-
-    /** Callback interface for listening to changes in a split-screen stage. */
-    interface SplitScreenListener {
-        default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {}
-        default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {}
-        default void onSplitVisibilityChanged(boolean visible) {}
-    }
-
-    /** Registers listener that gets split screen callback. */
-    void registerSplitScreenListener(@NonNull SplitScreenListener listener,
-            @NonNull Executor executor);
-
-    /** Unregisters listener that gets split screen callback. */
-    void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
-
-    /**
-     * Returns a binder that can be passed to an external process to manipulate SplitScreen.
-     */
-    default ISplitScreen createExternalInterface() {
-        return null;
-    }
-
-    /**
-     * Called when the keyguard occluded state changes.
-     * @param occluded Indicates if the keyguard is now occluded.
-     */
-    void onKeyguardOccludedChanged(boolean occluded);
-
-    /**
-     * Called when the visibility of the keyguard changes.
-     * @param showing Indicates if the keyguard is now visible.
-     */
-    void onKeyguardVisibilityChanged(boolean showing);
-
-    /** Get a string representation of a stage type */
-    static String stageTypeToString(@StageType int stage) {
-        switch (stage) {
-            case STAGE_TYPE_UNDEFINED: return "UNDEFINED";
-            case STAGE_TYPE_MAIN: return "MAIN";
-            case STAGE_TYPE_SIDE: return "SIDE";
-            default: return "UNKNOWN(" + stage + ")";
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java
deleted file mode 100644
index 0717405..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java
+++ /dev/null
@@ -1,595 +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.wm.shell.stagesplit;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
-
-import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
-
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Slog;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.view.WindowManager;
-import android.window.RemoteTransition;
-import android.window.WindowContainerTransaction;
-
-import androidx.annotation.BinderThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.logging.InstanceId;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.RemoteCallable;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
-import com.android.wm.shell.draganddrop.DragAndDropPolicy;
-import com.android.wm.shell.transition.LegacyTransitions;
-import com.android.wm.shell.transition.Transitions;
-
-import java.io.PrintWriter;
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
-import javax.inject.Provider;
-
-/**
- * Class manages split-screen multitasking mode and implements the main interface
- * {@link SplitScreen}.
- * @see StageCoordinator
- */
-// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
-public class SplitScreenController implements DragAndDropPolicy.Starter,
-        RemoteCallable<SplitScreenController> {
-    private static final String TAG = SplitScreenController.class.getSimpleName();
-
-    private final ShellTaskOrganizer mTaskOrganizer;
-    private final SyncTransactionQueue mSyncQueue;
-    private final Context mContext;
-    private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
-    private final ShellExecutor mMainExecutor;
-    private final SplitScreenImpl mImpl = new SplitScreenImpl();
-    private final DisplayImeController mDisplayImeController;
-    private final DisplayInsetsController mDisplayInsetsController;
-    private final Transitions mTransitions;
-    private final TransactionPool mTransactionPool;
-    private final SplitscreenEventLogger mLogger;
-    private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
-
-    private StageCoordinator mStageCoordinator;
-
-    public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
-            SyncTransactionQueue syncQueue, Context context,
-            RootTaskDisplayAreaOrganizer rootTDAOrganizer,
-            ShellExecutor mainExecutor, DisplayImeController displayImeController,
-            DisplayInsetsController displayInsetsController,
-            Transitions transitions, TransactionPool transactionPool,
-            Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
-        mTaskOrganizer = shellTaskOrganizer;
-        mSyncQueue = syncQueue;
-        mContext = context;
-        mRootTDAOrganizer = rootTDAOrganizer;
-        mMainExecutor = mainExecutor;
-        mDisplayImeController = displayImeController;
-        mDisplayInsetsController = displayInsetsController;
-        mTransitions = transitions;
-        mTransactionPool = transactionPool;
-        mUnfoldControllerProvider = unfoldControllerProvider;
-        mLogger = new SplitscreenEventLogger();
-    }
-
-    public SplitScreen asSplitScreen() {
-        return mImpl;
-    }
-
-    @Override
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    public ShellExecutor getRemoteCallExecutor() {
-        return mMainExecutor;
-    }
-
-    public void onOrganizerRegistered() {
-        if (mStageCoordinator == null) {
-            // TODO: Multi-display
-            mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
-                    mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
-                    mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
-                    mUnfoldControllerProvider);
-        }
-    }
-
-    public boolean isSplitScreenVisible() {
-        return mStageCoordinator.isSplitScreenVisible();
-    }
-
-    public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
-        final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
-        if (task == null) {
-            throw new IllegalArgumentException("Unknown taskId" + taskId);
-        }
-        return moveToSideStage(task, sideStagePosition);
-    }
-
-    public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
-            @SplitPosition int sideStagePosition) {
-        return mStageCoordinator.moveToSideStage(task, sideStagePosition);
-    }
-
-    public boolean removeFromSideStage(int taskId) {
-        return mStageCoordinator.removeFromSideStage(taskId);
-    }
-
-    public void setSideStageOutline(boolean enable) {
-        mStageCoordinator.setSideStageOutline(enable);
-    }
-
-    public void setSideStagePosition(@SplitPosition int sideStagePosition) {
-        mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
-    }
-
-    public void setSideStageVisibility(boolean visible) {
-        mStageCoordinator.setSideStageVisibility(visible);
-    }
-
-    public void enterSplitScreen(int taskId, boolean leftOrTop) {
-        moveToSideStage(taskId,
-                leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
-    }
-
-    public void exitSplitScreen(int toTopTaskId, int exitReason) {
-        mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
-    }
-
-    public void onKeyguardOccludedChanged(boolean occluded) {
-        mStageCoordinator.onKeyguardOccludedChanged(occluded);
-    }
-
-    public void onKeyguardVisibilityChanged(boolean showing) {
-        mStageCoordinator.onKeyguardVisibilityChanged(showing);
-    }
-
-    public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
-        mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
-    }
-
-    public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
-        mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds);
-    }
-
-    public void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
-        mStageCoordinator.registerSplitScreenListener(listener);
-    }
-
-    public void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
-        mStageCoordinator.unregisterSplitScreenListener(listener);
-    }
-
-    public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
-        options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
-                null /* wct */);
-
-        try {
-            ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to launch task", e);
-        }
-    }
-
-    public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
-            @Nullable Bundle options, UserHandle user) {
-        options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
-                null /* wct */);
-
-        try {
-            LauncherApps launcherApps =
-                    mContext.getSystemService(LauncherApps.class);
-            launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
-                    options, user);
-        } catch (ActivityNotFoundException e) {
-            Slog.e(TAG, "Failed to launch shortcut", e);
-        }
-    }
-
-    public void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
-            @Nullable Bundle options) {
-        if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
-            startIntentLegacy(intent, fillInIntent, position, options);
-            return;
-        }
-        mStageCoordinator.startIntent(intent, fillInIntent, STAGE_TYPE_UNDEFINED, position, options,
-                null /* remote */);
-    }
-
-    private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
-            @SplitPosition int position, @Nullable Bundle options) {
-        LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
-            @Override
-            public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
-                    RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
-                    IRemoteAnimationFinishedCallback finishedCallback,
-                    SurfaceControl.Transaction t) {
-                mStageCoordinator.updateSurfaceBounds(null /* layout */, t,
-                        false /* applyResizingOffset */);
-
-                if (apps != null) {
-                    for (int i = 0; i < apps.length; ++i) {
-                        if (apps[i].mode == MODE_OPENING) {
-                            t.show(apps[i].leash);
-                        }
-                    }
-                }
-
-                t.apply();
-                if (finishedCallback != null) {
-                    try {
-                        finishedCallback.onAnimationFinished();
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "Error finishing legacy transition: ", e);
-                    }
-                }
-            }
-        };
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
-        wct.sendPendingIntent(intent, fillInIntent, options);
-        mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
-    }
-
-    RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) {
-        if (!isSplitScreenVisible()) return null;
-        final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
-                .setContainerLayer()
-                .setName("RecentsAnimationSplitTasks")
-                .setHidden(false)
-                .setCallsite("SplitScreenController#onGoingtoRecentsLegacy");
-        mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder);
-        SurfaceControl sc = builder.build();
-        SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
-
-        // Ensure that we order these in the parent in the right z-order as their previous order
-        Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex);
-        int layer = 1;
-        for (RemoteAnimationTarget appTarget : apps) {
-            transaction.reparent(appTarget.leash, sc);
-            transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,
-                    appTarget.screenSpaceBounds.top);
-            transaction.setLayer(appTarget.leash, layer++);
-        }
-        transaction.apply();
-        transaction.close();
-        return new RemoteAnimationTarget[]{
-                mStageCoordinator.getDividerBarLegacyTarget(),
-                mStageCoordinator.getOutlineLegacyTarget()};
-    }
-
-    /**
-     * Sets drag info to be logged when splitscreen is entered.
-     */
-    public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
-        mStageCoordinator.logOnDroppedToSplit(position, dragSessionId);
-    }
-
-    public void dump(@NonNull PrintWriter pw, String prefix) {
-        pw.println(prefix + TAG);
-        if (mStageCoordinator != null) {
-            mStageCoordinator.dump(pw, prefix);
-        }
-    }
-
-    /**
-     * The interface for calls from outside the Shell, within the host process.
-     */
-    @ExternalThread
-    private class SplitScreenImpl implements SplitScreen {
-        private ISplitScreenImpl mISplitScreen;
-        private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
-        private final SplitScreenListener mListener = new SplitScreenListener() {
-            @Override
-            public void onStagePositionChanged(int stage, int position) {
-                for (int i = 0; i < mExecutors.size(); i++) {
-                    final int index = i;
-                    mExecutors.valueAt(index).execute(() -> {
-                        mExecutors.keyAt(index).onStagePositionChanged(stage, position);
-                    });
-                }
-            }
-
-            @Override
-            public void onTaskStageChanged(int taskId, int stage, boolean visible) {
-                for (int i = 0; i < mExecutors.size(); i++) {
-                    final int index = i;
-                    mExecutors.valueAt(index).execute(() -> {
-                        mExecutors.keyAt(index).onTaskStageChanged(taskId, stage, visible);
-                    });
-                }
-            }
-
-            @Override
-            public void onSplitVisibilityChanged(boolean visible) {
-                for (int i = 0; i < mExecutors.size(); i++) {
-                    final int index = i;
-                    mExecutors.valueAt(index).execute(() -> {
-                        mExecutors.keyAt(index).onSplitVisibilityChanged(visible);
-                    });
-                }
-            }
-        };
-
-        @Override
-        public ISplitScreen createExternalInterface() {
-            if (mISplitScreen != null) {
-                mISplitScreen.invalidate();
-            }
-            mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
-            return mISplitScreen;
-        }
-
-        @Override
-        public void onKeyguardOccludedChanged(boolean occluded) {
-            mMainExecutor.execute(() -> {
-                SplitScreenController.this.onKeyguardOccludedChanged(occluded);
-            });
-        }
-
-        @Override
-        public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
-            if (mExecutors.containsKey(listener)) return;
-
-            mMainExecutor.execute(() -> {
-                if (mExecutors.size() == 0) {
-                    SplitScreenController.this.registerSplitScreenListener(mListener);
-                }
-
-                mExecutors.put(listener, executor);
-            });
-
-            executor.execute(() -> {
-                mStageCoordinator.sendStatusToListener(listener);
-            });
-        }
-
-        @Override
-        public void unregisterSplitScreenListener(SplitScreenListener listener) {
-            mMainExecutor.execute(() -> {
-                mExecutors.remove(listener);
-
-                if (mExecutors.size() == 0) {
-                    SplitScreenController.this.unregisterSplitScreenListener(mListener);
-                }
-            });
-        }
-
-        @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
-            mMainExecutor.execute(() -> {
-                SplitScreenController.this.onKeyguardVisibilityChanged(showing);
-            });
-        }
-    }
-
-    /**
-     * The interface for calls from outside the host process.
-     */
-    @BinderThread
-    private static class ISplitScreenImpl extends ISplitScreen.Stub {
-        private SplitScreenController mController;
-        private ISplitScreenListener mListener;
-        private final SplitScreen.SplitScreenListener mSplitScreenListener =
-                new SplitScreen.SplitScreenListener() {
-                    @Override
-                    public void onStagePositionChanged(int stage, int position) {
-                        try {
-                            if (mListener != null) {
-                                mListener.onStagePositionChanged(stage, position);
-                            }
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "onStagePositionChanged", e);
-                        }
-                    }
-
-                    @Override
-                    public void onTaskStageChanged(int taskId, int stage, boolean visible) {
-                        try {
-                            if (mListener != null) {
-                                mListener.onTaskStageChanged(taskId, stage, visible);
-                            }
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "onTaskStageChanged", e);
-                        }
-                    }
-                };
-        private final IBinder.DeathRecipient mListenerDeathRecipient =
-                new IBinder.DeathRecipient() {
-                    @Override
-                    @BinderThread
-                    public void binderDied() {
-                        final SplitScreenController controller = mController;
-                        controller.getRemoteCallExecutor().execute(() -> {
-                            mListener = null;
-                            controller.unregisterSplitScreenListener(mSplitScreenListener);
-                        });
-                    }
-                };
-
-        public ISplitScreenImpl(SplitScreenController controller) {
-            mController = controller;
-        }
-
-        /**
-         * Invalidates this instance, preventing future calls from updating the controller.
-         */
-        void invalidate() {
-            mController = null;
-        }
-
-        @Override
-        public void registerSplitScreenListener(ISplitScreenListener listener) {
-            executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
-                    (controller) -> {
-                        if (mListener != null) {
-                            mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
-                                    0 /* flags */);
-                        }
-                        if (listener != null) {
-                            try {
-                                listener.asBinder().linkToDeath(mListenerDeathRecipient,
-                                        0 /* flags */);
-                            } catch (RemoteException e) {
-                                Slog.e(TAG, "Failed to link to death");
-                                return;
-                            }
-                        }
-                        mListener = listener;
-                        controller.registerSplitScreenListener(mSplitScreenListener);
-                    });
-        }
-
-        @Override
-        public void unregisterSplitScreenListener(ISplitScreenListener listener) {
-            executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener",
-                    (controller) -> {
-                        if (mListener != null) {
-                            mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
-                                    0 /* flags */);
-                        }
-                        mListener = null;
-                        controller.unregisterSplitScreenListener(mSplitScreenListener);
-                    });
-        }
-
-        @Override
-        public void exitSplitScreen(int toTopTaskId) {
-            executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
-                    (controller) -> {
-                        controller.exitSplitScreen(toTopTaskId,
-                                FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT);
-                    });
-        }
-
-        @Override
-        public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
-            executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
-                    (controller) -> {
-                        controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
-                    });
-        }
-
-        @Override
-        public void setSideStageVisibility(boolean visible) {
-            executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility",
-                    (controller) -> {
-                        controller.setSideStageVisibility(visible);
-                    });
-        }
-
-        @Override
-        public void removeFromSideStage(int taskId) {
-            executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
-                    (controller) -> {
-                        controller.removeFromSideStage(taskId);
-                    });
-        }
-
-        @Override
-        public void startTask(int taskId, int stage, int position, @Nullable Bundle options) {
-            executeRemoteCallWithTaskPermission(mController, "startTask",
-                    (controller) -> {
-                        controller.startTask(taskId, position, options);
-                    });
-        }
-
-        @Override
-        public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
-                int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
-                RemoteAnimationAdapter adapter) {
-            executeRemoteCallWithTaskPermission(mController, "startTasks",
-                    (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
-                            mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
-                            adapter));
-        }
-
-        @Override
-        public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
-                int sideTaskId, @Nullable Bundle sideOptions,
-                @SplitPosition int sidePosition,
-                @Nullable RemoteTransition remoteTransition) {
-            executeRemoteCallWithTaskPermission(mController, "startTasks",
-                    (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
-                            sideTaskId, sideOptions, sidePosition, remoteTransition));
-        }
-
-        @Override
-        public void startShortcut(String packageName, String shortcutId, int stage, int position,
-                @Nullable Bundle options, UserHandle user) {
-            executeRemoteCallWithTaskPermission(mController, "startShortcut",
-                    (controller) -> {
-                        controller.startShortcut(packageName, shortcutId, position,
-                                options, user);
-                    });
-        }
-
-        @Override
-        public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
-                @Nullable Bundle options) {
-            executeRemoteCallWithTaskPermission(mController, "startIntent",
-                    (controller) -> {
-                        controller.startIntent(intent, fillInIntent, position, options);
-                    });
-        }
-
-        @Override
-        public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
-                RemoteAnimationTarget[] apps) {
-            final RemoteAnimationTarget[][] out = new RemoteAnimationTarget[][]{null};
-            executeRemoteCallWithTaskPermission(mController, "onGoingToRecentsLegacy",
-                    (controller) -> out[0] = controller.onGoingToRecentsLegacy(cancel, apps),
-                    true /* blocking */);
-            return out[0];
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
deleted file mode 100644
index 0183654..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
-
-import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
-import static com.android.wm.shell.transition.Transitions.isOpeningType;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.window.RemoteTransition;
-import android.window.TransitionInfo;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.transition.OneShotRemoteHandler;
-import com.android.wm.shell.transition.Transitions;
-
-import java.util.ArrayList;
-
-/** Manages transition animations for split-screen. */
-class SplitScreenTransitions {
-    private static final String TAG = "SplitScreenTransitions";
-
-    /** Flag applied to a transition change to identify it as a divider bar for animation. */
-    public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
-
-    private final TransactionPool mTransactionPool;
-    private final Transitions mTransitions;
-    private final Runnable mOnFinish;
-
-    IBinder mPendingDismiss = null;
-    IBinder mPendingEnter = null;
-
-    private IBinder mAnimatingTransition = null;
-    private OneShotRemoteHandler mRemoteHandler = null;
-
-    private Transitions.TransitionFinishCallback mRemoteFinishCB = (wct, wctCB) -> {
-        if (wct != null || wctCB != null) {
-            throw new UnsupportedOperationException("finish transactions not supported yet.");
-        }
-        onFinish();
-    };
-
-    /** Keeps track of currently running animations */
-    private final ArrayList<Animator> mAnimations = new ArrayList<>();
-
-    private Transitions.TransitionFinishCallback mFinishCallback = null;
-    private SurfaceControl.Transaction mFinishTransaction;
-
-    SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
-            @NonNull Runnable onFinishCallback) {
-        mTransactionPool = pool;
-        mTransitions = transitions;
-        mOnFinish = onFinishCallback;
-    }
-
-    void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Transitions.TransitionFinishCallback finishCallback,
-            @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) {
-        mFinishCallback = finishCallback;
-        mAnimatingTransition = transition;
-        if (mRemoteHandler != null) {
-            mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction,
-                    mRemoteFinishCB);
-            mRemoteHandler = null;
-            return;
-        }
-        playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot);
-    }
-
-    private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
-            @NonNull WindowContainerToken sideRoot) {
-        mFinishTransaction = mTransactionPool.acquire();
-
-        // Play some place-holder fade animations
-        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-            final TransitionInfo.Change change = info.getChanges().get(i);
-            final SurfaceControl leash = change.getLeash();
-            final int mode = info.getChanges().get(i).getMode();
-
-            if (mode == TRANSIT_CHANGE) {
-                if (change.getParent() != null) {
-                    // This is probably reparented, so we want the parent to be immediately visible
-                    final TransitionInfo.Change parentChange = info.getChange(change.getParent());
-                    t.show(parentChange.getLeash());
-                    t.setAlpha(parentChange.getLeash(), 1.f);
-                    // and then animate this layer outside the parent (since, for example, this is
-                    // the home task animating from fullscreen to part-screen).
-                    t.reparent(leash, info.getRootLeash());
-                    t.setLayer(leash, info.getChanges().size() - i);
-                    // build the finish reparent/reposition
-                    mFinishTransaction.reparent(leash, parentChange.getLeash());
-                    mFinishTransaction.setPosition(leash,
-                            change.getEndRelOffset().x, change.getEndRelOffset().y);
-                }
-                // TODO(shell-transitions): screenshot here
-                final Rect startBounds = new Rect(change.getStartAbsBounds());
-                final Rect endBounds = new Rect(change.getEndAbsBounds());
-                startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
-                endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
-                startExampleResizeAnimation(leash, startBounds, endBounds);
-            }
-            if (change.getParent() != null) {
-                continue;
-            }
-
-            if (transition == mPendingEnter && (mainRoot.equals(change.getContainer())
-                    || sideRoot.equals(change.getContainer()))) {
-                t.setWindowCrop(leash, change.getStartAbsBounds().width(),
-                        change.getStartAbsBounds().height());
-            }
-            boolean isOpening = isOpeningType(info.getType());
-            if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
-                // fade in
-                startExampleAnimation(leash, true /* show */);
-            } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
-                // fade out
-                if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
-                    // Dismissing via snap-to-top/bottom means that the dismissed task is already
-                    // not-visible (usually cropped to oblivion) so immediately set its alpha to 0
-                    // and don't animate it so it doesn't pop-in when reparented.
-                    t.setAlpha(leash, 0.f);
-                } else {
-                    startExampleAnimation(leash, false /* show */);
-                }
-            }
-        }
-        t.apply();
-        onFinish();
-    }
-
-    /** Starts a transition to enter split with a remote transition animator. */
-    IBinder startEnterTransition(@WindowManager.TransitionType int transitType,
-            @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition,
-            @NonNull Transitions.TransitionHandler handler) {
-        if (remoteTransition != null) {
-            // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
-            mRemoteHandler = new OneShotRemoteHandler(
-                    mTransitions.getMainExecutor(), remoteTransition);
-        }
-        final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
-        mPendingEnter = transition;
-        if (mRemoteHandler != null) {
-            mRemoteHandler.setTransition(transition);
-        }
-        return transition;
-    }
-
-    /** Starts a transition for dismissing split after dragging the divider to a screen edge */
-    IBinder startSnapToDismiss(@NonNull WindowContainerTransaction wct,
-            @NonNull Transitions.TransitionHandler handler) {
-        final IBinder transition = mTransitions.startTransition(
-                TRANSIT_SPLIT_DISMISS_SNAP, wct, handler);
-        mPendingDismiss = transition;
-        return transition;
-    }
-
-    void onFinish() {
-        if (!mAnimations.isEmpty()) return;
-        mOnFinish.run();
-        if (mFinishTransaction != null) {
-            mFinishTransaction.apply();
-            mTransactionPool.release(mFinishTransaction);
-            mFinishTransaction = null;
-        }
-        mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
-        mFinishCallback = null;
-        if (mAnimatingTransition == mPendingEnter) {
-            mPendingEnter = null;
-        }
-        if (mAnimatingTransition == mPendingDismiss) {
-            mPendingDismiss = null;
-        }
-        mAnimatingTransition = null;
-    }
-
-    // TODO(shell-transitions): real animations
-    private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) {
-        final float end = show ? 1.f : 0.f;
-        final float start = 1.f - end;
-        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
-        final ValueAnimator va = ValueAnimator.ofFloat(start, end);
-        va.setDuration(500);
-        va.addUpdateListener(animation -> {
-            float fraction = animation.getAnimatedFraction();
-            transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
-            transaction.apply();
-        });
-        final Runnable finisher = () -> {
-            transaction.setAlpha(leash, end);
-            transaction.apply();
-            mTransactionPool.release(transaction);
-            mTransitions.getMainExecutor().execute(() -> {
-                mAnimations.remove(va);
-                onFinish();
-            });
-        };
-        va.addListener(new Animator.AnimatorListener() {
-            @Override
-            public void onAnimationStart(Animator animation) { }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                finisher.run();
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                finisher.run();
-            }
-
-            @Override
-            public void onAnimationRepeat(Animator animation) { }
-        });
-        mAnimations.add(va);
-        mTransitions.getAnimExecutor().execute(va::start);
-    }
-
-    // TODO(shell-transitions): real animations
-    private void startExampleResizeAnimation(@NonNull SurfaceControl leash,
-            @NonNull Rect startBounds, @NonNull Rect endBounds) {
-        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
-        final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f);
-        va.setDuration(500);
-        va.addUpdateListener(animation -> {
-            float fraction = animation.getAnimatedFraction();
-            transaction.setWindowCrop(leash,
-                    (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction),
-                    (int) (startBounds.height() * (1.f - fraction)
-                            + endBounds.height() * fraction));
-            transaction.setPosition(leash,
-                    startBounds.left * (1.f - fraction) + endBounds.left * fraction,
-                    startBounds.top * (1.f - fraction) + endBounds.top * fraction);
-            transaction.apply();
-        });
-        final Runnable finisher = () -> {
-            transaction.setWindowCrop(leash, 0, 0);
-            transaction.setPosition(leash, endBounds.left, endBounds.top);
-            transaction.apply();
-            mTransactionPool.release(transaction);
-            mTransitions.getMainExecutor().execute(() -> {
-                mAnimations.remove(va);
-                onFinish();
-            });
-        };
-        va.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                finisher.run();
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                finisher.run();
-            }
-        });
-        mAnimations.add(va);
-        mTransitions.getAnimExecutor().execute(va::start);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java
deleted file mode 100644
index e185039..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-
-import com.android.internal.logging.InstanceId;
-import com.android.internal.logging.InstanceIdSequence;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
-
-/**
- * Helper class that to log Drag & Drop UIEvents for a single session, see also go/uievent
- */
-public class SplitscreenEventLogger {
-
-    // Used to generate instance ids for this drag if one is not provided
-    private final InstanceIdSequence mIdSequence;
-
-    // The instance id for the current splitscreen session (from start to end)
-    private InstanceId mLoggerSessionId;
-
-    // Drag info
-    private @SplitPosition int mDragEnterPosition;
-    private InstanceId mDragEnterSessionId;
-
-    // For deduping async events
-    private int mLastMainStagePosition = -1;
-    private int mLastMainStageUid = -1;
-    private int mLastSideStagePosition = -1;
-    private int mLastSideStageUid = -1;
-    private float mLastSplitRatio = -1f;
-
-    public SplitscreenEventLogger() {
-        mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE);
-    }
-
-    /**
-     * Return whether a splitscreen session has started.
-     */
-    public boolean hasStartedSession() {
-        return mLoggerSessionId != null;
-    }
-
-    /**
-     * May be called before logEnter() to indicate that the session was started from a drag.
-     */
-    public void enterRequestedByDrag(@SplitPosition int position, InstanceId dragSessionId) {
-        mDragEnterPosition = position;
-        mDragEnterSessionId = dragSessionId;
-    }
-
-    /**
-     * Logs when the user enters splitscreen.
-     */
-    public void logEnter(float splitRatio,
-            @SplitPosition int mainStagePosition, int mainStageUid,
-            @SplitPosition int sideStagePosition, int sideStageUid,
-            boolean isLandscape) {
-        mLoggerSessionId = mIdSequence.newInstanceId();
-        int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED
-                ? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape)
-                : SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
-        updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
-                mainStageUid);
-        updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
-                sideStageUid);
-        updateSplitRatioState(splitRatio);
-        FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
-                FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__ENTER,
-                enterReason,
-                0 /* exitReason */,
-                splitRatio,
-                mLastMainStagePosition,
-                mLastMainStageUid,
-                mLastSideStagePosition,
-                mLastSideStageUid,
-                mDragEnterSessionId != null ? mDragEnterSessionId.getId() : 0,
-                mLoggerSessionId.getId());
-    }
-
-    /**
-     * Logs when the user exits splitscreen.  Only one of the main or side stages should be
-     * specified to indicate which position was focused as a part of exiting (both can be unset).
-     */
-    public void logExit(int exitReason, @SplitPosition int mainStagePosition, int mainStageUid,
-            @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
-        if (mLoggerSessionId == null) {
-            // Ignore changes until we've started logging the session
-            return;
-        }
-        if ((mainStagePosition != SPLIT_POSITION_UNDEFINED
-                && sideStagePosition != SPLIT_POSITION_UNDEFINED)
-                        || (mainStageUid != 0 && sideStageUid != 0)) {
-            throw new IllegalArgumentException("Only main or side stage should be set");
-        }
-        FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
-                FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__EXIT,
-                0 /* enterReason */,
-                exitReason,
-                0f /* splitRatio */,
-                getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
-                mainStageUid,
-                getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
-                sideStageUid,
-                0 /* dragInstanceId */,
-                mLoggerSessionId.getId());
-
-        // Reset states
-        mLoggerSessionId = null;
-        mDragEnterPosition = SPLIT_POSITION_UNDEFINED;
-        mDragEnterSessionId = null;
-        mLastMainStagePosition = -1;
-        mLastMainStageUid = -1;
-        mLastSideStagePosition = -1;
-        mLastSideStageUid = -1;
-    }
-
-    /**
-     * Logs when an app in the main stage changes.
-     */
-    public void logMainStageAppChange(@SplitPosition int mainStagePosition, int mainStageUid,
-            boolean isLandscape) {
-        if (mLoggerSessionId == null) {
-            // Ignore changes until we've started logging the session
-            return;
-        }
-        if (!updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition,
-                isLandscape), mainStageUid)) {
-            // Ignore if there are no user perceived changes
-            return;
-        }
-
-        FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
-                FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
-                0 /* enterReason */,
-                0 /* exitReason */,
-                0f /* splitRatio */,
-                mLastMainStagePosition,
-                mLastMainStageUid,
-                0 /* sideStagePosition */,
-                0 /* sideStageUid */,
-                0 /* dragInstanceId */,
-                mLoggerSessionId.getId());
-    }
-
-    /**
-     * Logs when an app in the side stage changes.
-     */
-    public void logSideStageAppChange(@SplitPosition int sideStagePosition, int sideStageUid,
-            boolean isLandscape) {
-        if (mLoggerSessionId == null) {
-            // Ignore changes until we've started logging the session
-            return;
-        }
-        if (!updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition,
-                isLandscape), sideStageUid)) {
-            // Ignore if there are no user perceived changes
-            return;
-        }
-
-        FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
-                FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
-                0 /* enterReason */,
-                0 /* exitReason */,
-                0f /* splitRatio */,
-                0 /* mainStagePosition */,
-                0 /* mainStageUid */,
-                mLastSideStagePosition,
-                mLastSideStageUid,
-                0 /* dragInstanceId */,
-                mLoggerSessionId.getId());
-    }
-
-    /**
-     * Logs when the splitscreen ratio changes.
-     */
-    public void logResize(float splitRatio) {
-        if (mLoggerSessionId == null) {
-            // Ignore changes until we've started logging the session
-            return;
-        }
-        if (splitRatio <= 0f || splitRatio >= 1f) {
-            // Don't bother reporting resizes that end up dismissing the split, that will be logged
-            // via the exit event
-            return;
-        }
-        if (!updateSplitRatioState(splitRatio)) {
-            // Ignore if there are no user perceived changes
-            return;
-        }
-        FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
-                FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__RESIZE,
-                0 /* enterReason */,
-                0 /* exitReason */,
-                mLastSplitRatio,
-                0 /* mainStagePosition */, 0 /* mainStageUid */,
-                0 /* sideStagePosition */, 0 /* sideStageUid */,
-                0 /* dragInstanceId */,
-                mLoggerSessionId.getId());
-    }
-
-    /**
-     * Logs when the apps in splitscreen are swapped.
-     */
-    public void logSwap(@SplitPosition int mainStagePosition, int mainStageUid,
-            @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
-        if (mLoggerSessionId == null) {
-            // Ignore changes until we've started logging the session
-            return;
-        }
-
-        updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
-                mainStageUid);
-        updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
-                sideStageUid);
-        FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
-                FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__SWAP,
-                0 /* enterReason */,
-                0 /* exitReason */,
-                0f /* splitRatio */,
-                mLastMainStagePosition,
-                mLastMainStageUid,
-                mLastSideStagePosition,
-                mLastSideStageUid,
-                0 /* dragInstanceId */,
-                mLoggerSessionId.getId());
-    }
-
-    private boolean updateMainStageState(int mainStagePosition, int mainStageUid) {
-        boolean changed = (mLastMainStagePosition != mainStagePosition)
-                || (mLastMainStageUid != mainStageUid);
-        if (!changed) {
-            return false;
-        }
-
-        mLastMainStagePosition = mainStagePosition;
-        mLastMainStageUid = mainStageUid;
-        return true;
-    }
-
-    private boolean updateSideStageState(int sideStagePosition, int sideStageUid) {
-        boolean changed = (mLastSideStagePosition != sideStagePosition)
-                || (mLastSideStageUid != sideStageUid);
-        if (!changed) {
-            return false;
-        }
-
-        mLastSideStagePosition = sideStagePosition;
-        mLastSideStageUid = sideStageUid;
-        return true;
-    }
-
-    private boolean updateSplitRatioState(float splitRatio) {
-        boolean changed = Float.compare(mLastSplitRatio, splitRatio) != 0;
-        if (!changed) {
-            return false;
-        }
-
-        mLastSplitRatio = splitRatio;
-        return true;
-    }
-
-    public int getDragEnterReasonFromSplitPosition(@SplitPosition int position,
-            boolean isLandscape) {
-        if (isLandscape) {
-            return position == SPLIT_POSITION_TOP_OR_LEFT
-                    ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_LEFT
-                    : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_RIGHT;
-        } else {
-            return position == SPLIT_POSITION_TOP_OR_LEFT
-                    ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_TOP
-                    : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_BOTTOM;
-        }
-    }
-
-    private int getMainStagePositionFromSplitPosition(@SplitPosition int position,
-            boolean isLandscape) {
-        if (position == SPLIT_POSITION_UNDEFINED) {
-            return 0;
-        }
-        if (isLandscape) {
-            return position == SPLIT_POSITION_TOP_OR_LEFT
-                    ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__LEFT
-                    : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__RIGHT;
-        } else {
-            return position == SPLIT_POSITION_TOP_OR_LEFT
-                    ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__TOP
-                    : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__BOTTOM;
-        }
-    }
-
-    private int getSideStagePositionFromSplitPosition(@SplitPosition int position,
-            boolean isLandscape) {
-        if (position == SPLIT_POSITION_UNDEFINED) {
-            return 0;
-        }
-        if (isLandscape) {
-            return position == SPLIT_POSITION_TOP_OR_LEFT
-                    ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__LEFT
-                    : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__RIGHT;
-        } else {
-            return position == SPLIT_POSITION_TOP_OR_LEFT
-                    ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__TOP
-                    : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__BOTTOM;
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
deleted file mode 100644
index ac25c75..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
+++ /dev/null
@@ -1,1333 +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.wm.shell.stagesplit;
-
-import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
-
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_MAIN;
-import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_SIDE;
-import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_UNDEFINED;
-import static com.android.wm.shell.stagesplit.SplitScreen.stageTypeToString;
-import static com.android.wm.shell.stagesplit.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
-import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
-import static com.android.wm.shell.transition.Transitions.isClosingType;
-import static com.android.wm.shell.transition.Transitions.isOpeningType;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
-import android.app.WindowConfiguration;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.hardware.devicestate.DeviceStateManager;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Slog;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.view.WindowManager;
-import android.window.DisplayAreaInfo;
-import android.window.RemoteTransition;
-import android.window.TransitionInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.InstanceId;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.split.SplitLayout;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
-import com.android.wm.shell.common.split.SplitWindowManager;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.transition.Transitions;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-
-import javax.inject.Provider;
-
-/**
- * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
- * {@link SideStage} stages.
- * Some high-level rules:
- * - The {@link StageCoordinator} is only considered active if the {@link SideStage} contains at
- * least one child task.
- * - The {@link MainStage} should only have children if the coordinator is active.
- * - The {@link SplitLayout} divider is only visible if both the {@link MainStage}
- * and {@link SideStage} are visible.
- * - The {@link MainStage} configuration is fullscreen when the {@link SideStage} isn't visible.
- * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and
- * {@link #onStageHasChildrenChanged(StageListenerImpl).}
- */
-class StageCoordinator implements SplitLayout.SplitLayoutHandler,
-        RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler {
-
-    private static final String TAG = StageCoordinator.class.getSimpleName();
-
-    /** internal value for mDismissTop that represents no dismiss */
-    private static final int NO_DISMISS = -2;
-
-    private final SurfaceSession mSurfaceSession = new SurfaceSession();
-
-    private final MainStage mMainStage;
-    private final StageListenerImpl mMainStageListener = new StageListenerImpl();
-    private final StageTaskUnfoldController mMainUnfoldController;
-    private final SideStage mSideStage;
-    private final StageListenerImpl mSideStageListener = new StageListenerImpl();
-    private final StageTaskUnfoldController mSideUnfoldController;
-    @SplitPosition
-    private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
-
-    private final int mDisplayId;
-    private SplitLayout mSplitLayout;
-    private boolean mDividerVisible;
-    private final SyncTransactionQueue mSyncQueue;
-    private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
-    private final ShellTaskOrganizer mTaskOrganizer;
-    private DisplayAreaInfo mDisplayAreaInfo;
-    private final Context mContext;
-    private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
-    private final DisplayImeController mDisplayImeController;
-    private final DisplayInsetsController mDisplayInsetsController;
-    private final SplitScreenTransitions mSplitTransitions;
-    private final SplitscreenEventLogger mLogger;
-    private boolean mExitSplitScreenOnHide;
-    private boolean mKeyguardOccluded;
-
-    // TODO(b/187041611): remove this flag after totally deprecated legacy split
-    /** Whether the device is supporting legacy split or not. */
-    private boolean mUseLegacySplit;
-
-    @SplitScreen.StageType private int mDismissTop = NO_DISMISS;
-
-    /** The target stage to dismiss to when unlock after folded. */
-    @SplitScreen.StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
-
-    private final Runnable mOnTransitionAnimationComplete = () -> {
-        // If still playing, let it finish.
-        if (!isSplitScreenVisible()) {
-            // Update divider state after animation so that it is still around and positioned
-            // properly for the animation itself.
-            setDividerVisibility(false);
-            mSplitLayout.resetDividerPosition();
-        }
-        mDismissTop = NO_DISMISS;
-    };
-
-    private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
-            new SplitWindowManager.ParentContainerCallbacks() {
-        @Override
-        public void attachToParentSurface(SurfaceControl.Builder b) {
-            mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b);
-        }
-
-        @Override
-        public void onLeashReady(SurfaceControl leash) {
-            mSyncQueue.runInSync(t -> applyDividerVisibility(t));
-        }
-    };
-
-    StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
-            RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
-            DisplayImeController displayImeController,
-            DisplayInsetsController displayInsetsController, Transitions transitions,
-            TransactionPool transactionPool, SplitscreenEventLogger logger,
-            Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
-        mContext = context;
-        mDisplayId = displayId;
-        mSyncQueue = syncQueue;
-        mRootTDAOrganizer = rootTDAOrganizer;
-        mTaskOrganizer = taskOrganizer;
-        mLogger = logger;
-        mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
-        mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
-
-        mMainStage = new MainStage(
-                mTaskOrganizer,
-                mDisplayId,
-                mMainStageListener,
-                mSyncQueue,
-                mSurfaceSession,
-                mMainUnfoldController);
-        mSideStage = new SideStage(
-                mContext,
-                mTaskOrganizer,
-                mDisplayId,
-                mSideStageListener,
-                mSyncQueue,
-                mSurfaceSession,
-                mSideUnfoldController);
-        mDisplayImeController = displayImeController;
-        mDisplayInsetsController = displayInsetsController;
-        mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage);
-        mRootTDAOrganizer.registerListener(displayId, this);
-        final DeviceStateManager deviceStateManager =
-                mContext.getSystemService(DeviceStateManager.class);
-        deviceStateManager.registerCallback(taskOrganizer.getExecutor(),
-                new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged));
-        mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
-                mOnTransitionAnimationComplete);
-        transitions.addHandler(this);
-    }
-
-    @VisibleForTesting
-    StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
-            RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
-            MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController,
-            DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
-            Transitions transitions, TransactionPool transactionPool,
-            SplitscreenEventLogger logger,
-            Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
-        mContext = context;
-        mDisplayId = displayId;
-        mSyncQueue = syncQueue;
-        mRootTDAOrganizer = rootTDAOrganizer;
-        mTaskOrganizer = taskOrganizer;
-        mMainStage = mainStage;
-        mSideStage = sideStage;
-        mDisplayImeController = displayImeController;
-        mDisplayInsetsController = displayInsetsController;
-        mRootTDAOrganizer.registerListener(displayId, this);
-        mSplitLayout = splitLayout;
-        mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
-                mOnTransitionAnimationComplete);
-        mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
-        mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
-        mLogger = logger;
-        transitions.addHandler(this);
-    }
-
-    @VisibleForTesting
-    SplitScreenTransitions getSplitTransitions() {
-        return mSplitTransitions;
-    }
-
-    boolean isSplitScreenVisible() {
-        return mSideStageListener.mVisible && mMainStageListener.mVisible;
-    }
-
-    boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
-            @SplitPosition int sideStagePosition) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        setSideStagePosition(sideStagePosition, wct);
-        mMainStage.activate(getMainStageBounds(), wct);
-        mSideStage.addTask(task, getSideStageBounds(), wct);
-        mSyncQueue.queue(wct);
-        mSyncQueue.runInSync(
-                t -> updateSurfaceBounds(null /* layout */, t, false /* applyResizingOffset */));
-        return true;
-    }
-
-    boolean removeFromSideStage(int taskId) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-
-        /**
-         * {@link MainStage} will be deactivated in {@link #onStageHasChildrenChanged} if the
-         * {@link SideStage} no longer has children.
-         */
-        final boolean result = mSideStage.removeTask(taskId,
-                mMainStage.isActive() ? mMainStage.mRootTaskInfo.token : null,
-                wct);
-        mTaskOrganizer.applyTransaction(wct);
-        return result;
-    }
-
-    void setSideStageOutline(boolean enable) {
-        mSideStage.enableOutline(enable);
-    }
-
-    /** Starts 2 tasks in one transition. */
-    void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
-            @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
-            @Nullable RemoteTransition remoteTransition) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        mainOptions = mainOptions != null ? mainOptions : new Bundle();
-        sideOptions = sideOptions != null ? sideOptions : new Bundle();
-        setSideStagePosition(sidePosition, wct);
-
-        // Build a request WCT that will launch both apps such that task 0 is on the main stage
-        // while task 1 is on the side stage.
-        mMainStage.activate(getMainStageBounds(), wct);
-        mSideStage.setBounds(getSideStageBounds(), wct);
-
-        // Make sure the launch options will put tasks in the corresponding split roots
-        addActivityOptions(mainOptions, mMainStage);
-        addActivityOptions(sideOptions, mSideStage);
-
-        // Add task launch requests
-        wct.startTask(mainTaskId, mainOptions);
-        wct.startTask(sideTaskId, sideOptions);
-
-        mSplitTransitions.startEnterTransition(
-                TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this);
-    }
-
-    /** Starts 2 tasks in one legacy transition. */
-    void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
-            int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
-            RemoteAnimationAdapter adapter) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        // Need to add another wrapper here in shell so that we can inject the divider bar
-        // and also manage the process elevation via setRunningRemote
-        IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
-            @Override
-            public void onAnimationStart(@WindowManager.TransitionOldType int transit,
-                    RemoteAnimationTarget[] apps,
-                    RemoteAnimationTarget[] wallpapers,
-                    RemoteAnimationTarget[] nonApps,
-                    final IRemoteAnimationFinishedCallback finishedCallback) {
-                RemoteAnimationTarget[] augmentedNonApps =
-                        new RemoteAnimationTarget[nonApps.length + 1];
-                for (int i = 0; i < nonApps.length; ++i) {
-                    augmentedNonApps[i] = nonApps[i];
-                }
-                augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget();
-                try {
-                    ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
-                            adapter.getCallingApplication());
-                    adapter.getRunner().onAnimationStart(transit, apps, wallpapers, nonApps,
-                            finishedCallback);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Error starting remote animation", e);
-                }
-            }
-
-            @Override
-            public void onAnimationCancelled() {
-                try {
-                    adapter.getRunner().onAnimationCancelled();
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Error starting remote animation", e);
-                }
-            }
-        };
-        RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
-                wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
-
-        if (mainOptions == null) {
-            mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
-        } else {
-            ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
-            mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
-        }
-
-        sideOptions = sideOptions != null ? sideOptions : new Bundle();
-        setSideStagePosition(sidePosition, wct);
-
-        // Build a request WCT that will launch both apps such that task 0 is on the main stage
-        // while task 1 is on the side stage.
-        mMainStage.activate(getMainStageBounds(), wct);
-        mSideStage.setBounds(getSideStageBounds(), wct);
-
-        // Make sure the launch options will put tasks in the corresponding split roots
-        addActivityOptions(mainOptions, mMainStage);
-        addActivityOptions(sideOptions, mSideStage);
-
-        // Add task launch requests
-        wct.startTask(mainTaskId, mainOptions);
-        wct.startTask(sideTaskId, sideOptions);
-
-        // Using legacy transitions, so we can't use blast sync since it conflicts.
-        mTaskOrganizer.applyTransaction(wct);
-    }
-
-    public void startIntent(PendingIntent intent, Intent fillInIntent,
-            @SplitScreen.StageType int stage, @SplitPosition int position,
-            @androidx.annotation.Nullable Bundle options,
-            @Nullable RemoteTransition remoteTransition) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        options = resolveStartStage(stage, position, options, wct);
-        wct.sendPendingIntent(intent, fillInIntent, options);
-        mSplitTransitions.startEnterTransition(
-                TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this);
-    }
-
-    Bundle resolveStartStage(@SplitScreen.StageType int stage,
-            @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
-            @androidx.annotation.Nullable WindowContainerTransaction wct) {
-        switch (stage) {
-            case STAGE_TYPE_UNDEFINED: {
-                // Use the stage of the specified position is valid.
-                if (position != SPLIT_POSITION_UNDEFINED) {
-                    if (position == getSideStagePosition()) {
-                        options = resolveStartStage(STAGE_TYPE_SIDE, position, options, wct);
-                    } else {
-                        options = resolveStartStage(STAGE_TYPE_MAIN, position, options, wct);
-                    }
-                } else {
-                    // Exit split-screen and launch fullscreen since stage wasn't specified.
-                    prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
-                }
-                break;
-            }
-            case STAGE_TYPE_SIDE: {
-                if (position != SPLIT_POSITION_UNDEFINED) {
-                    setSideStagePosition(position, wct);
-                } else {
-                    position = getSideStagePosition();
-                }
-                if (options == null) {
-                    options = new Bundle();
-                }
-                updateActivityOptions(options, position);
-                break;
-            }
-            case STAGE_TYPE_MAIN: {
-                if (position != SPLIT_POSITION_UNDEFINED) {
-                    // Set the side stage opposite of what we want to the main stage.
-                    final int sideStagePosition = position == SPLIT_POSITION_TOP_OR_LEFT
-                            ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
-                    setSideStagePosition(sideStagePosition, wct);
-                } else {
-                    position = getMainStagePosition();
-                }
-                if (options == null) {
-                    options = new Bundle();
-                }
-                updateActivityOptions(options, position);
-                break;
-            }
-            default:
-                throw new IllegalArgumentException("Unknown stage=" + stage);
-        }
-
-        return options;
-    }
-
-    @SplitPosition
-    int getSideStagePosition() {
-        return mSideStagePosition;
-    }
-
-    @SplitPosition
-    int getMainStagePosition() {
-        return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
-                ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
-    }
-
-    void setSideStagePosition(@SplitPosition int sideStagePosition,
-            @Nullable WindowContainerTransaction wct) {
-        setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
-    }
-
-    private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
-            @Nullable WindowContainerTransaction wct) {
-        if (mSideStagePosition == sideStagePosition) return;
-        mSideStagePosition = sideStagePosition;
-        sendOnStagePositionChanged();
-
-        if (mSideStageListener.mVisible && updateBounds) {
-            if (wct == null) {
-                // onLayoutSizeChanged builds/applies a wct with the contents of updateWindowBounds.
-                onLayoutSizeChanged(mSplitLayout);
-            } else {
-                updateWindowBounds(mSplitLayout, wct);
-                updateUnfoldBounds();
-            }
-        }
-    }
-
-    void setSideStageVisibility(boolean visible) {
-        if (mSideStageListener.mVisible == visible) return;
-
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        mSideStage.setVisibility(visible, wct);
-        mTaskOrganizer.applyTransaction(wct);
-    }
-
-    void onKeyguardOccludedChanged(boolean occluded) {
-        // Do not exit split directly, because it needs to wait for task info update to determine
-        // which task should remain on top after split dismissed.
-        mKeyguardOccluded = occluded;
-    }
-
-    void onKeyguardVisibilityChanged(boolean showing) {
-        if (!showing && mMainStage.isActive()
-                && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
-            exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
-                    SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED);
-        }
-    }
-
-    void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
-        mExitSplitScreenOnHide = exitSplitScreenOnHide;
-    }
-
-    void exitSplitScreen(int toTopTaskId, int exitReason) {
-        StageTaskListener childrenToTop = null;
-        if (mMainStage.containsTask(toTopTaskId)) {
-            childrenToTop = mMainStage;
-        } else if (mSideStage.containsTask(toTopTaskId)) {
-            childrenToTop = mSideStage;
-        }
-
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        if (childrenToTop != null) {
-            childrenToTop.reorderChild(toTopTaskId, true /* onTop */, wct);
-        }
-        applyExitSplitScreen(childrenToTop, wct, exitReason);
-    }
-
-    private void exitSplitScreen(StageTaskListener childrenToTop, int exitReason) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        applyExitSplitScreen(childrenToTop, wct, exitReason);
-    }
-
-    private void applyExitSplitScreen(
-            StageTaskListener childrenToTop,
-            WindowContainerTransaction wct, int exitReason) {
-        mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
-        mMainStage.deactivate(wct, childrenToTop == mMainStage);
-        mTaskOrganizer.applyTransaction(wct);
-        mSyncQueue.runInSync(t -> t
-                .setWindowCrop(mMainStage.mRootLeash, null)
-                .setWindowCrop(mSideStage.mRootLeash, null));
-        // Hide divider and reset its position.
-        setDividerVisibility(false);
-        mSplitLayout.resetDividerPosition();
-        mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
-        if (childrenToTop != null) {
-            logExitToStage(exitReason, childrenToTop == mMainStage);
-        } else {
-            logExit(exitReason);
-        }
-    }
-
-    /**
-     * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
-     * an existing WindowContainerTransaction (rather than applying immediately). This is intended
-     * to be used when exiting split might be bundled with other window operations.
-     */
-    void prepareExitSplitScreen(@SplitScreen.StageType int stageToTop,
-            @NonNull WindowContainerTransaction wct) {
-        mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
-        mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
-    }
-
-    void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
-        outTopOrLeftBounds.set(mSplitLayout.getBounds1());
-        outBottomOrRightBounds.set(mSplitLayout.getBounds2());
-    }
-
-    private void addActivityOptions(Bundle opts, StageTaskListener stage) {
-        opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
-    }
-
-    void updateActivityOptions(Bundle opts, @SplitPosition int position) {
-        addActivityOptions(opts, position == mSideStagePosition ? mSideStage : mMainStage);
-    }
-
-    void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
-        if (mListeners.contains(listener)) return;
-        mListeners.add(listener);
-        sendStatusToListener(listener);
-    }
-
-    void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
-        mListeners.remove(listener);
-    }
-
-    void sendStatusToListener(SplitScreen.SplitScreenListener listener) {
-        listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
-        listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
-        listener.onSplitVisibilityChanged(isSplitScreenVisible());
-        mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
-        mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
-    }
-
-    private void sendOnStagePositionChanged() {
-        for (int i = mListeners.size() - 1; i >= 0; --i) {
-            final SplitScreen.SplitScreenListener l = mListeners.get(i);
-            l.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
-            l.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
-        }
-    }
-
-    private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId,
-            boolean present, boolean visible) {
-        int stage;
-        if (present) {
-            stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
-        } else {
-            // No longer on any stage
-            stage = STAGE_TYPE_UNDEFINED;
-        }
-        if (stage == STAGE_TYPE_MAIN) {
-            mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
-                    mSplitLayout.isLandscape());
-        } else {
-            mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
-                    mSplitLayout.isLandscape());
-        }
-
-        for (int i = mListeners.size() - 1; i >= 0; --i) {
-            mListeners.get(i).onTaskStageChanged(taskId, stage, visible);
-        }
-    }
-
-    private void sendSplitVisibilityChanged() {
-        for (int i = mListeners.size() - 1; i >= 0; --i) {
-            final SplitScreen.SplitScreenListener l = mListeners.get(i);
-            l.onSplitVisibilityChanged(mDividerVisible);
-        }
-
-        if (mMainUnfoldController != null && mSideUnfoldController != null) {
-            mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
-            mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
-        }
-    }
-
-    private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
-        if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
-            mUseLegacySplit = mContext.getResources().getBoolean(R.bool.config_useLegacySplit);
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
-            // Make the stages adjacent to each other so they occlude what's behind them.
-            wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token,
-                    true /* moveTogether */);
-
-            // Only sets side stage as launch-adjacent-flag-root when the device is not using legacy
-            // split to prevent new split behavior confusing users.
-            if (!mUseLegacySplit) {
-                wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
-            }
-
-            mTaskOrganizer.applyTransaction(wct);
-        }
-    }
-
-    private void onStageRootTaskVanished(StageListenerImpl stageListener) {
-        if (stageListener == mMainStageListener || stageListener == mSideStageListener) {
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
-            // Deactivate the main stage if it no longer has a root task.
-            mMainStage.deactivate(wct);
-
-            if (!mUseLegacySplit) {
-                wct.clearLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
-            }
-
-            mTaskOrganizer.applyTransaction(wct);
-        }
-    }
-
-    private void setDividerVisibility(boolean visible) {
-        if (mDividerVisible == visible) return;
-        mDividerVisible = visible;
-        if (visible) {
-            mSplitLayout.init();
-            updateUnfoldBounds();
-        } else {
-            mSplitLayout.release();
-        }
-        sendSplitVisibilityChanged();
-    }
-
-    private void onStageVisibilityChanged(StageListenerImpl stageListener) {
-        final boolean sideStageVisible = mSideStageListener.mVisible;
-        final boolean mainStageVisible = mMainStageListener.mVisible;
-        final boolean bothStageVisible = sideStageVisible && mainStageVisible;
-        final boolean bothStageInvisible = !sideStageVisible && !mainStageVisible;
-        final boolean sameVisibility = sideStageVisible == mainStageVisible;
-        // Only add or remove divider when both visible or both invisible to avoid sometimes we only
-        // got one stage visibility changed for a moment and it will cause flicker.
-        if (sameVisibility) {
-            setDividerVisibility(bothStageVisible);
-        }
-
-        if (bothStageInvisible) {
-            if (mExitSplitScreenOnHide
-            // Don't dismiss staged split when both stages are not visible due to sleeping display,
-            // like the cases keyguard showing or screen off.
-            || (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping)) {
-                exitSplitScreen(null /* childrenToTop */,
-                        SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME);
-            }
-        } else if (mKeyguardOccluded) {
-            // At least one of the stages is visible while keyguard occluded. Dismiss split because
-            // there's show-when-locked activity showing on top of keyguard. Also make sure the
-            // task contains show-when-locked activity remains on top after split dismissed.
-            final StageTaskListener toTop =
-                    mainStageVisible ? mMainStage : (sideStageVisible ? mSideStage : null);
-            exitSplitScreen(toTop, SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP);
-        }
-
-        mSyncQueue.runInSync(t -> {
-            // Same above, we only set root tasks and divider leash visibility when both stage
-            // change to visible or invisible to avoid flicker.
-            if (sameVisibility) {
-                t.setVisibility(mSideStage.mRootLeash, bothStageVisible)
-                        .setVisibility(mMainStage.mRootLeash, bothStageVisible);
-                applyDividerVisibility(t);
-                applyOutlineVisibility(t);
-            }
-        });
-    }
-
-    private void applyDividerVisibility(SurfaceControl.Transaction t) {
-        final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
-        if (dividerLeash == null) {
-            return;
-        }
-
-        if (mDividerVisible) {
-            t.show(dividerLeash)
-                    .setLayer(dividerLeash, Integer.MAX_VALUE)
-                    .setPosition(dividerLeash,
-                            mSplitLayout.getDividerBounds().left,
-                            mSplitLayout.getDividerBounds().top);
-        } else {
-            t.hide(dividerLeash);
-        }
-    }
-
-    private void applyOutlineVisibility(SurfaceControl.Transaction t) {
-        final SurfaceControl outlineLeash = mSideStage.getOutlineLeash();
-        if (outlineLeash == null) {
-            return;
-        }
-
-        if (mDividerVisible) {
-            t.show(outlineLeash).setLayer(outlineLeash, Integer.MAX_VALUE);
-        } else {
-            t.hide(outlineLeash);
-        }
-    }
-
-    private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
-        final boolean hasChildren = stageListener.mHasChildren;
-        final boolean isSideStage = stageListener == mSideStageListener;
-        if (!hasChildren) {
-            if (isSideStage && mMainStageListener.mVisible) {
-                // Exit to main stage if side stage no longer has children.
-                exitSplitScreen(mMainStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
-            } else if (!isSideStage && mSideStageListener.mVisible) {
-                // Exit to side stage if main stage no longer has children.
-                exitSplitScreen(mSideStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
-            }
-        } else if (isSideStage) {
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
-            // Make sure the main stage is active.
-            mMainStage.activate(getMainStageBounds(), wct);
-            mSideStage.setBounds(getSideStageBounds(), wct);
-            mTaskOrganizer.applyTransaction(wct);
-        }
-        if (!mLogger.hasStartedSession() && mMainStageListener.mHasChildren
-                && mSideStageListener.mHasChildren) {
-            mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
-                    getMainStagePosition(), mMainStage.getTopChildTaskUid(),
-                    getSideStagePosition(), mSideStage.getTopChildTaskUid(),
-                    mSplitLayout.isLandscape());
-        }
-    }
-
-    @VisibleForTesting
-    IBinder onSnappedToDismissTransition(boolean mainStageToTop) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        prepareExitSplitScreen(mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE, wct);
-        return mSplitTransitions.startSnapToDismiss(wct, this);
-    }
-
-    @Override
-    public void onSnappedToDismiss(boolean bottomOrRight) {
-        final boolean mainStageToTop =
-                bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
-                        : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
-        if (ENABLE_SHELL_TRANSITIONS) {
-            onSnappedToDismissTransition(mainStageToTop);
-            return;
-        }
-        exitSplitScreen(mainStageToTop ? mMainStage : mSideStage,
-                SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER);
-    }
-
-    @Override
-    public void onDoubleTappedDivider() {
-        setSideStagePosition(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
-                ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT, null /* wct */);
-        mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
-                getSideStagePosition(), mSideStage.getTopChildTaskUid(),
-                mSplitLayout.isLandscape());
-    }
-
-    @Override
-    public void onLayoutPositionChanging(SplitLayout layout) {
-        mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t, true /* applyResizingOffset */));
-    }
-
-    @Override
-    public void onLayoutSizeChanging(SplitLayout layout) {
-        mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t, true /* applyResizingOffset */));
-        mSideStage.setOutlineVisibility(false);
-    }
-
-    @Override
-    public void onLayoutSizeChanged(SplitLayout layout) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        updateWindowBounds(layout, wct);
-        updateUnfoldBounds();
-        mSyncQueue.queue(wct);
-        mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t, false /* applyResizingOffset */));
-        mSideStage.setOutlineVisibility(true);
-        mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
-    }
-
-    private void updateUnfoldBounds() {
-        if (mMainUnfoldController != null && mSideUnfoldController != null) {
-            mMainUnfoldController.onLayoutChanged(getMainStageBounds());
-            mSideUnfoldController.onLayoutChanged(getSideStageBounds());
-        }
-    }
-
-    /**
-     * Populates `wct` with operations that match the split windows to the current layout.
-     * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
-     */
-    private void updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) {
-        final StageTaskListener topLeftStage =
-                mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
-        final StageTaskListener bottomRightStage =
-                mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
-        layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo);
-    }
-
-    void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t,
-            boolean applyResizingOffset) {
-        final StageTaskListener topLeftStage =
-                mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
-        final StageTaskListener bottomRightStage =
-                mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
-        (layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash,
-                bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer,
-                applyResizingOffset);
-    }
-
-    @Override
-    public int getSplitItemPosition(WindowContainerToken token) {
-        if (token == null) {
-            return SPLIT_POSITION_UNDEFINED;
-        }
-
-        if (token.equals(mMainStage.mRootTaskInfo.getToken())) {
-            return getMainStagePosition();
-        } else if (token.equals(mSideStage.mRootTaskInfo.getToken())) {
-            return getSideStagePosition();
-        }
-
-        return SPLIT_POSITION_UNDEFINED;
-    }
-
-    @Override
-    public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
-        final StageTaskListener topLeftStage =
-                mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
-        final StageTaskListener bottomRightStage =
-                mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo,
-                bottomRightStage.mRootTaskInfo);
-        mTaskOrganizer.applyTransaction(wct);
-    }
-
-    @Override
-    public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) {
-        mDisplayAreaInfo = displayAreaInfo;
-        if (mSplitLayout == null) {
-            mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
-                    mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
-                    mDisplayImeController, mTaskOrganizer, SplitLayout.PARALLAX_DISMISSING);
-            mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
-
-            if (mMainUnfoldController != null && mSideUnfoldController != null) {
-                mMainUnfoldController.init();
-                mSideUnfoldController.init();
-            }
-        }
-    }
-
-    @Override
-    public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) {
-        throw new IllegalStateException("Well that was unexpected...");
-    }
-
-    @Override
-    public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) {
-        mDisplayAreaInfo = displayAreaInfo;
-        if (mSplitLayout != null
-                && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)
-                && mMainStage.isActive()) {
-            onLayoutSizeChanged(mSplitLayout);
-        }
-    }
-
-    private void onFoldedStateChanged(boolean folded) {
-        mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
-        if (!folded) return;
-
-        if (mMainStage.isFocused()) {
-            mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
-        } else if (mSideStage.isFocused()) {
-            mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
-        }
-    }
-
-    private Rect getSideStageBounds() {
-        return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
-                ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
-    }
-
-    private Rect getMainStageBounds() {
-        return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
-                ? mSplitLayout.getBounds2() : mSplitLayout.getBounds1();
-    }
-
-    /**
-     * Get the stage that should contain this `taskInfo`. The stage doesn't necessarily contain
-     * this task (yet) so this can also be used to identify which stage to put a task into.
-     */
-    private StageTaskListener getStageOfTask(ActivityManager.RunningTaskInfo taskInfo) {
-        // TODO(b/184679596): Find a way to either include task-org information in the transition,
-        //                    or synchronize task-org callbacks so we can use stage.containsTask
-        if (mMainStage.mRootTaskInfo != null
-                && taskInfo.parentTaskId == mMainStage.mRootTaskInfo.taskId) {
-            return mMainStage;
-        } else if (mSideStage.mRootTaskInfo != null
-                && taskInfo.parentTaskId == mSideStage.mRootTaskInfo.taskId) {
-            return mSideStage;
-        }
-        return null;
-    }
-
-    @SplitScreen.StageType
-    private int getStageType(StageTaskListener stage) {
-        return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
-    }
-
-    @Override
-    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
-            @Nullable TransitionRequestInfo request) {
-        final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
-        if (triggerTask == null) {
-            // still want to monitor everything while in split-screen, so return non-null.
-            return isSplitScreenVisible() ? new WindowContainerTransaction() : null;
-        }
-
-        WindowContainerTransaction out = null;
-        final @WindowManager.TransitionType int type = request.getType();
-        if (isSplitScreenVisible()) {
-            // try to handle everything while in split-screen, so return a WCT even if it's empty.
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  split is active so using split"
-                            + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d"
-                            + " sideChildren=%d", triggerTask.taskId, transitTypeToString(type),
-                    mMainStage.getChildCount(), mSideStage.getChildCount());
-            out = new WindowContainerTransaction();
-            final StageTaskListener stage = getStageOfTask(triggerTask);
-            if (stage != null) {
-                // dismiss split if the last task in one of the stages is going away
-                if (isClosingType(type) && stage.getChildCount() == 1) {
-                    // The top should be the opposite side that is closing:
-                    mDismissTop = getStageType(stage) == STAGE_TYPE_MAIN
-                            ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
-                }
-            } else {
-                if (triggerTask.getActivityType() == ACTIVITY_TYPE_HOME && isOpeningType(type)) {
-                    // Going home so dismiss both.
-                    mDismissTop = STAGE_TYPE_UNDEFINED;
-                }
-            }
-            if (mDismissTop != NO_DISMISS) {
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
-                                + " deduced Dismiss from request. toTop=%s",
-                        stageTypeToString(mDismissTop));
-                prepareExitSplitScreen(mDismissTop, out);
-                mSplitTransitions.mPendingDismiss = transition;
-            }
-        } else {
-            // Not in split mode, so look for an open into a split stage just so we can whine and
-            // complain about how this isn't a supported operation.
-            if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)) {
-                if (getStageOfTask(triggerTask) != null) {
-                    throw new IllegalStateException("Entering split implicitly with only one task"
-                            + " isn't supported.");
-                }
-            }
-        }
-        return out;
-    }
-
-    @Override
-    public boolean startAnimation(@NonNull IBinder transition,
-            @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        if (transition != mSplitTransitions.mPendingDismiss
-                && transition != mSplitTransitions.mPendingEnter) {
-            // Not entering or exiting, so just do some house-keeping and validation.
-
-            // If we're not in split-mode, just abort so something else can handle it.
-            if (!isSplitScreenVisible()) return false;
-
-            for (int iC = 0; iC < info.getChanges().size(); ++iC) {
-                final TransitionInfo.Change change = info.getChanges().get(iC);
-                final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-                if (taskInfo == null || !taskInfo.hasParentTask()) continue;
-                final StageTaskListener stage = getStageOfTask(taskInfo);
-                if (stage == null) continue;
-                if (isOpeningType(change.getMode())) {
-                    if (!stage.containsTask(taskInfo.taskId)) {
-                        Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called"
-                                + " with " + taskInfo.taskId + " before startAnimation().");
-                    }
-                } else if (isClosingType(change.getMode())) {
-                    if (stage.containsTask(taskInfo.taskId)) {
-                        Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
-                                + " with " + taskInfo.taskId + " before startAnimation().");
-                    }
-                }
-            }
-            if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
-                // TODO(shell-transitions): Implement a fallback behavior for now.
-                throw new IllegalStateException("Somehow removed the last task in a stage"
-                        + " outside of a proper transition");
-                // This can happen in some pathological cases. For example:
-                // 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C]
-                // 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time
-                // In this case, the result *should* be that we leave split.
-                // TODO(b/184679596): Find a way to either include task-org information in
-                //                    the transition, or synchronize task-org callbacks.
-            }
-
-            // Use normal animations.
-            return false;
-        }
-
-        boolean shouldAnimate = true;
-        if (mSplitTransitions.mPendingEnter == transition) {
-            shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
-        } else if (mSplitTransitions.mPendingDismiss == transition) {
-            shouldAnimate = startPendingDismissAnimation(transition, info, startTransaction);
-        }
-        if (!shouldAnimate) return false;
-
-        mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction,
-                finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
-        return true;
-    }
-
-    private boolean startPendingEnterAnimation(@NonNull IBinder transition,
-            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
-        if (info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN) {
-            // First, verify that we actually have opened 2 apps in split.
-            TransitionInfo.Change mainChild = null;
-            TransitionInfo.Change sideChild = null;
-            for (int iC = 0; iC < info.getChanges().size(); ++iC) {
-                final TransitionInfo.Change change = info.getChanges().get(iC);
-                final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-                if (taskInfo == null || !taskInfo.hasParentTask()) continue;
-                final @SplitScreen.StageType int stageType = getStageType(getStageOfTask(taskInfo));
-                if (stageType == STAGE_TYPE_MAIN) {
-                    mainChild = change;
-                } else if (stageType == STAGE_TYPE_SIDE) {
-                    sideChild = change;
-                }
-            }
-            if (mainChild == null || sideChild == null) {
-                throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
-                        + " 2 tasks in transition. Possibly one of them failed to launch");
-                // TODO: fallback logic. Probably start a new transition to exit split before
-                //       applying anything here. Ideally consolidate with transition-merging.
-            }
-
-            // Update local states (before animating).
-            setDividerVisibility(true);
-            setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */,
-                    null /* wct */);
-            setSplitsVisible(true);
-
-            addDividerBarToTransition(info, t, true /* show */);
-
-            // Make some noise if things aren't totally expected. These states shouldn't effect
-            // transitions locally, but remotes (like Launcher) may get confused if they were
-            // depending on listener callbacks. This can happen because task-organizer callbacks
-            // aren't serialized with transition callbacks.
-            // TODO(b/184679596): Find a way to either include task-org information in
-            //                    the transition, or synchronize task-org callbacks.
-            if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
-                Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
-                        + " to have been called with " + mainChild.getTaskInfo().taskId
-                        + " before startAnimation().");
-            }
-            if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
-                Log.w(TAG, "Expected onTaskAppeared on " + mSideStage
-                        + " to have been called with " + sideChild.getTaskInfo().taskId
-                        + " before startAnimation().");
-            }
-            return true;
-        } else {
-            // TODO: other entry method animations
-            throw new RuntimeException("Unsupported split-entry");
-        }
-    }
-
-    private boolean startPendingDismissAnimation(@NonNull IBinder transition,
-            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
-        // Make some noise if things aren't totally expected. These states shouldn't effect
-        // transitions locally, but remotes (like Launcher) may get confused if they were
-        // depending on listener callbacks. This can happen because task-organizer callbacks
-        // aren't serialized with transition callbacks.
-        // TODO(b/184679596): Find a way to either include task-org information in
-        //                    the transition, or synchronize task-org callbacks.
-        if (mMainStage.getChildCount() != 0) {
-            final StringBuilder tasksLeft = new StringBuilder();
-            for (int i = 0; i < mMainStage.getChildCount(); ++i) {
-                tasksLeft.append(i != 0 ? ", " : "");
-                tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i));
-            }
-            Log.w(TAG, "Expected onTaskVanished on " + mMainStage
-                    + " to have been called with [" + tasksLeft.toString()
-                    + "] before startAnimation().");
-        }
-        if (mSideStage.getChildCount() != 0) {
-            final StringBuilder tasksLeft = new StringBuilder();
-            for (int i = 0; i < mSideStage.getChildCount(); ++i) {
-                tasksLeft.append(i != 0 ? ", " : "");
-                tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i));
-            }
-            Log.w(TAG, "Expected onTaskVanished on " + mSideStage
-                    + " to have been called with [" + tasksLeft.toString()
-                    + "] before startAnimation().");
-        }
-
-        // Update local states.
-        setSplitsVisible(false);
-        // Wait until after animation to update divider
-
-        if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
-            // Reset crops so they don't interfere with subsequent launches
-            t.setWindowCrop(mMainStage.mRootLeash, null);
-            t.setWindowCrop(mSideStage.mRootLeash, null);
-        }
-
-        if (mDismissTop == STAGE_TYPE_UNDEFINED) {
-            // Going home (dismissing both splits)
-
-            // TODO: Have a proper remote for this. Until then, though, reset state and use the
-            //       normal animation stuff (which falls back to the normal launcher remote).
-            t.hide(mSplitLayout.getDividerLeash());
-            setDividerVisibility(false);
-            mSplitTransitions.mPendingDismiss = null;
-            return false;
-        }
-
-        addDividerBarToTransition(info, t, false /* show */);
-        // We're dismissing split by moving the other one to fullscreen.
-        // Since we don't have any animations for this yet, just use the internal example
-        // animations.
-        return true;
-    }
-
-    private void addDividerBarToTransition(@NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t, boolean show) {
-        final SurfaceControl leash = mSplitLayout.getDividerLeash();
-        final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash);
-        final Rect bounds = mSplitLayout.getDividerBounds();
-        barChange.setStartAbsBounds(bounds);
-        barChange.setEndAbsBounds(bounds);
-        barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
-        barChange.setFlags(FLAG_IS_DIVIDER_BAR);
-        // Technically this should be order-0, but this is running after layer assignment
-        // and it's a special case, so just add to end.
-        info.addChange(barChange);
-        // Be default, make it visible. The remote animator can adjust alpha if it plans to animate.
-        if (show) {
-            t.setAlpha(leash, 1.f);
-            t.setLayer(leash, Integer.MAX_VALUE);
-            t.setPosition(leash, bounds.left, bounds.top);
-            t.show(leash);
-        }
-    }
-
-    RemoteAnimationTarget getDividerBarLegacyTarget() {
-        final Rect bounds = mSplitLayout.getDividerBounds();
-        return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
-                mSplitLayout.getDividerLeash(), false /* isTranslucent */, null /* clipRect */,
-                null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
-                new android.graphics.Point(0, 0) /* position */, bounds, bounds,
-                new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
-                null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
-    }
-
-    RemoteAnimationTarget getOutlineLegacyTarget() {
-        final Rect bounds = mSideStage.mRootTaskInfo.configuration.windowConfiguration.getBounds();
-        // Leverage TYPE_DOCK_DIVIDER type when wrapping outline remote animation target in order to
-        // distinguish as a split auxiliary target in Launcher.
-        return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
-                mSideStage.getOutlineLeash(), false /* isTranslucent */, null /* clipRect */,
-                null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
-                new android.graphics.Point(0, 0) /* position */, bounds, bounds,
-                new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
-                null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
-    }
-
-    @Override
-    public void dump(@NonNull PrintWriter pw, String prefix) {
-        final String innerPrefix = prefix + "  ";
-        final String childPrefix = innerPrefix + "  ";
-        pw.println(prefix + TAG + " mDisplayId=" + mDisplayId);
-        pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
-        pw.println(innerPrefix + "MainStage");
-        pw.println(childPrefix + "isActive=" + mMainStage.isActive());
-        mMainStageListener.dump(pw, childPrefix);
-        pw.println(innerPrefix + "SideStage");
-        mSideStageListener.dump(pw, childPrefix);
-        pw.println(innerPrefix + "mSplitLayout=" + mSplitLayout);
-    }
-
-    /**
-     * Directly set the visibility of both splits. This assumes hasChildren matches visibility.
-     * This is intended for batch use, so it assumes other state management logic is already
-     * handled.
-     */
-    private void setSplitsVisible(boolean visible) {
-        mMainStageListener.mVisible = mSideStageListener.mVisible = visible;
-        mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible;
-    }
-
-    /**
-     * Sets drag info to be logged when splitscreen is next entered.
-     */
-    public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
-        mLogger.enterRequestedByDrag(position, dragSessionId);
-    }
-
-    /**
-     * Logs the exit of splitscreen.
-     */
-    private void logExit(int exitReason) {
-        mLogger.logExit(exitReason,
-                SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */,
-                SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */,
-                mSplitLayout.isLandscape());
-    }
-
-    /**
-     * Logs the exit of splitscreen to a specific stage. This must be called before the exit is
-     * executed.
-     */
-    private void logExitToStage(int exitReason, boolean toMainStage) {
-        mLogger.logExit(exitReason,
-                toMainStage ? getMainStagePosition() : SPLIT_POSITION_UNDEFINED,
-                toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */,
-                !toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED,
-                !toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */,
-                mSplitLayout.isLandscape());
-    }
-
-    class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
-        boolean mHasRootTask = false;
-        boolean mVisible = false;
-        boolean mHasChildren = false;
-
-        @Override
-        public void onRootTaskAppeared() {
-            mHasRootTask = true;
-            StageCoordinator.this.onStageRootTaskAppeared(this);
-        }
-
-        @Override
-        public void onStatusChanged(boolean visible, boolean hasChildren) {
-            if (!mHasRootTask) return;
-
-            if (mHasChildren != hasChildren) {
-                mHasChildren = hasChildren;
-                StageCoordinator.this.onStageHasChildrenChanged(this);
-            }
-            if (mVisible != visible) {
-                mVisible = visible;
-                StageCoordinator.this.onStageVisibilityChanged(this);
-            }
-        }
-
-        @Override
-        public void onChildTaskStatusChanged(int taskId, boolean present, boolean visible) {
-            StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible);
-        }
-
-        @Override
-        public void onRootTaskVanished() {
-            reset();
-            StageCoordinator.this.onStageRootTaskVanished(this);
-        }
-
-        @Override
-        public void onNoLongerSupportMultiWindow() {
-            if (mMainStage.isActive()) {
-                StageCoordinator.this.exitSplitScreen(null /* childrenToTop */,
-                        SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW);
-            }
-        }
-
-        private void reset() {
-            mHasRootTask = false;
-            mVisible = false;
-            mHasChildren = false;
-        }
-
-        public void dump(@NonNull PrintWriter pw, String prefix) {
-            pw.println(prefix + "mHasRootTask=" + mHasRootTask);
-            pw.println(prefix + "mVisible=" + mVisible);
-            pw.println(prefix + "mHasChildren=" + mHasChildren);
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
deleted file mode 100644
index 7b679580..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
+++ /dev/null
@@ -1,298 +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.wm.shell.stagesplit;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
-
-import android.annotation.CallSuper;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.SparseArray;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.window.WindowContainerTransaction;
-
-import androidx.annotation.NonNull;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.SurfaceUtils;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-import java.io.PrintWriter;
-
-/**
- * Base class that handle common task org. related for split-screen stages.
- * Note that this class and its sub-class do not directly perform hierarchy operations.
- * They only serve to hold a collection of tasks and provide APIs like
- * {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator}
- * to perform operations in-sync with other containers.
- *
- * @see StageCoordinator
- */
-class StageTaskListener implements ShellTaskOrganizer.TaskListener {
-    private static final String TAG = StageTaskListener.class.getSimpleName();
-
-    protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
-    protected static final int[] CONTROLLED_WINDOWING_MODES =
-            {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
-    protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
-            {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
-
-    /** Callback interface for listening to changes in a split-screen stage. */
-    public interface StageListenerCallbacks {
-        void onRootTaskAppeared();
-
-        void onStatusChanged(boolean visible, boolean hasChildren);
-
-        void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
-
-        void onRootTaskVanished();
-        void onNoLongerSupportMultiWindow();
-    }
-
-    private final StageListenerCallbacks mCallbacks;
-    private final SurfaceSession mSurfaceSession;
-    protected final SyncTransactionQueue mSyncQueue;
-
-    protected ActivityManager.RunningTaskInfo mRootTaskInfo;
-    protected SurfaceControl mRootLeash;
-    protected SurfaceControl mDimLayer;
-    protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
-    private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
-
-    private final StageTaskUnfoldController mStageTaskUnfoldController;
-
-    StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
-            StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession,
-            @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
-        mCallbacks = callbacks;
-        mSyncQueue = syncQueue;
-        mSurfaceSession = surfaceSession;
-        mStageTaskUnfoldController = stageTaskUnfoldController;
-        taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
-    }
-
-    int getChildCount() {
-        return mChildrenTaskInfo.size();
-    }
-
-    boolean containsTask(int taskId) {
-        return mChildrenTaskInfo.contains(taskId);
-    }
-
-    /**
-     * Returns the top activity uid for the top child task.
-     */
-    int getTopChildTaskUid() {
-        for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
-            final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i);
-            if (info.topActivityInfo == null) {
-                continue;
-            }
-            return info.topActivityInfo.applicationInfo.uid;
-        }
-        return 0;
-    }
-
-    /** @return {@code true} if this listener contains the currently focused task. */
-    boolean isFocused() {
-        if (mRootTaskInfo == null) {
-            return false;
-        }
-
-        if (mRootTaskInfo.isFocused) {
-            return true;
-        }
-
-        for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
-            if (mChildrenTaskInfo.valueAt(i).isFocused) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    @Override
-    @CallSuper
-    public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
-        if (mRootTaskInfo == null && !taskInfo.hasParentTask()) {
-            mRootLeash = leash;
-            mRootTaskInfo = taskInfo;
-            mCallbacks.onRootTaskAppeared();
-            sendStatusChanged();
-            mSyncQueue.runInSync(t -> {
-                t.hide(mRootLeash);
-                mDimLayer =
-                        SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession);
-            });
-        } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
-            final int taskId = taskInfo.taskId;
-            mChildrenLeashes.put(taskId, leash);
-            mChildrenTaskInfo.put(taskId, taskInfo);
-            updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
-            mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible);
-            if (ENABLE_SHELL_TRANSITIONS) {
-                // Status is managed/synchronized by the transition lifecycle.
-                return;
-            }
-            sendStatusChanged();
-        } else {
-            throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
-                    + "\n mRootTaskInfo: " + mRootTaskInfo);
-        }
-
-        if (mStageTaskUnfoldController != null) {
-            mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash);
-        }
-    }
-
-    @Override
-    @CallSuper
-    public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
-        if (!taskInfo.supportsMultiWindow) {
-            // Leave split screen if the task no longer supports multi window.
-            mCallbacks.onNoLongerSupportMultiWindow();
-            return;
-        }
-        if (mRootTaskInfo.taskId == taskInfo.taskId) {
-            mRootTaskInfo = taskInfo;
-        } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
-            mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
-            mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
-                    taskInfo.isVisible);
-            if (!ENABLE_SHELL_TRANSITIONS) {
-                updateChildTaskSurface(
-                        taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
-            }
-        } else {
-            throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
-                    + "\n mRootTaskInfo: " + mRootTaskInfo);
-        }
-        if (ENABLE_SHELL_TRANSITIONS) {
-            // Status is managed/synchronized by the transition lifecycle.
-            return;
-        }
-        sendStatusChanged();
-    }
-
-    @Override
-    @CallSuper
-    public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
-        final int taskId = taskInfo.taskId;
-        if (mRootTaskInfo.taskId == taskId) {
-            mCallbacks.onRootTaskVanished();
-            mSyncQueue.runInSync(t -> t.remove(mDimLayer));
-            mRootTaskInfo = null;
-        } else if (mChildrenTaskInfo.contains(taskId)) {
-            mChildrenTaskInfo.remove(taskId);
-            mChildrenLeashes.remove(taskId);
-            mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
-            if (ENABLE_SHELL_TRANSITIONS) {
-                // Status is managed/synchronized by the transition lifecycle.
-                return;
-            }
-            sendStatusChanged();
-        } else {
-            throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
-                    + "\n mRootTaskInfo: " + mRootTaskInfo);
-        }
-
-        if (mStageTaskUnfoldController != null) {
-            mStageTaskUnfoldController.onTaskVanished(taskInfo);
-        }
-    }
-
-    @Override
-    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
-        b.setParent(findTaskSurface(taskId));
-    }
-
-    @Override
-    public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
-            SurfaceControl.Transaction t) {
-        t.reparent(sc, findTaskSurface(taskId));
-    }
-
-    private SurfaceControl findTaskSurface(int taskId) {
-        if (mRootTaskInfo.taskId == taskId) {
-            return mRootLeash;
-        } else if (mChildrenLeashes.contains(taskId)) {
-            return mChildrenLeashes.get(taskId);
-        } else {
-            throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
-        }
-    }
-
-    void setBounds(Rect bounds, WindowContainerTransaction wct) {
-        wct.setBounds(mRootTaskInfo.token, bounds);
-    }
-
-    void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
-        if (!containsTask(taskId)) {
-            return;
-        }
-        wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
-    }
-
-    void setVisibility(boolean visible, WindowContainerTransaction wct) {
-        wct.reorder(mRootTaskInfo.token, visible /* onTop */);
-    }
-
-    void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
-            @SplitScreen.StageType int stage) {
-        for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
-            int taskId = mChildrenTaskInfo.keyAt(i);
-            listener.onTaskStageChanged(taskId, stage,
-                    mChildrenTaskInfo.get(taskId).isVisible);
-        }
-    }
-
-    private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo,
-            SurfaceControl leash, boolean firstAppeared) {
-        final Point taskPositionInParent = taskInfo.positionInParent;
-        mSyncQueue.runInSync(t -> {
-            t.setWindowCrop(leash, null);
-            t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
-            if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) {
-                t.setAlpha(leash, 1f);
-                t.setMatrix(leash, 1, 0, 0, 1);
-                t.show(leash);
-            }
-        });
-    }
-
-    private void sendStatusChanged() {
-        mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0);
-    }
-
-    @Override
-    @CallSuper
-    public void dump(@NonNull PrintWriter pw, String prefix) {
-        final String innerPrefix = prefix + "  ";
-        final String childPrefix = innerPrefix + "  ";
-        pw.println(prefix + this);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java
deleted file mode 100644
index 62b9da6..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import android.animation.RectEvaluator;
-import android.animation.TypeEvaluator;
-import android.annotation.NonNull;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.SparseArray;
-import android.view.InsetsSource;
-import android.view.InsetsState;
-import android.view.SurfaceControl;
-
-import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
-import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
-import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
-import com.android.wm.shell.unfold.UnfoldBackgroundController;
-
-import java.util.concurrent.Executor;
-
-/**
- * Controls transformations of the split screen task surfaces in response
- * to the unfolding/folding action on foldable devices
- */
-public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener {
-
-    private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
-    private static final float CROPPING_START_MARGIN_FRACTION = 0.05f;
-
-    private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
-    private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
-    private final DisplayInsetsController mDisplayInsetsController;
-    private final UnfoldBackgroundController mBackgroundController;
-    private final Executor mExecutor;
-    private final int mExpandedTaskBarHeight;
-    private final float mWindowCornerRadiusPx;
-    private final Rect mStageBounds = new Rect();
-    private final TransactionPool mTransactionPool;
-
-    private InsetsSource mTaskbarInsetsSource;
-    private boolean mBothStagesVisible;
-
-    public StageTaskUnfoldController(@NonNull Context context,
-            @NonNull TransactionPool transactionPool,
-            @NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
-            @NonNull DisplayInsetsController displayInsetsController,
-            @NonNull UnfoldBackgroundController backgroundController,
-            @NonNull Executor executor) {
-        mUnfoldProgressProvider = unfoldProgressProvider;
-        mTransactionPool = transactionPool;
-        mExecutor = executor;
-        mBackgroundController = backgroundController;
-        mDisplayInsetsController = displayInsetsController;
-        mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
-        mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.taskbar_frame_height);
-    }
-
-    /**
-     * Initializes the controller, starts listening for the external events
-     */
-    public void init() {
-        mUnfoldProgressProvider.addListener(mExecutor, this);
-        mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
-    }
-
-    @Override
-    public void insetsChanged(InsetsState insetsState) {
-        mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
-        for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
-            AnimationContext context = mAnimationContextByTaskId.valueAt(i);
-            context.update();
-        }
-    }
-
-    /**
-     * Called when split screen task appeared
-     * @param taskInfo info for the appeared task
-     * @param leash surface leash for the appeared task
-     */
-    public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
-        AnimationContext context = new AnimationContext(leash);
-        mAnimationContextByTaskId.put(taskInfo.taskId, context);
-    }
-
-    /**
-     * Called when a split screen task vanished
-     * @param taskInfo info for the vanished task
-     */
-    public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
-        AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
-        if (context != null) {
-            final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
-            resetSurface(transaction, context);
-            transaction.apply();
-            mTransactionPool.release(transaction);
-        }
-        mAnimationContextByTaskId.remove(taskInfo.taskId);
-    }
-
-    @Override
-    public void onStateChangeProgress(float progress) {
-        if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return;
-
-        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
-        mBackgroundController.ensureBackground(transaction);
-
-        for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
-            AnimationContext context = mAnimationContextByTaskId.valueAt(i);
-
-            context.mCurrentCropRect.set(RECT_EVALUATOR
-                    .evaluate(progress, context.mStartCropRect, context.mEndCropRect));
-
-            transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
-                    .setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
-        }
-
-        transaction.apply();
-
-        mTransactionPool.release(transaction);
-    }
-
-    @Override
-    public void onStateChangeFinished() {
-        resetTransformations();
-    }
-
-    /**
-     * Called when split screen visibility changes
-     * @param bothStagesVisible true if both stages of the split screen are visible
-     */
-    public void onSplitVisibilityChanged(boolean bothStagesVisible) {
-        mBothStagesVisible = bothStagesVisible;
-        if (!bothStagesVisible) {
-            resetTransformations();
-        }
-    }
-
-    /**
-     * Called when split screen stage bounds changed
-     * @param bounds new bounds for this stage
-     */
-    public void onLayoutChanged(Rect bounds) {
-        mStageBounds.set(bounds);
-
-        for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
-            final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
-            context.update();
-        }
-    }
-
-    private void resetTransformations() {
-        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
-
-        for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
-            final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
-            resetSurface(transaction, context);
-        }
-        mBackgroundController.removeBackground(transaction);
-        transaction.apply();
-
-        mTransactionPool.release(transaction);
-    }
-
-    private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) {
-        transaction
-                .setWindowCrop(context.mLeash, null)
-                .setCornerRadius(context.mLeash, 0.0F);
-    }
-
-    private class AnimationContext {
-        final SurfaceControl mLeash;
-        final Rect mStartCropRect = new Rect();
-        final Rect mEndCropRect = new Rect();
-        final Rect mCurrentCropRect = new Rect();
-
-        private AnimationContext(SurfaceControl leash) {
-            this.mLeash = leash;
-            update();
-        }
-
-        private void update() {
-            mStartCropRect.set(mStageBounds);
-
-            if (mTaskbarInsetsSource != null) {
-                // Only insets the cropping window with taskbar when taskbar is expanded
-                if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
-                    mStartCropRect.inset(mTaskbarInsetsSource
-                            .calculateVisibleInsets(mStartCropRect));
-                }
-            }
-
-            // Offset to surface coordinates as layout bounds are in screen coordinates
-            mStartCropRect.offsetTo(0, 0);
-
-            mEndCropRect.set(mStartCropRect);
-
-            int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height());
-            int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION);
-            mStartCropRect.inset(margin, margin, margin, margin);
-        }
-    }
-}
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 9154226..79e9cda 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
@@ -24,6 +24,7 @@
 import static android.app.ActivityOptions.ANIM_SCALE_UP;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -42,6 +43,7 @@
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.transitTypeToString;
 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
@@ -684,6 +686,8 @@
         final Rect endBounds = Transitions.isClosingType(changeMode)
                 ? mRotator.getEndBoundsInStartRotation(change)
                 : change.getEndAbsBounds();
+        final boolean isDream =
+                isTask && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM;
 
         if (info.isKeyguardGoingAway()) {
             a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
@@ -726,7 +730,17 @@
         } else {
             int animAttr = 0;
             boolean translucent = false;
-            if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
+            if (isDream) {
+                if (type == TRANSIT_OPEN) {
+                    animAttr = enter
+                            ? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation
+                            : R.styleable.WindowAnimation_dreamActivityOpenExitAnimation;
+                } else if (type == TRANSIT_CLOSE) {
+                    animAttr = enter
+                            ? 0
+                            : R.styleable.WindowAnimation_dreamActivityCloseExitAnimation;
+                }
+            } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
                 animAttr = enter
                         ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
                         : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
@@ -790,6 +804,11 @@
                     a = mTransitionAnimation.loadDefaultAnimationAttr(animAttr, translucent);
                 }
             }
+
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                    "loadAnimation: anim=%s animAttr=0x%x type=%s isEntrance=%b", a, animAttr,
+                    transitTypeToString(type),
+                    enter);
         }
 
         if (a != null) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 169f03e..bde94d9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -115,7 +115,7 @@
     private ArgumentCaptor<BubbleData.Update> mUpdateCaptor;
 
     @Mock
-    private Bubbles.SuppressionChangedListener mSuppressionListener;
+    private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener;
 
     @Mock
     private Bubbles.PendingIntentCanceledListener mPendingIntentCanceledListener;
@@ -136,30 +136,47 @@
                 mock(NotificationListenerService.Ranking.class);
         when(ranking.isTextChanged()).thenReturn(true);
         mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking);
-        mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null,
+        mBubbleInterruptive = new Bubble(mEntryInterruptive, mBubbleMetadataFlagListener, null,
                 mMainExecutor);
 
         mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d", null);
-        mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener, null,
+        mBubbleDismissed = new Bubble(mEntryDismissed, mBubbleMetadataFlagListener, null,
                 mMainExecutor);
 
         mEntryLocusId = createBubbleEntry(1, "keyLocus", "package.e", null,
                 new LocusId("locusId1"));
-        mBubbleLocusId = new Bubble(mEntryLocusId, mSuppressionListener, null, mMainExecutor);
+        mBubbleLocusId = new Bubble(mEntryLocusId,
+                mBubbleMetadataFlagListener,
+                null /* pendingIntentCanceledListener */,
+                mMainExecutor);
 
-        mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener, mPendingIntentCanceledListener,
+        mBubbleA1 = new Bubble(mEntryA1,
+                mBubbleMetadataFlagListener,
+                mPendingIntentCanceledListener,
                 mMainExecutor);
-        mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener, mPendingIntentCanceledListener,
+        mBubbleA2 = new Bubble(mEntryA2,
+                mBubbleMetadataFlagListener,
+                mPendingIntentCanceledListener,
                 mMainExecutor);
-        mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener, mPendingIntentCanceledListener,
+        mBubbleA3 = new Bubble(mEntryA3,
+                mBubbleMetadataFlagListener,
+                mPendingIntentCanceledListener,
                 mMainExecutor);
-        mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener, mPendingIntentCanceledListener,
+        mBubbleB1 = new Bubble(mEntryB1,
+                mBubbleMetadataFlagListener,
+                mPendingIntentCanceledListener,
                 mMainExecutor);
-        mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener, mPendingIntentCanceledListener,
+        mBubbleB2 = new Bubble(mEntryB2,
+                mBubbleMetadataFlagListener,
+                mPendingIntentCanceledListener,
                 mMainExecutor);
-        mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener, mPendingIntentCanceledListener,
+        mBubbleB3 = new Bubble(mEntryB3,
+                mBubbleMetadataFlagListener,
+                mPendingIntentCanceledListener,
                 mMainExecutor);
-        mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener,
+        mBubbleC1 = new Bubble(mEntryC1,
+                mBubbleMetadataFlagListener,
+                mPendingIntentCanceledListener,
                 mMainExecutor);
         mPositioner = new TestableBubblePositioner(mContext,
                 mock(WindowManager.class));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
index 819a984..e8f3f69 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
@@ -63,7 +63,7 @@
     private Bubble mBubble;
 
     @Mock
-    private Bubbles.SuppressionChangedListener mSuppressionListener;
+    private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener;
 
     @Before
     public void setUp() {
@@ -81,7 +81,7 @@
         when(mNotif.getBubbleMetadata()).thenReturn(metadata);
         when(mSbn.getKey()).thenReturn("mock");
         mBubbleEntry = new BubbleEntry(mSbn, null, true, false, false, false);
-        mBubble = new Bubble(mBubbleEntry, mSuppressionListener, null, mMainExecutor);
+        mBubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor);
     }
 
     @Test
@@ -144,22 +144,22 @@
     }
 
     @Test
-    public void testSuppressionListener_change_notified() {
+    public void testBubbleMetadataFlagListener_change_notified() {
         assertThat(mBubble.showInShade()).isTrue();
 
         mBubble.setSuppressNotification(true);
 
         assertThat(mBubble.showInShade()).isFalse();
 
-        verify(mSuppressionListener).onBubbleNotificationSuppressionChange(mBubble);
+        verify(mBubbleMetadataFlagListener).onBubbleMetadataFlagChanged(mBubble);
     }
 
     @Test
-    public void testSuppressionListener_noChange_doesntNotify() {
+    public void testBubbleMetadataFlagListener_noChange_doesntNotify() {
         assertThat(mBubble.showInShade()).isTrue();
 
         mBubble.setSuppressNotification(false);
 
-        verify(mSuppressionListener, never()).onBubbleNotificationSuppressionChange(any());
+        verify(mBubbleMetadataFlagListener, never()).onBubbleMetadataFlagChanged(any());
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 5368b7d..bf08261 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -75,7 +75,6 @@
     @Mock private PhonePipMenuController mMockPhonePipMenuController;
     @Mock private PipAppOpsListener mMockPipAppOpsListener;
     @Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
-    @Mock private PipKeepClearAlgorithm mMockPipKeepClearAlgorithm;
     @Mock private PipSnapAlgorithm mMockPipSnapAlgorithm;
     @Mock private PipMediaController mMockPipMediaController;
     @Mock private PipTaskOrganizer mMockPipTaskOrganizer;
@@ -100,12 +99,12 @@
             return null;
         }).when(mMockExecutor).execute(any());
         mPipController = new PipController(mContext, mMockDisplayController,
-                mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
-                mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
+                mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
+                mMockPipBoundsState, mMockPipMediaController,
                 mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
                 mMockPipTransitionController, mMockWindowManagerShellWrapper,
-                mMockTaskStackListener, mPipParamsChangedForwarder, mMockOneHandedController,
-                mMockExecutor);
+                mMockTaskStackListener, mPipParamsChangedForwarder,
+                mMockOneHandedController, mMockExecutor);
         when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
         when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
     }
@@ -133,12 +132,12 @@
         when(spyContext.getPackageManager()).thenReturn(mockPackageManager);
 
         assertNull(PipController.create(spyContext, mMockDisplayController,
-                mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
-                mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
+                mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
+                mMockPipBoundsState, mMockPipMediaController,
                 mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
                 mMockPipTransitionController, mMockWindowManagerShellWrapper,
-                mMockTaskStackListener, mPipParamsChangedForwarder, mMockOneHandedController,
-                mMockExecutor));
+                mMockTaskStackListener, mPipParamsChangedForwarder,
+                mMockOneHandedController, mMockExecutor));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java
deleted file mode 100644
index f657b5e..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip.phone;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
-import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Set;
-
-/**
- * Unit tests against {@link PipKeepClearAlgorithm}.
- */
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class PipKeepClearAlgorithmTest extends ShellTestCase {
-
-    private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
-
-
-    @Before
-    public void setUp() throws Exception {
-        mPipKeepClearAlgorithm = new PipKeepClearAlgorithm();
-    }
-
-    @Test
-    public void adjust_withCollidingRestrictedKeepClearAreas_movesBounds() {
-        final Rect inBounds = new Rect(0, 0, 100, 100);
-        final Rect keepClearRect = new Rect(50, 50, 150, 150);
-
-        final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
-                Set.of());
-
-        assertFalse(outBounds.contains(keepClearRect));
-    }
-
-    @Test
-    public void adjust_withNonCollidingRestrictedKeepClearAreas_boundsDoNotChange() {
-        final Rect inBounds = new Rect(0, 0, 100, 100);
-        final Rect keepClearRect = new Rect(100, 100, 150, 150);
-
-        final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
-                Set.of());
-
-        assertEquals(inBounds, outBounds);
-    }
-
-    @Test
-    public void adjust_withCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
-        // TODO(b/183746978): update this test to accommodate for the updated algorithm
-        final Rect inBounds = new Rect(0, 0, 100, 100);
-        final Rect keepClearRect = new Rect(50, 50, 150, 150);
-
-        final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
-                Set.of(keepClearRect));
-
-        assertEquals(inBounds, outBounds);
-    }
-
-    @Test
-    public void adjust_withNonCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
-        final Rect inBounds = new Rect(0, 0, 100, 100);
-        final Rect keepClearRect = new Rect(100, 100, 150, 150);
-
-        final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
-                Set.of(keepClearRect));
-
-        assertEquals(inBounds, outBounds);
-    }
-}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index e8a1a5c..f333b86 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -150,8 +150,6 @@
         mBtAdapter = mBtManager.getAdapter();
         mBleScanner = mBtAdapter.getBluetoothLeScanner();
         mWifiManager = getSystemService(WifiManager.class);
-
-        sScanResultsLiveData.setValue(Collections.emptyList());
     }
 
     @Override
@@ -175,6 +173,7 @@
 
     @Override
     public void onDestroy() {
+        sScanResultsLiveData.setValue(Collections.emptyList());
         super.onDestroy();
         if (DEBUG) Log.d(TAG, "onDestroy()");
     }
@@ -188,6 +187,7 @@
         mStopAfterFirstMatch = request.isSingleDevice();
         mDiscoveryStarted = true;
         sStateLiveData.setValue(DiscoveryState.DISCOVERY_IN_PROGRESS);
+        sScanResultsLiveData.setValue(Collections.emptyList());
 
         final List<DeviceFilter<?>> allFilters = request.getDeviceFilters();
         final List<BluetoothDeviceFilter> btFilters =
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index bdeb474..e6160bb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -16,10 +16,14 @@
 
 package com.android.settingslib;
 
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY;
+
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
 import android.annotation.NonNull;
 import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.os.Process;
@@ -37,6 +41,8 @@
 import androidx.preference.PreferenceViewHolder;
 import androidx.preference.SwitchPreference;
 
+import com.android.settingslib.utils.BuildCompatUtils;
+
 /**
  * Version of SwitchPreference that can be disabled by a device admin
  * using a user restriction.
@@ -117,8 +123,13 @@
 
         CharSequence switchSummary;
         if (mRestrictedSwitchSummary == null) {
-            switchSummary = getContext().getText(isChecked()
-                    ? R.string.enabled_by_admin : R.string.disabled_by_admin);
+            switchSummary = isChecked()
+                    ? getUpdatableEnterpriseString(
+                            getContext(), ENABLED_BY_ADMIN_SWITCH_SUMMARY,
+                            R.string.enabled_by_admin)
+                    : getUpdatableEnterpriseString(
+                            getContext(), DISABLED_BY_ADMIN_SWITCH_SUMMARY,
+                            R.string.disabled_by_admin);
         } else {
             switchSummary = mRestrictedSwitchSummary;
         }
@@ -153,6 +164,16 @@
         }
     }
 
+    private static String getUpdatableEnterpriseString(
+            Context context, String updatableStringId, int resId) {
+        if (!BuildCompatUtils.isAtLeastT()) {
+            return context.getString(resId);
+        }
+        return context.getSystemService(DevicePolicyManager.class).getResources().getString(
+                updatableStringId,
+                () -> context.getString(resId));
+    }
+
     @Override
     public void performClick() {
         if (!mHelper.performClick()) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index c248fff..b0392be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -26,6 +26,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeBroadcastAssistant;
 import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothProfile.ServiceListener;
 import android.content.Context;
@@ -202,6 +203,43 @@
         mService.startSearchingForSources(filters);
     }
 
+    /**
+     * Return true if a search has been started by this application.
+     *
+     * @return true if a search has been started by this application
+     * @hide
+     */
+    public boolean isSearchInProgress() {
+        if (DEBUG) {
+            Log.d(TAG, "isSearchInProgress()");
+        }
+        if (mService == null) {
+            Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+            return false;
+        }
+        return mService.isSearchInProgress();
+    }
+
+    /**
+     * Get information about all Broadcast Sources that a Broadcast Sink knows about.
+     *
+     * @param sink Broadcast Sink from which to get all Broadcast Sources
+     * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState}
+     *         stored in the Broadcast Sink
+     * @throws NullPointerException when <var>sink</var> is null
+     */
+    public @NonNull List<BluetoothLeBroadcastReceiveState> getAllSources(
+            @NonNull BluetoothDevice sink) {
+        if (DEBUG) {
+            Log.d(TAG, "getAllSources()");
+        }
+        if (mService == null) {
+            Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+            return new ArrayList<BluetoothLeBroadcastReceiveState>();
+        }
+        return mService.getAllSources(sink);
+    }
+
     public void registerServiceCallBack(@NonNull @CallbackExecutor Executor executor,
             @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
         if (mService == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
index 5b1fefb..aff9a6e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
@@ -25,6 +25,7 @@
 import android.bluetooth.BluetoothLeBroadcastSubgroup;
 import android.util.Log;
 
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -95,6 +96,7 @@
     }
 
     public String convertToQrCodeString() {
+        String subgroupString = convertSubgroupToString(mSubgroupList);
         return new StringBuilder()
                 .append(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA)
                 .append(BluetoothBroadcastUtils.PREFIX_BT_ADDRESS_TYPE)
@@ -122,11 +124,83 @@
                 .append(METADATA_START).append(mPresentationDelayMicros).append(METADATA_END)
                 .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
                 .append(BluetoothBroadcastUtils.PREFIX_BT_SUBGROUPS)
-                .append(METADATA_START).append(mSubgroupList).append(METADATA_END)
+                .append(METADATA_START).append(subgroupString).append(METADATA_END)
                 .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
                 .toString();
     }
 
+    private String convertSubgroupToString(List<BluetoothLeBroadcastSubgroup> subgroupList) {
+        StringBuilder subgroupListBuilder = new StringBuilder();
+        String subgroupString = "";
+        for (BluetoothLeBroadcastSubgroup subgroup: subgroupList) {
+            String audioCodec = convertAudioCodecConfigToString(subgroup.getCodecSpecificConfig());
+            String audioContent = convertAudioContentToString(subgroup.getContentMetadata());
+            String channels = convertChannelToString(subgroup.getChannels());
+            subgroupString = new StringBuilder()
+                    .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_ID)
+                    .append(METADATA_START).append(subgroup.getCodecId()).append(METADATA_END)
+                    .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                    .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_CONFIG)
+                    .append(METADATA_START).append(audioCodec).append(METADATA_END)
+                    .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                    .append(BluetoothBroadcastUtils.PREFIX_BTSG_AUDIO_CONTENT)
+                    .append(METADATA_START).append(audioContent).append(METADATA_END)
+                    .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                    .append(BluetoothBroadcastUtils.PREFIX_BTSG_BROADCAST_CHANNEL)
+                    .append(METADATA_START).append(channels).append(METADATA_END)
+                    .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                    .toString();
+            subgroupListBuilder.append(subgroupString);
+        }
+        return subgroupListBuilder.toString();
+    }
+
+    private String convertAudioCodecConfigToString(BluetoothLeAudioCodecConfigMetadata config) {
+        String audioLocation = String.valueOf(config.getAudioLocation());
+        String rawMetadata = new String(config.getRawMetadata(), StandardCharsets.UTF_8);
+        return new StringBuilder()
+            .append(BluetoothBroadcastUtils.PREFIX_BTCC_AUDIO_LOCATION)
+            .append(METADATA_START).append(audioLocation).append(METADATA_END)
+            .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+            .append(BluetoothBroadcastUtils.PREFIX_BTCC_RAW_METADATA)
+            .append(METADATA_START).append(rawMetadata).append(METADATA_END)
+            .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+            .toString();
+    }
+
+    private String convertAudioContentToString(BluetoothLeAudioContentMetadata audioContent) {
+        String rawMetadata = new String(audioContent.getRawMetadata(), StandardCharsets.UTF_8);
+        return new StringBuilder()
+            .append(BluetoothBroadcastUtils.PREFIX_BTAC_PROGRAM_INFO)
+            .append(METADATA_START).append(audioContent.getProgramInfo()).append(METADATA_END)
+            .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+            .append(BluetoothBroadcastUtils.PREFIX_BTAC_LANGUAGE)
+            .append(METADATA_START).append(audioContent.getLanguage()).append(METADATA_END)
+            .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+            .append(BluetoothBroadcastUtils.PREFIX_BTAC_RAW_METADATA)
+            .append(METADATA_START).append(rawMetadata).append(METADATA_END)
+            .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+            .toString();
+    }
+
+    private String convertChannelToString(List<BluetoothLeBroadcastChannel> channelList) {
+        StringBuilder channelListBuilder = new StringBuilder();
+        String channelString = "";
+        for (BluetoothLeBroadcastChannel channel: channelList) {
+            String channelAudioCodec = convertAudioCodecConfigToString(channel.getCodecMetadata());
+            channelString = new StringBuilder()
+                .append(BluetoothBroadcastUtils.PREFIX_BTBC_CHANNEL_INDEX)
+                .append(METADATA_START).append(channel.getChannelIndex()).append(METADATA_END)
+                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                .append(BluetoothBroadcastUtils.PREFIX_BTBC_CODEC_CONFIG)
+                .append(METADATA_START).append(channelAudioCodec).append(METADATA_END)
+                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                .toString();
+            channelListBuilder.append(channelString);
+        }
+        return channelListBuilder.toString();
+    }
+
     /**
      * Example : prefix is with the “BT:”, and end by the Android Version.
      * BT:T:<1>;D:<00:11:22:AA:BB:CC>;AS:<1>;B:…;V:T;;
diff --git a/packages/SystemUI/res-product/values/strings.xml b/packages/SystemUI/res-product/values/strings.xml
index c1e81ba..b71caef 100644
--- a/packages/SystemUI/res-product/values/strings.xml
+++ b/packages/SystemUI/res-product/values/strings.xml
@@ -130,4 +130,8 @@
     <!-- Text shown when viewing global actions while phone is locked and additional controls are hidden [CHAR LIMIT=NONE] -->
     <string name="global_action_lock_message" product="device">Unlock your device for more options</string>
 
+    <!-- Text informing the user that their media is now playing on this device. [CHAR LIMIT=50] -->
+    <string name="media_transfer_playing_this_device" product="default">Playing on this phone</string>
+    <!-- Text informing the user that their media is now playing on this tablet device. [CHAR LIMIT=50] -->
+    <string name="media_transfer_playing_this_device" product="tablet">Playing on this tablet</string>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9ac76c7..8d92b26 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2255,11 +2255,7 @@
     <string name="media_move_closer_to_end_cast">Move closer to <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g> to play here</string>
     <!-- Text informing the user that their media is now playing on a different device (deviceName). [CHAR LIMIT=50] -->
     <string name="media_transfer_playing_different_device">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
-    <!-- Text informing the user that their media is now playing on this device. [CHAR LIMIT=50] -->
-    <string name="media_transfer_playing_this_device" product="default">Playing on this phone</string>
-    <!-- Text informing the user that their media is now playing on this tablet device. [CHAR LIMIT=50] -->
-    <string name="media_transfer_playing_this_device" product="tablet">Playing on this tablet</string>
-    <!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIMIT=50] -->
+    <!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIsMIT=50] -->
     <string name="media_transfer_failed">Something went wrong. Try again.</string>
 
     <!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 6a68c70..98ac640 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -28,7 +28,6 @@
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -169,7 +168,7 @@
         private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
         private final FalsingCollector mFalsingCollector;
         private final DevicePostureController mDevicePostureController;
-        private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+        private final KeyguardViewController mKeyguardViewController;
 
         @Inject
         public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -181,7 +180,7 @@
                 TelephonyManager telephonyManager, FalsingCollector falsingCollector,
                 EmergencyButtonController.Factory emergencyButtonControllerFactory,
                 DevicePostureController devicePostureController,
-                StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+                KeyguardViewController keyguardViewController) {
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
             mLatencyTracker = latencyTracker;
@@ -194,7 +193,7 @@
             mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
             mFalsingCollector = falsingCollector;
             mDevicePostureController = devicePostureController;
-            mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+            mKeyguardViewController = keyguardViewController;
         }
 
         /** Create a new {@link KeyguardInputViewController}. */
@@ -215,7 +214,7 @@
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
-                        mFalsingCollector, mStatusBarKeyguardViewManager);
+                        mFalsingCollector, mKeyguardViewController);
 
             } else if (keyguardInputView instanceof KeyguardPINView) {
                 return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 1903526..8f44e97 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -43,7 +43,6 @@
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.List;
@@ -56,7 +55,7 @@
     private final KeyguardSecurityCallback mKeyguardSecurityCallback;
     private final InputMethodManager mInputMethodManager;
     private final DelayableExecutor mMainExecutor;
-    private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private final KeyguardViewController mKeyguardViewController;
     private final boolean mShowImeAtScreenOn;
     private EditText mPasswordEntry;
     private ImageView mSwitchImeButton;
@@ -119,14 +118,14 @@
             @Main DelayableExecutor mainExecutor,
             @Main Resources resources,
             FalsingCollector falsingCollector,
-            StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+            KeyguardViewController keyguardViewController) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, falsingCollector,
                 emergencyButtonController);
         mKeyguardSecurityCallback = keyguardSecurityCallback;
         mInputMethodManager = inputMethodManager;
         mMainExecutor = mainExecutor;
-        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mKeyguardViewController = keyguardViewController;
         mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on);
         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
         mSwitchImeButton = mView.findViewById(R.id.switch_ime_button);
@@ -209,7 +208,7 @@
     }
 
     private void showInput() {
-        if (!mStatusBarKeyguardViewManager.isBouncerShowing()) {
+        if (!mKeyguardViewController.isBouncerShowing()) {
             return;
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index caf7ee4..c91c899 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -30,7 +30,6 @@
 
 import androidx.annotation.StyleRes;
 
-import com.android.settingslib.Utils;
 import com.android.systemui.animation.Interpolators;
 
 /**
@@ -42,24 +41,29 @@
     private ValueAnimator mContractAnimator;
     private AnimatorSet mContractAnimatorSet;
     private GradientDrawable mBackground;
-    private int mNormalColor;
-    private int mHighlightColor;
-    private int mStyle;
+    private Drawable mImageButton;
     private TextView mDigitTextView;
+    private int mNormalBackgroundColor;
+    private int mPressedBackgroundColor;
+    private int mTextColorPrimary;
+    private int mTextColorPressed;
+    private int mStyle;
     private static final int EXPAND_ANIMATION_MS = 100;
     private static final int EXPAND_COLOR_ANIMATION_MS = 50;
     private static final int CONTRACT_ANIMATION_DELAY_MS = 33;
     private static final int CONTRACT_ANIMATION_MS = 417;
 
-    NumPadAnimator(Context context, final Drawable drawable, @StyleRes int style) {
-        this(context, drawable, style, null);
+    NumPadAnimator(Context context, final Drawable drawable,
+            @StyleRes int style, Drawable buttonImage) {
+        this(context, drawable, style, null, buttonImage);
     }
 
     NumPadAnimator(Context context, final Drawable drawable, @StyleRes int style,
-            @Nullable TextView digitTextView) {
+            @Nullable TextView digitTextView, @Nullable Drawable buttonImage) {
         mStyle = style;
         mBackground = (GradientDrawable) drawable;
         mDigitTextView = digitTextView;
+        mImageButton = buttonImage;
 
         reloadColors(context);
     }
@@ -88,26 +92,28 @@
      * Reload colors from resources.
      **/
     void reloadColors(Context context) {
-        int[] customAttrs = {android.R.attr.colorControlNormal,
-                android.R.attr.colorControlHighlight};
+        boolean isNumPadKey = mImageButton == null;
 
+        int[] customAttrs = {android.R.attr.colorControlNormal};
         ContextThemeWrapper ctw = new ContextThemeWrapper(context, mStyle);
         TypedArray a = ctw.obtainStyledAttributes(customAttrs);
-        mNormalColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0,
+        mNormalBackgroundColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0,
                 com.android.internal.R.attr.colorSurface);
-        mHighlightColor = a.getColor(1, 0);
         a.recycle();
+        mBackground.setColor(mNormalBackgroundColor);
 
-        mBackground.setColor(mNormalColor);
-        createAnimators(context);
+        mPressedBackgroundColor = context.getColor(android.R.color.system_accent1_200);
+        mTextColorPrimary = isNumPadKey
+                ? com.android.settingslib.Utils
+                .getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
+                : com.android.settingslib.Utils
+                        .getColorAttrDefaultColor(context, android.R.attr.textColorPrimaryInverse);
+        mTextColorPressed = com.android.settingslib.Utils
+                .getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorOnAccent);
+        createAnimators();
     }
 
-    private void createAnimators(Context context) {
-        int textColorPrimary = Utils.getColorAttrDefaultColor(context,
-                android.R.attr.textColorPrimary);
-        int textColorPrimaryInverse = Utils.getColorAttrDefaultColor(context,
-                android.R.attr.textColorPrimaryInverse);
-
+    private void createAnimators() {
         // Actual values will be updated later, usually during an onLayout() call
         mExpandAnimator = ValueAnimator.ofFloat(0f, 1f);
         mExpandAnimator.setDuration(EXPAND_ANIMATION_MS);
@@ -116,7 +122,7 @@
                 anim -> mBackground.setCornerRadius((float) anim.getAnimatedValue()));
 
         ValueAnimator expandBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(),
-                mNormalColor, mHighlightColor);
+                mNormalBackgroundColor, mPressedBackgroundColor);
         expandBackgroundColorAnimator.setDuration(EXPAND_COLOR_ANIMATION_MS);
         expandBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR);
         expandBackgroundColorAnimator.addUpdateListener(
@@ -124,13 +130,16 @@
 
         ValueAnimator expandTextColorAnimator =
                 ValueAnimator.ofObject(new ArgbEvaluator(),
-                textColorPrimary, textColorPrimaryInverse);
+                mTextColorPrimary, mTextColorPressed);
         expandTextColorAnimator.setInterpolator(Interpolators.LINEAR);
         expandTextColorAnimator.setDuration(EXPAND_COLOR_ANIMATION_MS);
         expandTextColorAnimator.addUpdateListener(valueAnimator -> {
             if (mDigitTextView != null) {
                 mDigitTextView.setTextColor((int) valueAnimator.getAnimatedValue());
             }
+            if (mImageButton != null) {
+                mImageButton.setTint((int) valueAnimator.getAnimatedValue());
+            }
         });
 
         mExpandAnimatorSet = new AnimatorSet();
@@ -144,7 +153,7 @@
         mContractAnimator.addUpdateListener(
                 anim -> mBackground.setCornerRadius((float) anim.getAnimatedValue()));
         ValueAnimator contractBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(),
-                mHighlightColor, mNormalColor);
+                mPressedBackgroundColor, mNormalBackgroundColor);
         contractBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR);
         contractBackgroundColorAnimator.setStartDelay(CONTRACT_ANIMATION_DELAY_MS);
         contractBackgroundColorAnimator.setDuration(CONTRACT_ANIMATION_MS);
@@ -152,8 +161,8 @@
                 animator -> mBackground.setColor((int) animator.getAnimatedValue()));
 
         ValueAnimator contractTextColorAnimator =
-                ValueAnimator.ofObject(new ArgbEvaluator(), textColorPrimaryInverse,
-                textColorPrimary);
+                ValueAnimator.ofObject(new ArgbEvaluator(), mTextColorPressed,
+                mTextColorPrimary);
         contractTextColorAnimator.setInterpolator(Interpolators.LINEAR);
         contractTextColorAnimator.setStartDelay(CONTRACT_ANIMATION_DELAY_MS);
         contractTextColorAnimator.setDuration(CONTRACT_ANIMATION_MS);
@@ -161,6 +170,9 @@
             if (mDigitTextView != null) {
                 mDigitTextView.setTextColor((int) valueAnimator.getAnimatedValue());
             }
+            if (mImageButton != null) {
+                mImageButton.setTint((int) valueAnimator.getAnimatedValue());
+            }
         });
 
         mContractAnimatorSet = new AnimatorSet();
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 7b98f27..8099f75 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -42,7 +42,7 @@
         Drawable background = getBackground();
         if (background instanceof GradientDrawable) {
             mAnimator = new NumPadAnimator(context, background.mutate(),
-                    attrs.getStyleAttribute());
+                    attrs.getStyleAttribute(), getDrawable());
         } else {
             mAnimator = null;
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index f771c97..4aed251 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -134,7 +134,7 @@
         Drawable background = getBackground();
         if (background instanceof GradientDrawable) {
             mAnimator = new NumPadAnimator(context, background.mutate(),
-                    R.style.NumPadKey, mDigitText);
+                    R.style.NumPadKey, mDigitText, null);
         } else {
             mAnimator = null;
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
index 6e06130..334bb1e 100644
--- a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
@@ -76,12 +76,6 @@
     void playTrustedSound();
 
     /**
-     * When the bouncer is shown or hides
-     * @param shown
-     */
-    void onBouncerVisiblityChanged(boolean shown);
-
-    /**
      * @return true if the screen is on
      */
     boolean isScreenOn();
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 8d65098..cbce854 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -899,8 +899,8 @@
         final DisplayInfo displayInfo = new DisplayInfo();
         mContext.getDisplay().getDisplayInfo(displayInfo);
         return DisplayUtils.getPhysicalPixelDisplaySizeRatio(
-                stableDisplaySize.x, stableDisplaySize.y, displayInfo.logicalWidth,
-                displayInfo.logicalHeight);
+                stableDisplaySize.x, stableDisplaySize.y, displayInfo.getNaturalWidth(),
+                displayInfo.getNaturalHeight());
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index d59ad92..9324893 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -278,6 +278,7 @@
                 }
             });
             mUdfpsController.setAuthControllerUpdateUdfpsLocation(this::updateUdfpsLocation);
+            mUdfpsController.setHalControlsIllumination(mUdfpsProps.get(0).halControlsIllumination);
             mUdfpsBounds = mUdfpsProps.get(0).getLocation().getRect();
             updateUdfpsLocation();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 76c1dbc..d9aa1ba 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -80,6 +80,7 @@
 
     @VisibleForTesting
     internal var startLightRevealScrimOnKeyguardFadingAway = false
+    var lightRevealScrimAnimator: ValueAnimator? = null
     var fingerprintSensorLocation: PointF? = null
     private var faceSensorLocation: PointF? = null
     private var circleReveal: LightRevealEffect? = null
@@ -163,7 +164,8 @@
         if (keyguardStateController.isKeyguardFadingAway) {
             val lightRevealScrim = centralSurfaces.lightRevealScrim
             if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) {
-                ValueAnimator.ofFloat(.1f, 1f).apply {
+                lightRevealScrimAnimator?.cancel()
+                lightRevealScrimAnimator = ValueAnimator.ofFloat(.1f, 1f).apply {
                     interpolator = Interpolators.LINEAR_OUT_SLOW_IN
                     duration = RIPPLE_ANIMATION_DURATION
                     startDelay = keyguardStateController.keyguardFadingAwayDelay
@@ -183,6 +185,8 @@
                             if (lightRevealScrim.revealEffect == circleReveal) {
                                 lightRevealScrim.revealEffect = LiftReveal
                             }
+
+                            lightRevealScrimAnimator = null
                         }
                     })
                     start()
@@ -192,6 +196,13 @@
         }
     }
 
+    /**
+     * Whether we're animating the light reveal scrim from a call to [onKeyguardFadingAwayChanged].
+     */
+    fun isAnimatingLightRevealScrim(): Boolean {
+        return lightRevealScrimAnimator?.isRunning ?: false
+    }
+
     override fun onStartedGoingToSleep() {
         // reset the light reveal start in case we were pending an unlock
         startLightRevealScrimOnKeyguardFadingAway = false
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 7742525..471240e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -131,6 +131,7 @@
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
     // sensors, this, in addition to a lot of the code here, will be updated.
     @VisibleForTesting int mSensorId;
+    private boolean mHalControlsIllumination;
     @VisibleForTesting @NonNull UdfpsOverlayParams mOverlayParams = new UdfpsOverlayParams();
     // TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this.
     @Nullable private Runnable mAuthControllerUpdateUdfpsLocation;
@@ -201,9 +202,10 @@
                             mKeyguardUpdateMonitor, mDialogManager, mDumpManager,
                             mLockscreenShadeTransitionController, mConfigurationController,
                             mSystemClock, mKeyguardStateController,
-                            mUnlockedScreenOffAnimationController, mHbmProvider, requestId, reason,
-                            callback, (view, event, fromUdfpsView) -> onTouch(requestId, event,
-                            fromUdfpsView), mActivityLaunchAnimator)));
+                            mUnlockedScreenOffAnimationController, mHalControlsIllumination,
+                            mHbmProvider, requestId, reason, callback,
+                            (view, event, fromUdfpsView) -> onTouch(requestId, event,
+                                    fromUdfpsView), mActivityLaunchAnimator)));
         }
 
         @Override
@@ -310,6 +312,11 @@
         mAuthControllerUpdateUdfpsLocation = r;
     }
 
+    // TODO(b/229290039): UDFPS controller should manage its properties on its own. Remove this.
+    public void setHalControlsIllumination(boolean value) {
+        mHalControlsIllumination = value;
+    }
+
     /**
      * Calculate the pointer speed given a velocity tracker and the pointer id.
      * This assumes that the velocity tracker has already been passed all relevant motion events.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 9c8aee4..2d51c97 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -77,6 +77,7 @@
     private val systemClock: SystemClock,
     private val keyguardStateController: KeyguardStateController,
     private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+    private val halControlsIllumination: Boolean,
     private var hbmProvider: UdfpsHbmProvider,
     val requestId: Long,
     @ShowReason val requestReason: Int,
@@ -137,6 +138,7 @@
                     R.layout.udfps_view, null, false
                 ) as UdfpsView).apply {
                     overlayParams = params
+                    halControlsIllumination = this@UdfpsControllerOverlay.halControlsIllumination
                     setHbmProvider(hbmProvider)
                     val animation = inflateUdfpsAnimation(this, controller)
                     if (animation != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
index 38c937f..f26dd5f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
@@ -34,8 +34,11 @@
      * invoked from the UI thread.
      *
      * @param onHbmEnabled A runnable that will be executed once HBM is enabled.
+     *
+     * TODO(b/231335067): enableHbm with halControlsIllumination=true shouldn't make sense.
+     *     This only makes sense now because vendor code may rely on the side effects of enableHbm.
      */
-    void enableHbm(@Nullable Runnable onHbmEnabled);
+    void enableHbm(boolean halControlsIllumination, @Nullable Runnable onHbmEnabled);
 
     /**
      * UdfpsView will call this to disable HBM when illumination is no longer needed.
@@ -46,8 +49,6 @@
      * The call must be made from the UI thread. The callback, if provided, will also be invoked
      * from the UI thread.
      *
-     *
-     *
      * @param onHbmDisabled A runnable that will be executed once HBM is disabled.
      */
     void disableHbm(@Nullable Runnable onHbmDisabled);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index 2aa345a..245c225 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -63,9 +63,12 @@
     /** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */
     var animationViewController: UdfpsAnimationViewController<*>? = null
 
-    /** Parameters that affect the position and size of the overlay. Visible for testing. */
+    /** Parameters that affect the position and size of the overlay. */
     var overlayParams = UdfpsOverlayParams()
 
+    /** Whether the HAL is responsible for enabling and disabling of LHBM. */
+    var halControlsIllumination: Boolean = true
+
     /** Debug message. */
     var debugMessage: String? = null
         set(value) {
@@ -154,11 +157,17 @@
     }
 
     private fun doIlluminate(onIlluminatedRunnable: Runnable?) {
-        hbmProvider?.enableHbm() {
+        // TODO(b/231335067): enableHbm with halControlsIllumination=true shouldn't make sense.
+        // This only makes sense now because vendor code may rely on the side effects of enableHbm.
+        hbmProvider?.enableHbm(halControlsIllumination) {
             if (onIlluminatedRunnable != null) {
-                // No framework API can reliably tell when a frame reaches the panel. A timeout
-                // is the safest solution.
-                postDelayed(onIlluminatedRunnable, onIlluminatedDelayMs)
+                if (halControlsIllumination) {
+                    onIlluminatedRunnable.run()
+                } else {
+                    // No framework API can reliably tell when a frame reaches the panel. A timeout
+                    // is the safest solution.
+                    postDelayed(onIlluminatedRunnable, onIlluminatedDelayMs)
+                }
             } else {
                 Log.w(TAG, "doIlluminate | onIlluminatedRunnable is null")
             }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index f9115b2..3eb58bb 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -343,7 +343,7 @@
         info.className = Switch::class.java.name
     }
 
-    override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+    override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
         if (super.performAccessibilityAction(host, action, args)) {
             return true
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index fb09132..9aebb9d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -813,6 +813,11 @@
             return false
         }
 
+        // The smartspace is not visible if the bouncer is showing, so don't shared element it.
+        if (keyguardStateController.isBouncerShowing) {
+            return false
+        }
+
         // We started to swipe to dismiss, but now we're doing a fling animation to complete the
         // dismiss. In this case, the smartspace swiped away with the rest of the keyguard, so don't
         // do the shared element transition.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 10ea1e0..7becc82 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -779,16 +779,6 @@
         }
 
         @Override
-        public void onBouncerVisiblityChanged(boolean shown) {
-            synchronized (KeyguardViewMediator.this) {
-                if (shown) {
-                    mPendingPinLock = false;
-                }
-                adjustStatusBarLocked(shown, false);
-            }
-        }
-
-        @Override
         public void playTrustedSound() {
             KeyguardViewMediator.this.playTrustedSound();
         }
@@ -989,6 +979,19 @@
     private DozeParameters mDozeParameters;
 
     private final KeyguardStateController mKeyguardStateController;
+    private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
+            new KeyguardStateController.Callback() {
+        @Override
+        public void onBouncerShowingChanged() {
+            synchronized (KeyguardViewMediator.this) {
+                if (mKeyguardStateController.isBouncerShowing()) {
+                    mPendingPinLock = false;
+                }
+                adjustStatusBarLocked(mKeyguardStateController.isBouncerShowing(), false);
+            }
+        }
+    };
+
     private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private boolean mWallpaperSupportsAmbientMode;
@@ -1059,6 +1062,7 @@
         statusBarStateController.addCallback(this);
 
         mKeyguardStateController = keyguardStateController;
+        keyguardStateController.addCallback(mKeyguardStateControllerCallback);
         mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy;
         mScreenOffAnimationController = screenOffAnimationController;
         mInteractionJankMonitor = interactionJankMonitor;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index e7f97d2..58b6ad3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -55,8 +55,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.graphics.drawable.IconCompat;
-import androidx.mediarouter.media.MediaRouter;
-import androidx.mediarouter.media.MediaRouterParams;
 
 import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.Utils;
@@ -215,9 +213,8 @@
     }
 
     boolean shouldShowLaunchSection() {
-        MediaRouterParams routerParams = MediaRouter.getInstance(mContext).getRouterParams();
-        Log.d(TAG, "try to get routerParams: " + routerParams);
-        return routerParams != null && !routerParams.isMediaTransferReceiverEnabled();
+        // TODO(b/231398073): Implements this when available.
+        return false;
     }
 
     void setRefreshing(boolean refreshing) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index c7cd48f..f0ff89a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -70,6 +70,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.Region;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -91,6 +92,8 @@
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.InternalInsetsInfo;
+import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 import android.view.WindowManager;
@@ -121,6 +124,7 @@
 import com.android.systemui.navigationbar.buttons.DeadZone;
 import com.android.systemui.navigationbar.buttons.KeyButtonView;
 import com.android.systemui.navigationbar.buttons.RotationContextButton;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
@@ -152,6 +156,7 @@
 
 import java.io.PrintWriter;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Optional;
 import java.util.function.Consumer;
 
@@ -198,11 +203,13 @@
     private final Optional<Recents> mRecentsOptional;
     private final DeviceConfigProxy mDeviceConfigProxy;
     private final NavigationBarTransitions mNavigationBarTransitions;
+    private final EdgeBackGestureHandler mEdgeBackGestureHandler;
     private final Optional<BackAnimation> mBackAnimation;
     private final Handler mHandler;
     private final UiEventLogger mUiEventLogger;
     private final NavBarHelper mNavBarHelper;
     private final NotificationShadeDepthController mNotificationShadeDepthController;
+    private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener;
     private NavigationBarFrame mFrame;
 
     private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
@@ -514,6 +521,7 @@
             DeadZone deadZone,
             DeviceConfigProxy deviceConfigProxy,
             NavigationBarTransitions navigationBarTransitions,
+            EdgeBackGestureHandler edgeBackGestureHandler,
             Optional<BackAnimation> backAnimation) {
         super(navigationBarView);
         mFrame = navigationBarFrame;
@@ -539,6 +547,7 @@
         mDeadZone = deadZone;
         mDeviceConfigProxy = deviceConfigProxy;
         mNavigationBarTransitions = navigationBarTransitions;
+        mEdgeBackGestureHandler = edgeBackGestureHandler;
         mBackAnimation = backAnimation;
         mHandler = mainHandler;
         mUiEventLogger = uiEventLogger;
@@ -551,6 +560,29 @@
         mTelecomManagerOptional = telecomManagerOptional;
         mInputMethodManager = inputMethodManager;
 
+        mOnComputeInternalInsetsListener = info -> {
+            // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
+            // gestural mode, the entire nav bar should be touchable.
+            if (!mEdgeBackGestureHandler.isHandlingGestures()) {
+                // We're in 2/3 button mode OR back button force-shown in SUW
+                if (!mImeVisible) {
+                    // IME not showing, take all touches
+                    info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+                }
+                if (!mView.isImeRenderingNavButtons()) {
+                    // IME showing but not drawing any buttons, take all touches
+                    info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+                }
+            }
+
+            // When in gestural and the IME is showing, don't use the nearest region since it will
+            // take gesture space away from the IME
+            info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+            info.touchableRegion.set(getButtonLocations(false /* includeFloatingButtons */,
+                    false /* inScreen */, false /* useNearestRegion */));
+        };
+
+        mView.setEdgeBackGestureHandler(mEdgeBackGestureHandler);
         mNavBarMode = mNavigationModeController.addListener(mModeChangedListener);
     }
 
@@ -565,6 +597,7 @@
         mView.setBarTransitions(mNavigationBarTransitions);
         mView.setTouchHandler(mTouchHandler);
         mView.setNavBarMode(mNavBarMode);
+        mEdgeBackGestureHandler.setStateChangeCallback(mView::updateStates);
         mView.updateRotationButton();
 
         mView.setVisibility(
@@ -642,11 +675,14 @@
         mView.setNavBarMode(mNavBarMode);
         mView.setUpdateActiveTouchRegionsCallback(
                 () -> mOverviewProxyService.onActiveNavBarRegionChanges(
-                        mView.getButtonLocations(
+                        getButtonLocations(
                                 true /* includeFloatingButtons */,
                                 true /* inScreen */,
                                 true /* useNearestRegion */)));
 
+        mView.getViewTreeObserver().addOnComputeInternalInsetsListener(
+                mOnComputeInternalInsetsListener);
+
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
         mPipOptional.ifPresent(mView::addPipExclusionBoundsChangeListener);
@@ -717,6 +753,8 @@
             mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener(
                     mOrientationHandleGlobalLayoutListener);
         }
+        mView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
+                mOnComputeInternalInsetsListener);
         mHandler.removeCallbacks(mAutoDim);
         mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
         mHandler.removeCallbacks(mEnableLayoutTransitions);
@@ -1040,8 +1078,8 @@
     }
 
     private void handleTransientChanged() {
-        mView.onTransientStateChanged(mTransientShown,
-                mTransientShownFromGestureOnSystemBar);
+        mEdgeBackGestureHandler.onNavBarTransientStateChanged(mTransientShown);
+
         final int transitionMode = transitionMode(mTransientShown, mAppearance);
         if (updateTransitionMode(transitionMode) && mLightBarController != null) {
             mLightBarController.onNavigationBarModeChanged(transitionMode);
@@ -1634,6 +1672,79 @@
         mNavigationIconHints = hints;
     }
 
+    /**
+     * @param includeFloatingButtons Whether to include the floating rotation and overlay button in
+     *                               the region for all the buttons
+     * @param inScreenSpace Whether to return values in screen space or window space
+     * @param useNearestRegion Whether to use the nearest region instead of the actual button bounds
+     * @return
+     */
+    Region getButtonLocations(boolean includeFloatingButtons, boolean inScreenSpace,
+            boolean useNearestRegion) {
+        if (useNearestRegion && !inScreenSpace) {
+            // We currently don't support getting the nearest region in anything but screen space
+            useNearestRegion = false;
+        }
+        Region region = new Region();
+        Map<View, Rect> touchRegionCache = mView.getButtonTouchRegionCache();
+        updateButtonLocation(
+                region, touchRegionCache, mView.getBackButton(), inScreenSpace, useNearestRegion);
+        updateButtonLocation(
+                region, touchRegionCache, mView.getHomeButton(), inScreenSpace, useNearestRegion);
+        updateButtonLocation(region, touchRegionCache, mView.getRecentsButton(), inScreenSpace,
+                useNearestRegion);
+        updateButtonLocation(region, touchRegionCache, mView.getImeSwitchButton(), inScreenSpace,
+                useNearestRegion);
+        updateButtonLocation(
+                region, touchRegionCache, mView.getAccessibilityButton(), inScreenSpace,
+                useNearestRegion);
+        if (includeFloatingButtons && mView.getFloatingRotationButton().isVisible()) {
+            // Note: this button is floating so the nearest region doesn't apply
+            updateButtonLocation(
+                    region, mView.getFloatingRotationButton().getCurrentView(), inScreenSpace);
+        } else {
+            updateButtonLocation(region, touchRegionCache, mView.getRotateSuggestionButton(),
+                    inScreenSpace, useNearestRegion);
+        }
+        return region;
+    }
+
+    private void updateButtonLocation(
+            Region region,
+            Map<View, Rect> touchRegionCache,
+            ButtonDispatcher button,
+            boolean inScreenSpace,
+            boolean useNearestRegion) {
+        if (button == null) {
+            return;
+        }
+        View view = button.getCurrentView();
+        if (view == null || !button.isVisible()) {
+            return;
+        }
+        // If the button is tappable from perspective of NearestTouchFrame, then we'll
+        // include the regions where the tap is valid instead of just the button layout location
+        if (useNearestRegion && touchRegionCache.containsKey(view)) {
+            region.op(touchRegionCache.get(view), Region.Op.UNION);
+            return;
+        }
+        updateButtonLocation(region, view, inScreenSpace);
+    }
+
+    private void updateButtonLocation(Region region, View view, boolean inScreenSpace) {
+        Rect bounds = new Rect();
+        if (inScreenSpace) {
+            view.getBoundsOnScreen(bounds);
+        } else {
+            int[] location = new int[2];
+            view.getLocationInWindow(location);
+            bounds.set(location[0], location[1],
+                    location[0] + view.getWidth(),
+                    location[1] + view.getHeight());
+        }
+        region.op(bounds, Region.Op.UNION);
+    }
+
     private final ModeChangedListener mModeChangedListener = new ModeChangedListener() {
         @Override
         public void onNavigationModeChanged(int mode) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
index 93bf136..f6bfd6c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
@@ -24,6 +24,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 
 import dagger.Module;
 import dagger.Provides;
@@ -55,6 +56,14 @@
         return barView.findViewById(R.id.navigation_bar_view);
     }
 
+    /** */
+    @Provides
+    @NavigationBarScope
+    static EdgeBackGestureHandler provideEdgeBackGestureHandler(
+            EdgeBackGestureHandler.Factory factory, @DisplayId Context context) {
+        return factory.create(context);
+    }
+
     /** A WindowManager specific to the display's context. */
     @Provides
     @NavigationBarScope
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 6e00ebc..a13c199 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -41,8 +41,6 @@
 import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.Region.Op;
 import android.os.Bundle;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -53,8 +51,6 @@
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver.InternalInsetsInfo;
-import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
 import android.view.WindowInsets;
 import android.view.WindowInsetsController.Behavior;
 import android.view.WindowManager;
@@ -93,7 +89,6 @@
 import com.android.wm.shell.pip.Pip;
 
 import java.io.PrintWriter;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.Executor;
@@ -123,11 +118,6 @@
     private int mNavBarMode;
     private boolean mImeDrawsImeNavBar;
 
-    private final Region mTmpRegion = new Region();
-    private final int[] mTmpPosition = new int[2];
-    private Rect mTmpBounds = new Rect();
-    private Map<View, Rect> mButtonFullTouchableRegions = new HashMap<>();
-
     private KeyButtonDrawable mBackIcon;
     private KeyButtonDrawable mHomeDefaultIcon;
     private KeyButtonDrawable mRecentIcon;
@@ -138,7 +128,6 @@
 
     private EdgeBackGestureHandler mEdgeBackGestureHandler;
     private final DeadZone mDeadZone;
-    private boolean mDeadZoneConsuming = false;
     private NavigationBarTransitions mBarTransitions;
     @Nullable
     private AutoHideController mAutoHideController;
@@ -152,7 +141,6 @@
     private boolean mUseCarModeUi = false;
     private boolean mInCarMode = false;
     private boolean mDockedStackExists;
-    private boolean mImeVisible;
     private boolean mScreenOn = true;
 
     private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
@@ -272,31 +260,6 @@
                 }
             };
 
-    private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
-        // When the nav bar is in 2-button or 3-button mode, or when the back button is force-shown
-        // while in gesture nav in SUW, the entire nav bar should be touchable.
-        if (!mEdgeBackGestureHandler.isHandlingGestures()) {
-            // We're in 2/3 button mode OR back button force-shown in SUW
-            if (!mImeVisible) {
-                // IME not showing, take all touches
-                info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
-                return;
-            }
-  
-            if (!isImeRenderingNavButtons()) {
-                // IME showing but not drawing any buttons, take all touches
-                info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
-                return;     
-            }
-        }
-
-        // When in gestural and the IME is showing, don't use the nearest region since it will take
-        // gesture space away from the IME
-        info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-        info.touchableRegion.set(getButtonLocations(false /* includeFloatingButtons */,
-                false /* inScreen */, false /* useNearestRegion */));
-    };
-
     private final RotationButtonUpdatesCallback mRotationButtonListener =
             new RotationButtonUpdatesCallback() {
                 @Override
@@ -315,13 +278,6 @@
                 }
             };
 
-    private final Consumer<Boolean> mNavbarOverlayVisibilityChangeCallback = (visible) -> {
-        if (visible && mAutoHideController != null) {
-            mAutoHideController.touchAutoHide();
-        }
-        notifyActiveTouchRegions();
-    };
-
     public NavigationBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -380,9 +336,6 @@
 
         mNavColorSampleMargin = getResources()
                         .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
-        mEdgeBackGestureHandler = Dependency.get(EdgeBackGestureHandler.Factory.class)
-                .create(mContext);
-        mEdgeBackGestureHandler.setStateChangeCallback(this::updateStates);
         Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
         mRegionSamplingHelper = new RegionSamplingHelper(this,
                 new RegionSamplingHelper.SamplingCallback() {
@@ -409,6 +362,10 @@
                 }, backgroundExecutor);
     }
 
+    public void setEdgeBackGestureHandler(EdgeBackGestureHandler edgeBackGestureHandler) {
+        mEdgeBackGestureHandler = edgeBackGestureHandler;
+    }
+
     void setBarTransitions(NavigationBarTransitions navigationBarTransitions) {
         mBarTransitions = navigationBarTransitions;
     }
@@ -681,8 +638,7 @@
         if (!visible) {
             mTransitionListener.onBackAltCleared();
         }
-        mImeVisible = visible;
-        mRotationButtonController.getRotationButton().setCanShowRotationButton(!mImeVisible);
+        mRotationButtonController.getRotationButton().setCanShowRotationButton(!visible);
     }
 
     void setDisabledFlags(int disabledFlags, SysUiState sysUiState) {
@@ -774,7 +730,7 @@
     /**
      * Returns whether the IME is currently visible and drawing the nav buttons.
      */
-    private boolean isImeRenderingNavButtons() {
+    boolean isImeRenderingNavButtons() {
         return mImeDrawsImeNavBar
                 && mImeCanRenderGesturalNavButtons
                 && (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0;
@@ -1003,75 +959,14 @@
         notifyActiveTouchRegions();
     }
 
-    private void updateButtonTouchRegionCache() {
+    Map<View, Rect> getButtonTouchRegionCache() {
         FrameLayout navBarLayout = mIsVertical
                 ? mNavigationInflaterView.mVertical
                 : mNavigationInflaterView.mHorizontal;
-        mButtonFullTouchableRegions = ((NearestTouchFrame) navBarLayout
+        return ((NearestTouchFrame) navBarLayout
                 .findViewById(R.id.nav_buttons)).getFullTouchableChildRegions();
     }
 
-    /**
-     * @param includeFloatingButtons Whether to include the floating rotation and overlay button in
-     *                               the region for all the buttons
-     * @param inScreenSpace Whether to return values in screen space or window space
-     * @param useNearestRegion Whether to use the nearest region instead of the actual button bounds
-     * @return
-     */
-    Region getButtonLocations(boolean includeFloatingButtons, boolean inScreenSpace,
-            boolean useNearestRegion) {
-        // TODO: move this method to NavigationBar.
-        // TODO: don't use member variables for temp storage like mTmpRegion.
-        if (useNearestRegion && !inScreenSpace) {
-            // We currently don't support getting the nearest region in anything but screen space
-            useNearestRegion = false;
-        }
-        mTmpRegion.setEmpty();
-        updateButtonTouchRegionCache();
-        updateButtonLocation(getBackButton(), inScreenSpace, useNearestRegion);
-        updateButtonLocation(getHomeButton(), inScreenSpace, useNearestRegion);
-        updateButtonLocation(getRecentsButton(), inScreenSpace, useNearestRegion);
-        updateButtonLocation(getImeSwitchButton(), inScreenSpace, useNearestRegion);
-        updateButtonLocation(getAccessibilityButton(), inScreenSpace, useNearestRegion);
-        if (includeFloatingButtons && mFloatingRotationButton.isVisible()) {
-            // Note: this button is floating so the nearest region doesn't apply
-            updateButtonLocation(mFloatingRotationButton.getCurrentView(), inScreenSpace);
-        } else {
-            updateButtonLocation(getRotateSuggestionButton(), inScreenSpace, useNearestRegion);
-        }
-        return mTmpRegion;
-    }
-
-    private void updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace,
-            boolean useNearestRegion) {
-        if (button == null) {
-            return;
-        }
-        View view = button.getCurrentView();
-        if (view == null || !button.isVisible()) {
-            return;
-        }
-        // If the button is tappable from perspective of NearestTouchFrame, then we'll
-        // include the regions where the tap is valid instead of just the button layout location
-        if (useNearestRegion && mButtonFullTouchableRegions.containsKey(view)) {
-            mTmpRegion.op(mButtonFullTouchableRegions.get(view), Op.UNION);
-            return;
-        }
-        updateButtonLocation(view, inScreenSpace);
-    }
-
-    private void updateButtonLocation(View view, boolean inScreenSpace) {
-        if (inScreenSpace) {
-            view.getBoundsOnScreen(mTmpBounds);
-        } else {
-            view.getLocationInWindow(mTmpPosition);
-            mTmpBounds.set(mTmpPosition[0], mTmpPosition[1],
-                    mTmpPosition[0] + view.getWidth(),
-                    mTmpPosition[1] + view.getHeight());
-        }
-        mTmpRegion.op(mTmpBounds, Op.UNION);
-    }
-
     private void updateOrientationViews() {
         mHorizontal = findViewById(R.id.horizontal);
         mVertical = findViewById(R.id.vertical);
@@ -1272,7 +1167,6 @@
             mRotationButtonController.registerListeners();
         }
 
-        getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
         updateNavButtonIcons();
     }
 
@@ -1288,8 +1182,6 @@
         }
 
         mEdgeBackGestureHandler.onNavBarDetached();
-        getViewTreeObserver().removeOnComputeInternalInsetsListener(
-                mOnComputeInternalInsetsListener);
     }
 
     public void dump(PrintWriter pw) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 3eb4b10..3e8cdf3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -420,7 +420,7 @@
                 PowerExemptionManager.REASON_SYSTEM_UID,
                 PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY
 
-                PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE,
+                PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED,
                 PowerExemptionManager.REASON_DEVICE_OWNER,
                 PowerExemptionManager.REASON_DISALLOW_APPS_CONTROL,
                 PowerExemptionManager.REASON_DPO_PROTECTED_APP,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index ebd610b..0c9e1ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -180,13 +180,6 @@
     default void setRequestTopUi(boolean requestTopUi, String componentTag) {}
 
     /**
-     * Under low light conditions, we might want to increase the display brightness on devices that
-     * don't have an IR camera.
-     * @param brightness float from 0 to 1 or {@code LayoutParams.BRIGHTNESS_OVERRIDE_NONE}
-     */
-    default void setFaceAuthDisplayBrightness(float brightness) {}
-
-    /**
      * If {@link LightRevealScrim} obscures the UI.
      * @param opaque if the scrim is opaque
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
index a390e9f..15ad312 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -20,11 +20,13 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.dagger.PeopleHeader
+import com.android.systemui.statusbar.notification.icon.ConversationIconManager
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
@@ -39,12 +41,40 @@
 @CoordinatorScope
 class ConversationCoordinator @Inject constructor(
     private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
+    private val conversationIconManager: ConversationIconManager,
     @PeopleHeader peopleHeaderController: NodeController
 ) : Coordinator {
 
+    private val promotedEntriesToSummaryOfSameChannel =
+        mutableMapOf<NotificationEntry, NotificationEntry>()
+
+    private val onBeforeRenderListListener = OnBeforeRenderListListener { _ ->
+        val unimportantSummaries = promotedEntriesToSummaryOfSameChannel
+            .mapNotNull { (promoted, summary) ->
+                val originalGroup = summary.parent
+                when {
+                    originalGroup == null -> null
+                    originalGroup == promoted.parent -> null
+                    originalGroup.parent == null -> null
+                    originalGroup.summary != summary -> null
+                    originalGroup.children.any { it.channel == summary.channel } -> null
+                    else -> summary.key
+                }
+            }
+        conversationIconManager.setUnimportantConversations(unimportantSummaries)
+        promotedEntriesToSummaryOfSameChannel.clear()
+    }
+
     private val notificationPromoter = object : NotifPromoter(TAG) {
         override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean {
-            return entry.channel?.isImportantConversation == true
+            val shouldPromote = entry.channel?.isImportantConversation == true
+            if (shouldPromote) {
+                val summary = entry.parent?.summary
+                if (summary != null && entry.channel == summary.channel) {
+                    promotedEntriesToSummaryOfSameChannel[entry] = summary
+                }
+            }
+            return shouldPromote
         }
     }
 
@@ -67,6 +97,7 @@
 
     override fun attach(pipeline: NotifPipeline) {
         pipeline.addPromoter(notificationPromoter)
+        pipeline.addOnBeforeRenderListListener(onBeforeRenderListListener)
     }
 
     private fun isConversation(entry: ListEntry): Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 386e2d3..f460644 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -18,7 +18,6 @@
 
 import android.annotation.MainThread
 import android.view.View
-import com.android.systemui.util.kotlin.transform
 import com.android.systemui.util.traceSection
 
 /**
@@ -41,7 +40,6 @@
 ) {
     private val rootNode = ShadeNode(rootController)
     private val nodes = mutableMapOf(rootController to rootNode)
-    private val views = mutableMapOf<View, ShadeNode>()
 
     /**
      * Adds and removes views from the root (and its children) until their structure matches the
@@ -66,26 +64,25 @@
      *
      * For debugging purposes.
      */
-    fun getViewLabel(view: View): String = views[view]?.label ?: view.toString()
+    fun getViewLabel(view: View): String =
+            nodes.values.firstOrNull { node -> node.view === view }?.label ?: view.toString()
 
-    private fun detachChildren(
-        parentNode: ShadeNode,
-        specMap: Map<NodeController, NodeSpec>
-    ) {
-        val parentSpec = specMap[parentNode.controller]
-
-        for (i in parentNode.getChildCount() - 1 downTo 0) {
-            val childView = parentNode.getChildAt(i)
-            views[childView]?.let { childNode ->
-                val childSpec = specMap[childNode.controller]
-
-                maybeDetachChild(parentNode, parentSpec, childNode, childSpec)
-
-                if (childNode.controller.getChildCount() > 0) {
-                    detachChildren(childNode, specMap)
+    private fun detachChildren(parentNode: ShadeNode, specMap: Map<NodeController, NodeSpec>) {
+        val views = nodes.values.associateBy { it.view }
+        fun detachRecursively(parentNode: ShadeNode, specMap: Map<NodeController, NodeSpec>) {
+            val parentSpec = specMap[parentNode.controller]
+            for (i in parentNode.getChildCount() - 1 downTo 0) {
+                val childView = parentNode.getChildAt(i)
+                views[childView]?.let { childNode ->
+                    val childSpec = specMap[childNode.controller]
+                    maybeDetachChild(parentNode, parentSpec, childNode, childSpec)
+                    if (childNode.controller.getChildCount() > 0) {
+                        detachRecursively(childNode, specMap)
+                    }
                 }
             }
         }
+        detachRecursively(parentNode, specMap)
     }
 
     private fun maybeDetachChild(
@@ -94,14 +91,13 @@
         childNode: ShadeNode,
         childSpec: NodeSpec?
     ) {
-        val newParentNode = transform(childSpec?.parent) { getNode(it) }
+        val newParentNode = childSpec?.parent?.let { getNode(it) }
 
         if (newParentNode != parentNode) {
             val childCompletelyRemoved = newParentNode == null
 
             if (childCompletelyRemoved) {
                 nodes.remove(childNode.controller)
-                views.remove(childNode.controller.view)
             }
 
             logger.logDetachingChild(
@@ -115,10 +111,7 @@
         }
     }
 
-    private fun attachChildren(
-        parentNode: ShadeNode,
-        specMap: Map<NodeController, NodeSpec>
-    ) {
+    private fun attachChildren(parentNode: ShadeNode, specMap: Map<NodeController, NodeSpec>) {
         val parentSpec = checkNotNull(specMap[parentNode.controller])
 
         for ((index, childSpec) in parentSpec.children.withIndex()) {
@@ -160,7 +153,6 @@
         if (node == null) {
             node = ShadeNode(spec.controller)
             nodes[node.controller] = node
-            views[node.view] = node
         }
         return node
     }
@@ -194,10 +186,9 @@
 
 private class DuplicateNodeException(message: String) : RuntimeException(message)
 
-private class ShadeNode(
-    val controller: NodeController
-) {
-    val view = controller.view
+private class ShadeNode(val controller: NodeController) {
+    val view: View
+        get() = controller.view
 
     var parent: ShadeNode? = null
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 34c8044..d96590a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -70,6 +70,8 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
+import com.android.systemui.statusbar.notification.icon.IconManager;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
@@ -370,6 +372,10 @@
 
     /** */
     @Binds
+    ConversationIconManager bindConversationIconManager(IconManager iconManager);
+
+    /** */
+    @Binds
     BindEventManager bindBindEventManagerImpl(BindEventManagerImpl bindEventManagerImpl);
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 5375ac3..d896541 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -27,6 +27,7 @@
 import android.widget.ImageView
 import com.android.internal.statusbar.StatusBarIcon
 import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.InflationException
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -44,11 +45,14 @@
  * TODO: Much of this code was copied whole-sale in order to get it out of NotificationEntry.
  *  Long-term, it should probably live somewhere in the content inflation pipeline.
  */
+@SysUISingleton
 class IconManager @Inject constructor(
     private val notifCollection: CommonNotifCollection,
     private val launcherApps: LauncherApps,
     private val iconBuilder: IconBuilder
-) {
+) : ConversationIconManager {
+    private var unimportantConversationKeys: Set<String> = emptySet()
+
     fun attach() {
         notifCollection.addCollectionListener(entryListener)
     }
@@ -63,16 +67,8 @@
         }
 
         override fun onRankingApplied() {
-            // When the sensitivity changes OR when the isImportantConversation status changes,
-            // we need to update the icons
-            for (entry in notifCollection.allNotifs) {
-                val isImportant = isImportantConversation(entry)
-                if (entry.icons.areIconsAvailable &&
-                        isImportant != entry.icons.isImportantConversation) {
-                    updateIconsSafe(entry)
-                }
-                entry.icons.isImportantConversation = isImportant
-            }
+            // rankings affect whether a conversation is important, which can change the icons
+            recalculateForImportantConversationChange()
         }
     }
 
@@ -80,6 +76,18 @@
         entry -> updateIconsSafe(entry)
     }
 
+    private fun recalculateForImportantConversationChange() {
+        for (entry in notifCollection.allNotifs) {
+            val isImportant = isImportantConversation(entry)
+            if (entry.icons.areIconsAvailable &&
+                isImportant != entry.icons.isImportantConversation
+            ) {
+                updateIconsSafe(entry)
+            }
+            entry.icons.isImportantConversation = isImportant
+        }
+    }
+
     /**
      * Inflate icon views for each icon variant and assign appropriate icons to them. Stores the
      * result in [NotificationEntry.getIcons].
@@ -306,8 +314,28 @@
     }
 
     private fun isImportantConversation(entry: NotificationEntry): Boolean {
-        return entry.ranking.channel != null && entry.ranking.channel.isImportantConversation
+        return entry.ranking.channel != null &&
+                entry.ranking.channel.isImportantConversation &&
+                entry.key !in unimportantConversationKeys
+    }
+
+    override fun setUnimportantConversations(keys: Collection<String>) {
+        val newKeys = keys.toSet()
+        val changed = unimportantConversationKeys != newKeys
+        unimportantConversationKeys = newKeys
+        if (changed) {
+            recalculateForImportantConversationChange()
+        }
     }
 }
 
-private const val TAG = "IconManager"
\ No newline at end of file
+private const val TAG = "IconManager"
+
+interface ConversationIconManager {
+    /**
+     * Sets the complete current set of notification keys which should (for the purposes of icon
+     * presentation) be considered unimportant.  This tells the icon manager to remove the avatar
+     * of a group from which the priority notification has been removed.
+     */
+    fun setUnimportantConversations(keys: Collection<String>)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index 5646545..cff42f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -18,6 +18,8 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
@@ -72,7 +74,7 @@
     private val lockscreenUserManager: NotificationLockscreenUserManager,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val highPriorityProvider: HighPriorityProvider,
-    private val statusBarStateController: StatusBarStateController,
+    private val statusBarStateController: SysuiStatusBarStateController,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val secureSettings: SecureSettings,
     private val globalSettings: GlobalSettings
@@ -105,7 +107,8 @@
                 if (uri == showSilentNotifsUri) {
                     readShowSilentNotificationSetting()
                 }
-                if (keyguardStateController.isShowing) {
+                if (statusBarStateController.getCurrentOrUpcomingState()
+                        == StatusBarState.KEYGUARD) {
                     notifyStateChanged("Settings $uri changed")
                 }
             }
@@ -131,13 +134,14 @@
 
         // register (maybe) public mode changed callbacks:
         statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
-            override fun onStateChanged(state: Int) {
-                notifyStateChanged("onStatusBarStateChanged")
+            override fun onUpcomingStateChanged(state: Int) {
+                notifyStateChanged("onStatusBarUpcomingStateChanged")
             }
         })
         broadcastDispatcher.registerReceiver(object : BroadcastReceiver() {
             override fun onReceive(context: Context, intent: Intent) {
-                if (keyguardStateController.isShowing) {
+                if (statusBarStateController.getCurrentOrUpcomingState()
+                        == StatusBarState.KEYGUARD) {
                     // maybe public mode changed
                     notifyStateChanged(intent.action!!)
                 }
@@ -159,17 +163,28 @@
 
     override fun shouldHideNotification(entry: NotificationEntry): Boolean = when {
         // Keyguard state doesn't matter if the keyguard is not showing.
-        !keyguardStateController.isShowing -> false
+        statusBarStateController.getCurrentOrUpcomingState() != StatusBarState.KEYGUARD -> false
         // Notifications not allowed on the lockscreen, always hide.
         !lockscreenUserManager.shouldShowLockscreenNotifications() -> true
         // User settings do not allow this notification on the lockscreen, so hide it.
         userSettingsDisallowNotification(entry) -> true
+        // if entry is silent, apply custom logic to see if should hide
+        shouldHideIfEntrySilent(entry) -> true
+        else -> false
+    }
+
+    private fun shouldHideIfEntrySilent(entry: ListEntry): Boolean = when {
+        // Show if high priority (not hidden)
+        highPriorityProvider.isHighPriority(entry) -> false
+        // Ambient notifications are hidden always from lock screen
+        entry.representativeEntry?.isAmbient == true -> true
+        // [Now notification is silent]
+        // Hide regardless of parent priority if user wants silent notifs hidden
+        hideSilentNotificationsOnLockscreen -> true
         // Parent priority is high enough to be shown on the lockscreen, do not hide.
-        entry.parent?.let(::priorityExceedsLockscreenShowingThreshold) == true -> false
-        // Entry priority is high enough to be shown on the lockscreen, do not hide.
-        priorityExceedsLockscreenShowingThreshold(entry) -> false
-        // Priority is too low, hide.
-        else -> true
+        entry.parent?.let(::shouldHideIfEntrySilent) == false -> false
+        // Show when silent notifications are allowed on lockscreen
+        else -> false
     }
 
     private fun userSettingsDisallowNotification(entry: NotificationEntry): Boolean {
@@ -193,11 +208,6 @@
         }
     }
 
-    private fun priorityExceedsLockscreenShowingThreshold(entry: ListEntry): Boolean = when {
-        hideSilentNotificationsOnLockscreen -> highPriorityProvider.isHighPriority(entry)
-        else -> entry.representativeEntry?.ranking?.isAmbient == false
-    }
-
     private fun readShowSilentNotificationSetting() {
         val showSilentNotifs =
                 secureSettings.getBoolForUser(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 36cd173..3ea5e5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -70,6 +70,7 @@
 import com.android.internal.graphics.ColorUtils;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.policy.SystemBarUtils;
+import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.keyguard.KeyguardSliceView;
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
@@ -1313,7 +1314,10 @@
         final float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition
                 + mAmbientState.getOverExpansion()
                 - getCurrentOverScrollAmount(false /* top */);
-        final float fraction = mAmbientState.getExpansionFraction();
+        float fraction = mAmbientState.getExpansionFraction();
+        if (mAmbientState.isBouncerInTransit()) {
+            fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
+        }
         final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
         mAmbientState.setStackY(stackY);
         if (mOnStackYChanged != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index ede4a20..1f9130e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1023,6 +1023,23 @@
             public void onUnlockedChanged() {
                 logStateToEventlog();
             }
+
+            @Override
+            public void onKeyguardGoingAwayChanged() {
+                // The light reveal scrim should always be fully revealed by the time the keyguard
+                // is done going away. Double check that this is true.
+                if (!mKeyguardStateController.isKeyguardGoingAway()) {
+                    if (mLightRevealScrim.getRevealAmount() != 1f) {
+                        Log.e(TAG, "Keyguard is done going away, but someone left the light reveal "
+                                + "scrim at reveal amount: " + mLightRevealScrim.getRevealAmount());
+                    }
+
+                    // If the auth ripple is still playing, let it finish.
+                    if (!mAuthRippleController.isAnimatingLightRevealScrim()) {
+                        mLightRevealScrim.setRevealAmount(1f);
+                    }
+                }
+            }
         });
         startKeyguard();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 69beaf56..0b72138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -220,7 +220,7 @@
                 DejankUtils.postAfterTraversal(mShowRunnable);
             }
 
-            mCallback.onBouncerVisiblityChanged(true /* shown */);
+            mKeyguardStateController.notifyBouncerShowing(true /* showing */);
             dispatchStartingToShow();
         } finally {
             Trace.endSection();
@@ -334,7 +334,7 @@
         }
         mIsScrimmed = false;
         mFalsingCollector.onBouncerHidden();
-        mCallback.onBouncerVisiblityChanged(false /* shown */);
+        mKeyguardStateController.notifyBouncerShowing(false /* showing */);
         cancelShowRunnable();
         if (mKeyguardViewController != null) {
             mKeyguardViewController.cancelDismissAction();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index 24660b2..01aa2ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -111,7 +111,6 @@
 
     private final SysuiColorExtractor mColorExtractor;
     private final ScreenOffAnimationController mScreenOffAnimationController;
-    private float mFaceAuthDisplayBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
     /**
      * Layout params would be aggregated and dispatched all at once if this is > 0.
      *
@@ -266,12 +265,6 @@
         mScreenBrightnessDoze = value / 255f;
     }
 
-    @Override
-    public void setFaceAuthDisplayBrightness(float brightness) {
-        mFaceAuthDisplayBrightness = brightness;
-        apply(mCurrentState);
-    }
-
     private void setKeyguardDark(boolean dark) {
         int vis = mNotificationShadeView.getSystemUiVisibility();
         if (dark) {
@@ -455,7 +448,9 @@
 
     private void applyWindowLayoutParams() {
         if (mDeferWindowLayoutParams == 0 && mLp != null && mLp.copyFrom(mLpChanged) != 0) {
+            Trace.beginSection("updateViewLayout");
             mWindowManager.updateViewLayout(mNotificationShadeView, mLp);
+            Trace.endSection();
         }
     }
 
@@ -523,7 +518,7 @@
         if (state.mForceDozeBrightness) {
             mLpChanged.screenBrightness = mScreenBrightnessDoze;
         } else {
-            mLpChanged.screenBrightness = mFaceAuthDisplayBrightness;
+            mLpChanged.screenBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
         }
     }
 
@@ -572,6 +567,10 @@
 
     @Override
     public void setPanelVisible(boolean visible) {
+        if (mCurrentState.mPanelVisible == visible
+                && mCurrentState.mNotificationShadeFocusable == visible) {
+            return;
+        }
         mCurrentState.mPanelVisible = visible;
         mCurrentState.mNotificationShadeFocusable = visible;
         apply(mCurrentState);
@@ -626,8 +625,14 @@
 
     @Override
     public void setScrimsVisibility(int scrimsVisibility) {
+        if (scrimsVisibility == mCurrentState.mScrimsVisibility) {
+            return;
+        }
+        boolean wasExpanded = isExpanded(mCurrentState);
         mCurrentState.mScrimsVisibility = scrimsVisibility;
-        apply(mCurrentState);
+        if (wasExpanded != isExpanded(mCurrentState)) {
+            apply(mCurrentState);
+        }
         mScrimsVisibilityListener.accept(scrimsVisibility);
     }
 
@@ -687,6 +692,9 @@
 
     @Override
     public void setPanelExpanded(boolean isExpanded) {
+        if (mCurrentState.mPanelExpanded == isExpanded) {
+            return;
+        }
         mCurrentState.mPanelExpanded = isExpanded;
         apply(mCurrentState);
     }
@@ -703,6 +711,9 @@
      */
     @Override
     public void setForceDozeBrightness(boolean forceDozeBrightness) {
+        if (mCurrentState.mForceDozeBrightness == forceDozeBrightness) {
+            return;
+        }
         mCurrentState.mForceDozeBrightness = forceDozeBrightness;
         apply(mCurrentState);
     }
@@ -841,7 +852,6 @@
         boolean mLightRevealScrimOpaque;
         boolean mForceCollapsed;
         boolean mForceDozeBrightness;
-        int mFaceAuthDisplayBrightness;
         boolean mForceUserActivity;
         boolean mLaunchingActivity;
         boolean mBackdropShowing;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 935f87d..c1d0769 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -175,28 +175,36 @@
                 .setDuration(duration.toLong())
                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
                 .alpha(1f)
+                .withEndAction {
+                    aodUiAnimationPlaying = false
+
+                    // Lock the keyguard if it was waiting for the screen off animation to end.
+                    keyguardViewMediatorLazy.get().maybeHandlePendingLock()
+
+                    // Tell the CentralSurfaces to become keyguard for real - we waited on that
+                    // since it is slow and would have caused the animation to jank.
+                    mCentralSurfaces.updateIsKeyguard()
+
+                    // Run the callback given to us by the KeyguardVisibilityHelper.
+                    after.run()
+
+                    // Done going to sleep, reset this flag.
+                    decidedToAnimateGoingToSleep = null
+
+                    // We need to unset the listener. These are persistent for future animators
+                    keyguardView.animate().setListener(null)
+                    interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
+                }
                 .setListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
-                        aodUiAnimationPlaying = false
-
-                        // Lock the keyguard if it was waiting for the screen off animation to end.
-                        keyguardViewMediatorLazy.get().maybeHandlePendingLock()
-
-                        // Tell the CentralSurfaces to become keyguard for real - we waited on that
-                        // since it is slow and would have caused the animation to jank.
-                        mCentralSurfaces.updateIsKeyguard()
-
-                        // Run the callback given to us by the KeyguardVisibilityHelper.
-                        after.run()
-
-                        // Done going to sleep, reset this flag.
-                        decidedToAnimateGoingToSleep = null
-                        // We need to unset the listener. These are persistent for future animators
-                        keyguardView.animate().setListener(null)
-                        interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
-                    }
-
                     override fun onAnimationCancel(animation: Animator?) {
+                        // If we're cancelled, reset state flags/listeners. The end action above
+                        // will not be called, which is what we want since that will finish the
+                        // screen off animation and show the lockscreen, which we don't want if we
+                        // were cancelled.
+                        aodUiAnimationPlaying = false
+                        decidedToAnimateGoingToSleep = null
+                        keyguardView.animate().setListener(null)
+
                         interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index 233778d..15ee553 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -46,6 +46,11 @@
     boolean isShowing();
 
     /**
+     * Whether the bouncer (PIN/password entry) is currently visible.
+     */
+    boolean isBouncerShowing();
+
+    /**
      * If swiping up will unlock without asking for a password.
      * @see #isUnlocked()
      */
@@ -186,6 +191,8 @@
     default void notifyKeyguardDoneFading() {}
     /** **/
     default void notifyKeyguardState(boolean showing, boolean occluded) {}
+    /** **/
+    default void notifyBouncerShowing(boolean showing) {}
 
     /**
      * Updates the keyguard state to reflect that it's in the process of being dismissed, either by
@@ -231,6 +238,11 @@
         default void onKeyguardShowingChanged() {}
 
         /**
+         * Called when the bouncer (PIN/password entry) is shown or hidden.
+         */
+        default void onBouncerShowingChanged() {}
+
+        /**
          * Triggered when the device was just unlocked and the lock screen is being dismissed.
          */
         default void onKeyguardFadingAwayChanged() {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index be5da37..77e285d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -63,6 +63,7 @@
 
     private boolean mCanDismissLockScreen;
     private boolean mShowing;
+    private boolean mBouncerShowing;
     private boolean mSecure;
     private boolean mOccluded;
 
@@ -153,6 +154,11 @@
     }
 
     @Override
+    public boolean isBouncerShowing() {
+        return mBouncerShowing;
+    }
+
+    @Override
     public boolean isMethodSecure() {
         return mSecure;
     }
@@ -328,6 +334,15 @@
     }
 
     @Override
+    public void notifyBouncerShowing(boolean showing) {
+        if (mBouncerShowing != showing) {
+            mBouncerShowing = showing;
+
+            new ArrayList<>(mCallbacks).forEach(Callback::onBouncerShowingChanged);
+        }
+    }
+
+    @Override
     public void notifyPanelFlingEnd() {
         mFlingingToDismissKeyguard = false;
         mFlingingToDismissKeyguardDuringSwipeGesture = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index f26176c..f8c36dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -86,7 +86,6 @@
     private boolean mShouldDisplayAllAccesses;
     private boolean mShowSystemAccessesFlag;
     private boolean mShowSystemAccessesSetting;
-    private boolean mExperimentStarted;
 
     @Inject
     public LocationControllerImpl(Context context, AppOpsController appOpsController,
@@ -108,9 +107,6 @@
         mShouldDisplayAllAccesses = getAllAccessesSetting();
         mShowSystemAccessesFlag = getShowSystemFlag();
         mShowSystemAccessesSetting = getShowSystemSetting();
-        mExperimentStarted = getExperimentStarted();
-        toggleSystemSettingIfExperimentJustStarted();
-
         mContentObserver = new ContentObserver(mBackgroundHandler) {
             @Override
             public void onChange(boolean selfChange) {
@@ -127,15 +123,8 @@
                 DeviceConfig.NAMESPACE_PRIVACY,
                 backgroundHandler::post,
                 properties -> {
-                    // Update the Device Config flag which controls the experiment to display
-                    // location accesses.
                     mShouldDisplayAllAccesses = getAllAccessesSetting();
-                    // Update the Device Config flag which controls the experiment to display
-                    // system location accesses.
-                    mShowSystemAccessesFlag = getShowSystemFlag();
-                    // Update the local flag for the system accesses experiment and potentially
-                    // update the behavior based on the flag value.
-                    toggleSystemSettingIfExperimentJustStarted();
+                    mShowSystemAccessesFlag = getShowSystemSetting();
                     updateActiveLocationRequests();
                 });
 
@@ -234,33 +223,6 @@
                 UserHandle.USER_CURRENT) == 1;
     }
 
-    private boolean getExperimentStarted() {
-        return mSecureSettings
-                .getInt(Settings.Secure.LOCATION_INDICATOR_EXPERIMENT_STARTED, 0) == 1;
-    }
-
-    private void toggleSystemSettingIfExperimentJustStarted() {
-        // mShowSystemAccessesFlag indicates whether the Device Config flag is flipped
-        // by an experiment. mExperimentStarted is the local device value which indicates the last
-        // value the device has seen for the Device Config flag.
-        // The local device value is needed to determine that the Device Config flag was just
-        // flipped, as the experiment behavior should only happen once after the experiment is
-        // enabled.
-        if (mShowSystemAccessesFlag && !mExperimentStarted) {
-            // If the Device Config flag is enabled, but the local device setting is not then the
-            // experiment just started. Update the local flag to match and enable the experiment
-            // behavior by flipping the show system setting value.
-            mSecureSettings.putInt(Settings.Secure.LOCATION_INDICATOR_EXPERIMENT_STARTED, 1);
-            mSecureSettings.putInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 1);
-            mExperimentStarted = true;
-        } else if (!mShowSystemAccessesFlag && mExperimentStarted) {
-            // If the Device Config flag is disabled, but the local device flag is enabled then
-            // update the local device flag to match.
-            mSecureSettings.putInt(Settings.Secure.LOCATION_INDICATOR_EXPERIMENT_STARTED, 0);
-            mExperimentStarted = false;
-        }
-    }
-
     /**
      * Returns true if there currently exist active high power location requests.
      */
@@ -288,6 +250,7 @@
         }
         boolean hadActiveLocationRequests = mAreActiveLocationRequests;
         boolean shouldDisplay = false;
+        boolean showSystem = mShowSystemAccessesFlag || mShowSystemAccessesSetting;
         boolean systemAppOp = false;
         boolean nonSystemAppOp = false;
         boolean isSystemApp;
@@ -305,7 +268,7 @@
                     nonSystemAppOp = true;
                 }
 
-                shouldDisplay = mShowSystemAccessesSetting || shouldDisplay || !isSystemApp;
+                shouldDisplay = showSystem || shouldDisplay || !isSystemApp;
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 3751721..5a33603 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -269,8 +269,12 @@
                 super.onEnd(animation);
                 if (animation.getTypeMask() == WindowInsets.Type.ime()) {
                     mEntry.mRemoteEditImeAnimatingAway = false;
-                    mEntry.mRemoteEditImeVisible =
-                            mEditText.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
+                    WindowInsets editTextRootWindowInsets = mEditText.getRootWindowInsets();
+                    if (editTextRootWindowInsets == null) {
+                        Log.w(TAG, "onEnd called on detached view", new Exception());
+                    }
+                    mEntry.mRemoteEditImeVisible = editTextRootWindowInsets != null
+                            && editTextRootWindowInsets.isVisible(WindowInsets.Type.ime());
                     if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) {
                         mController.removeRemoteInput(mEntry, mToken);
                     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index aff94eb..a1e23a20 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.util.concurrency.DelayableExecutor
 import org.junit.Before
 import org.junit.Test
@@ -63,7 +62,7 @@
     @Mock
     lateinit var falsingCollector: FalsingCollector
     @Mock
-    lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+    lateinit var keyguardViewController: KeyguardViewController
     @Mock
     private lateinit var mKeyguardMessageArea: KeyguardMessageArea
     @Mock
@@ -92,13 +91,13 @@
                 mainExecutor,
                 mContext.resources,
                 falsingCollector,
-                statusBarKeyguardViewManager
+                keyguardViewController
         )
     }
 
     @Test
     fun testFocusWhenBouncerIsShown() {
-        Mockito.`when`(statusBarKeyguardViewManager.isBouncerShowing).thenReturn(true)
+        Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
         Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
         keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
         keyguardPasswordView.post { verify(keyguardPasswordView).requestFocus() }
@@ -106,7 +105,7 @@
 
     @Test
     fun testDoNotFocusWhenBouncerIsHidden() {
-        Mockito.`when`(statusBarKeyguardViewManager.isBouncerShowing).thenReturn(false)
+        Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
         Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
         keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
         verify(keyguardPasswordView, never()).requestFocus()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 7b7dfdc..4d33430 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -51,7 +51,6 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -125,7 +124,7 @@
     @Mock
     private SessionTracker mSessionTracker;
     @Mock
-    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private KeyguardViewController mKeyguardViewController;
     private Configuration mConfiguration;
 
     private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
@@ -153,7 +152,7 @@
                 (KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
                 SecurityMode.Password, mLockPatternUtils, null,
                 mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController,
-                null, mock(Resources.class), null, mStatusBarKeyguardViewManager);
+                null, mock(Resources.class), null, mKeyguardViewController);
 
         mKeyguardSecurityContainerController = new KeyguardSecurityContainerController.Factory(
                 mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
index 102f37c..dec2b82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -185,6 +185,7 @@
                     5 /* maxEnrollmentsPerUser */,
                     listOf() /* componentInfo */,
                     FingerprintSensorProperties.TYPE_POWER_BUTTON,
+                    true /* halControlsIllumination */,
                     true /* resetLockoutRequiresHardwareAuthToken */,
                     locations
                 )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index a57b011..fc5ccbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -65,6 +65,7 @@
 import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
 
+private const val HAL_CONTROLS_ILLUMINATION = true
 private const val REQUEST_ID = 2L
 
 // Dimensions for the current display resolution.
@@ -129,8 +130,8 @@
             statusBarStateController, panelExpansionStateManager, statusBarKeyguardViewManager,
             keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
             configurationController, systemClock, keyguardStateController,
-            unlockedScreenOffAnimationController, hbmProvider, REQUEST_ID, reason,
-            controllerCallback, onTouch, activityLaunchAnimator)
+            unlockedScreenOffAnimationController, HAL_CONTROLS_ILLUMINATION, hbmProvider,
+            REQUEST_ID, reason, controllerCallback, onTouch, activityLaunchAnimator)
         block()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
index 27755ede..cd646c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
@@ -62,6 +62,7 @@
                 0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
                 componentInfo,
                 FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+                true /* halControlsIllumination */,
                 true /* resetLockoutRequiresHardwareAuthToken */,
                 List.of(new SensorLocationInternal("" /* displayId */,
                         sensorLocationX, sensorLocationY, sensorRadius)));
@@ -127,6 +128,7 @@
                 0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
                 componentInfo,
                 FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+                true /* halControlsIllumination */,
                 true /* resetLockoutRequiresHardwareAuthToken */,
                 List.of(new SensorLocationInternal("" /* displayId */,
                         sensorLocationX, sensorLocationY, sensorRadius)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index 744af58..0327cfc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
@@ -36,6 +36,7 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.nullable
@@ -43,7 +44,6 @@
 import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
 
-private const val DISPLAY_ID = "" // default display id
 private const val SENSOR_X = 50
 private const val SENSOR_Y = 250
 private const val SENSOR_RADIUS = 10
@@ -146,7 +146,7 @@
         view.startIllumination(onDone)
 
         val illuminator = withArgCaptor<Runnable> {
-            verify(hbmProvider).enableHbm(capture())
+            verify(hbmProvider).enableHbm(anyBoolean(), capture())
         }
 
         assertThat(view.isIlluminationRequested).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index be923a6..5ff316e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -413,6 +413,7 @@
                         /* max enrollments per user */ 5,
                         /* component info */ new ArrayList<>(),
                         /* sensorType */ 3,
+                        /* halControlsIllumination */ true,
                         /* resetLockoutRequiresHwToken */ false,
                         List.of(new SensorLocationInternal("" /* displayId */,
                                 (int) udfpsLocation.x, (int) udfpsLocation.y, radius)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 3c6aa48..fd0b948 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -60,6 +60,7 @@
 import android.view.DisplayInfo;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowMetrics;
@@ -163,7 +164,7 @@
     @Mock
     private UiEventLogger mUiEventLogger;
     @Mock
-    EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
+    private ViewTreeObserver mViewTreeObserver;
     @Mock
     EdgeBackGestureHandler mEdgeBackGestureHandler;
     NavBarHelper mNavBarHelper;
@@ -196,8 +197,6 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        when(mEdgeBackGestureHandlerFactory.create(any(Context.class)))
-                .thenReturn(mEdgeBackGestureHandler);
         when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController);
         when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController);
         when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton);
@@ -210,6 +209,7 @@
         when(mNavigationBarTransitions.getLightTransitionsController())
                 .thenReturn(mLightBarTransitionsController);
         when(mStatusBarKeyguardViewManager.isNavBarVisible()).thenReturn(true);
+        when(mNavigationBarView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
         setupSysuiDependency();
         // This class inflates views that call Dependency.get, thus these injections are still
         // necessary.
@@ -217,8 +217,6 @@
         mDependency.injectMockDependency(KeyguardStateController.class);
         mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
         mDependency.injectMockDependency(NavigationBarController.class);
-        mDependency.injectTestDependency(EdgeBackGestureHandler.Factory.class,
-                mEdgeBackGestureHandlerFactory);
         mDependency.injectTestDependency(OverviewProxyService.class, mOverviewProxyService);
         mDependency.injectTestDependency(NavigationModeController.class, mNavigationModeController);
         TestableLooper.get(this).runWithLooper(() -> {
@@ -458,6 +456,7 @@
                 mDeadZone,
                 mDeviceConfigProxyFake,
                 mNavigationBarTransitions,
+                mEdgeBackGestureHandler,
                 Optional.of(mock(BackAnimation.class))));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
index b45d78d..4b458f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
@@ -88,4 +88,7 @@
         return this;
     }
 
+    public static List<NotificationEntry> getRawChildren(GroupEntry groupEntry) {
+        return groupEntry.getRawChildren();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index 7692a05..742fcf5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -21,17 +21,22 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.render.NodeController
+import com.android.systemui.statusbar.notification.icon.ConversationIconManager
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertFalse
@@ -52,8 +57,10 @@
     private lateinit var promoter: NotifPromoter
     private lateinit var peopleSectioner: NotifSectioner
     private lateinit var peopleComparator: NotifComparator
+    private lateinit var beforeRenderListListener: OnBeforeRenderListListener
 
     @Mock private lateinit var pipeline: NotifPipeline
+    @Mock private lateinit var conversationIconManager: ConversationIconManager
     @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
     @Mock private lateinit var channel: NotificationChannel
     @Mock private lateinit var headerController: NodeController
@@ -66,7 +73,11 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        coordinator = ConversationCoordinator(peopleNotificationIdentifier, headerController)
+        coordinator = ConversationCoordinator(
+            peopleNotificationIdentifier,
+            conversationIconManager,
+            headerController
+        )
         whenever(channel.isImportantConversation).thenReturn(true)
 
         coordinator.attach(pipeline)
@@ -75,6 +86,9 @@
         promoter = withArgCaptor {
             verify(pipeline).addPromoter(capture())
         }
+        beforeRenderListListener = withArgCaptor {
+            verify(pipeline).addOnBeforeRenderListListener(capture())
+        }
 
         peopleSectioner = coordinator.sectioner
         peopleComparator = peopleSectioner.comparator!!
@@ -96,6 +110,25 @@
     }
 
     @Test
+    fun testPromotedImportantConversationsMakesSummaryUnimportant() {
+        val altChildA = NotificationEntryBuilder().setTag("A").build()
+        val altChildB = NotificationEntryBuilder().setTag("B").build()
+        val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build()
+        val groupEntry = GroupEntryBuilder()
+            .setParent(GroupEntry.ROOT_ENTRY)
+            .setSummary(summary)
+            .setChildren(listOf(entry, altChildA, altChildB))
+            .build()
+        assertTrue(promoter.shouldPromoteToTopLevel(entry))
+        assertFalse(promoter.shouldPromoteToTopLevel(altChildA))
+        assertFalse(promoter.shouldPromoteToTopLevel(altChildB))
+        NotificationEntryBuilder.setNewParent(entry, GroupEntry.ROOT_ENTRY)
+        GroupEntryBuilder.getRawChildren(groupEntry).remove(entry)
+        beforeRenderListListener.onBeforeRenderList(listOf(entry, groupEntry))
+        verify(conversationIconManager).setUnimportantConversations(eq(listOf(summary.key)))
+    }
+
+    @Test
     fun testInPeopleSection() {
         whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
             .thenReturn(TYPE_PERSON)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index cf99607..dc101f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -22,11 +22,14 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.notification.collection.EntryUtilKt.modifyEntry;
 import static com.android.systemui.util.mockito.KotlinMockitoHelpersKt.argThat;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
@@ -55,6 +58,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -90,7 +94,7 @@
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private HighPriorityProvider mHighPriorityProvider;
-    @Mock private StatusBarStateController mStatusBarStateController;
+    @Mock private SysuiStatusBarStateController mStatusBarStateController;
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     private final FakeSettings mFakeSettings = new FakeSettings();
 
@@ -178,7 +182,7 @@
         Consumer<String> listener = mock(Consumer.class);
         mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
 
-        callback.onStateChanged(0);
+        callback.onUpcomingStateChanged(0);
 
         verify(listener).accept(anyString());
     }
@@ -199,7 +203,7 @@
         Consumer<String> listener = mock(Consumer.class);
         mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
 
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
         callback.onReceive(mContext, new Intent(Intent.ACTION_USER_SWITCHED));
 
         verify(listener).accept(anyString());
@@ -207,7 +211,7 @@
 
     @Test
     public void notifyListeners_onSettingChange_lockScreenShowNotifs() {
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
         Consumer<String> listener = mock(Consumer.class);
         mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
 
@@ -218,7 +222,7 @@
 
     @Test
     public void notifyListeners_onSettingChange_lockScreenAllowPrivateNotifs() {
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
         Consumer<String> listener = mock(Consumer.class);
         mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
 
@@ -228,8 +232,43 @@
     }
 
     @Test
-    public void notifyListeners_onSettingChange_zenMode() {
+    public void hideSilentNotificationsPerUserSettingWithHighPriorityParent() {
         when(mKeyguardStateController.isShowing()).thenReturn(true);
+        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+        GroupEntry parent = new GroupEntryBuilder()
+                .setKey("parent")
+                .addChild(mEntry)
+                .setSummary(new NotificationEntryBuilder()
+                        .setUser(new UserHandle(NOTIF_USER_ID))
+                        .setImportance(IMPORTANCE_LOW)
+                        .build())
+                .build();
+        mEntry = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID))
+                .setImportance(IMPORTANCE_LOW)
+                .setParent(parent)
+                .build();
+        when(mHighPriorityProvider.isHighPriority(any())).thenReturn(false);
+        assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
+    public void hideSilentNotificationsPerUserSetting() {
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+        mEntry = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID))
+                .setImportance(IMPORTANCE_LOW)
+                .build();
+        when(mHighPriorityProvider.isHighPriority(any())).thenReturn(false);
+        assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
+    public void notifyListeners_onSettingChange_zenMode() {
+        when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
         Consumer<String> listener = mock(Consumer.class);
         mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
 
@@ -240,7 +279,7 @@
 
     @Test
     public void notifyListeners_onSettingChange_lockScreenShowSilentNotifs() {
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
         Consumer<String> listener = mock(Consumer.class);
         mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
 
@@ -262,7 +301,7 @@
     public void keyguardNotShowing() {
         // GIVEN the lockscreen isn't showing
         setupUnfilteredState(mEntry);
-        when(mKeyguardStateController.isShowing()).thenReturn(false);
+        when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(SHADE);
 
         // THEN don't filter out the entry
         assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
@@ -384,8 +423,8 @@
         mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
         when(mHighPriorityProvider.isHighPriority(parent)).thenReturn(true);
 
-        // THEN don't filter out the entry
-        assertFalse(
+        // THEN filter out the entry regardless of parent
+        assertTrue(
                 mKeyguardNotificationVisibilityProvider.shouldHideNotification(entryWithParent));
 
         // WHEN its parent doesn't exceed threshold to show on lockscreen
@@ -404,7 +443,7 @@
      */
     private void setupUnfilteredState(NotificationEntry entry) {
         // keyguard is showing
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
 
         // show notifications on the lockscreen
         when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(true);
@@ -452,11 +491,11 @@
                     @BindsInstance NotificationLockscreenUserManager lockscreenUserManager,
                     @BindsInstance KeyguardUpdateMonitor keyguardUpdateMonitor,
                     @BindsInstance HighPriorityProvider highPriorityProvider,
-                    @BindsInstance StatusBarStateController statusBarStateController,
+                    @BindsInstance SysuiStatusBarStateController statusBarStateController,
                     @BindsInstance BroadcastDispatcher broadcastDispatcher,
                     @BindsInstance SecureSettings secureSettings,
                     @BindsInstance GlobalSettings globalSettings
             );
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index 4986792..f43c2a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -160,7 +160,7 @@
     @Test
     public void testShow_notifiesVisibility() {
         mBouncer.show(true);
-        verify(mViewMediatorCallback).onBouncerVisiblityChanged(eq(true));
+        verify(mKeyguardStateController).notifyBouncerShowing(eq(true));
         verify(mExpansionCallback).onStartingToShow();
 
         // Not called again when visible
@@ -238,7 +238,7 @@
     @Test
     public void testHide_notifiesVisibility() {
         mBouncer.hide(false);
-        verify(mViewMediatorCallback).onBouncerVisiblityChanged(eq(false));
+        verify(mKeyguardStateController).notifyBouncerShowing(eq(false));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
index ddccd83..26199d53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.IActivityManager;
@@ -103,6 +104,7 @@
         mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
 
         mNotificationShadeWindowController.attach();
+        verify(mWindowManager).addView(eq(mNotificationShadeWindowView), any());
     }
 
     @Test
@@ -174,6 +176,14 @@
     }
 
     @Test
+    public void setScrimsVisibility_earlyReturn() {
+        clearInvocations(mWindowManager);
+        mNotificationShadeWindowController.setScrimsVisibility(ScrimController.TRANSPARENT);
+        // Abort early if value didn't change
+        verify(mWindowManager, never()).updateViewLayout(any(), mLayoutParameters.capture());
+    }
+
+    @Test
     public void attach_animatingKeyguardAndSurface_wallpaperVisible() {
         clearInvocations(mWindowManager);
         when(mKeyguardViewMediator.isShowingAndNotOccluded()).thenReturn(true);
@@ -221,6 +231,8 @@
     public void setPanelExpanded_notFocusable_altFocusable_whenPanelIsOpen() {
         mNotificationShadeWindowController.setPanelExpanded(true);
         clearInvocations(mWindowManager);
+        mNotificationShadeWindowController.setPanelExpanded(true);
+        verifyNoMoreInteractions(mWindowManager);
         mNotificationShadeWindowController.setNotificationShadeFocusable(true);
 
         verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
@@ -287,6 +299,8 @@
     public void batchApplyWindowLayoutParams_doesNotDispatchEvents() {
         mNotificationShadeWindowController.setForceDozeBrightness(true);
         verify(mWindowManager).updateViewLayout(any(), any());
+        mNotificationShadeWindowController.setForceDozeBrightness(true);
+        verifyNoMoreInteractions(mWindowManager);
 
         clearInvocations(mWindowManager);
         mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 0936b77..0112797 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -117,11 +117,18 @@
         val keyguardSpy = spy(keyguardView)
         Mockito.`when`(keyguardSpy.animate()).thenReturn(animator)
         val listener = ArgumentCaptor.forClass(Animator.AnimatorListener::class.java)
+        val endAction = ArgumentCaptor.forClass(Runnable::class.java)
         controller.animateInKeyguard(keyguardSpy, Runnable {})
         Mockito.verify(animator).setListener(listener.capture())
-        // Verify that the listener is cleared when it ends
-        listener.value.onAnimationEnd(null)
+        Mockito.verify(animator).withEndAction(endAction.capture())
+
+        // Verify that the listener is cleared if we cancel it.
+        listener.value.onAnimationCancel(null)
         Mockito.verify(animator).setListener(null)
+
+        // Verify that the listener is also cleared if the end action is triggered.
+        endAction.value.run()
+        verify(animator, times(2)).setListener(null)
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
index 14e0878..9c7dc6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
@@ -365,46 +365,4 @@
         // No new callbacks
         verify(callback).onLocationSettingsChanged(anyBoolean());
     }
-
-    @Test
-    public void testExperimentFlipsSystemFlag() throws Exception {
-        mSecureSettings.putInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0);
-        mDeviceConfigProxy.setProperty(
-                DeviceConfig.NAMESPACE_PRIVACY,
-                SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED,
-                "true",
-                true);
-        // Show system experiment not running
-        mDeviceConfigProxy.setProperty(
-                DeviceConfig.NAMESPACE_PRIVACY,
-                SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM,
-                "false",
-                false);
-        mTestableLooper.processAllMessages();
-
-        // Flip experiment on
-        mDeviceConfigProxy.setProperty(
-                DeviceConfig.NAMESPACE_PRIVACY,
-                SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM,
-                "true",
-                true);
-        mTestableLooper.processAllMessages();
-
-        // Verify settings were flipped
-        assertThat(mSecureSettings.getInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS))
-                .isEqualTo(1);
-        assertThat(mSecureSettings.getInt(Settings.Secure.LOCATION_INDICATOR_EXPERIMENT_STARTED))
-                .isEqualTo(1);
-
-        // Flip experiment off
-        mDeviceConfigProxy.setProperty(
-                DeviceConfig.NAMESPACE_PRIVACY,
-                SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM,
-                "false",
-                false);
-        mTestableLooper.processAllMessages();
-
-        assertThat(mSecureSettings.getInt(Settings.Secure.LOCATION_INDICATOR_EXPERIMENT_STARTED))
-                .isEqualTo(0);
-    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
index aaea4ec..95b62a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
@@ -49,6 +49,11 @@
     }
 
     @Override
+    public boolean isBouncerShowing() {
+        return false;
+    }
+
+    @Override
     public boolean canDismissLockScreen() {
         return false;
     }
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 193879e..238a4d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -57,6 +57,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
@@ -70,6 +71,8 @@
 import android.service.notification.ZenModeConfig;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.util.Pair;
+import android.util.SparseArray;
 import android.view.View;
 import android.view.WindowManager;
 
@@ -147,6 +150,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.Optional;
 
@@ -1010,7 +1014,7 @@
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
 
         // Should notify delegate that shade state changed
-        verify(mBubbleController).onBubbleNotificationSuppressionChanged(
+        verify(mBubbleController).onBubbleMetadataFlagChanged(
                 mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
     }
 
@@ -1027,7 +1031,7 @@
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
 
         // Should notify delegate that shade state changed
-        verify(mBubbleController).onBubbleNotificationSuppressionChanged(
+        verify(mBubbleController).onBubbleMetadataFlagChanged(
                 mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
     }
 
@@ -1447,6 +1451,69 @@
         assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE);
     }
 
+    @Test
+    public void testSetShouldAutoExpand_notifiesFlagChanged() {
+        mEntryListener.onPendingEntryAdded(mRow);
+
+        assertTrue(mBubbleController.hasBubbles());
+        Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey());
+        assertThat(b.shouldAutoExpand()).isFalse();
+
+        // Set it to the same thing
+        b.setShouldAutoExpand(false);
+
+        // Verify it doesn't notify
+        verify(mBubbleController, never()).onBubbleMetadataFlagChanged(any());
+
+        // Set it to something different
+        b.setShouldAutoExpand(true);
+        verify(mBubbleController).onBubbleMetadataFlagChanged(b);
+    }
+
+    @Test
+    public void testUpdateBubble_skipsDndSuppressListNotifs() {
+        mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
+                mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
+                mRow.shouldSuppressPeek());
+        mBubbleEntry.getBubbleMetadata().setFlags(
+                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
+
+        mBubbleController.updateBubble(mBubbleEntry);
+
+        Bubble b = mBubbleData.getPendingBubbleWithKey(mBubbleEntry.getKey());
+        assertThat(b.shouldAutoExpand()).isFalse();
+        assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey())).isNull();
+    }
+
+    @Test
+    public void testOnRankingUpdate_DndSuppressListNotif() {
+        // It's in the stack
+        mBubbleController.updateBubble(mBubbleEntry);
+        assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isTrue();
+
+        // Set current user profile
+        SparseArray<UserInfo> userInfos = new SparseArray<>();
+        userInfos.put(mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(),
+                mock(UserInfo.class));
+        mBubbleController.onCurrentProfilesChanged(userInfos);
+
+        // Send ranking update that the notif is suppressed from the list.
+        HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey = new HashMap<>();
+        mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
+                mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
+                mRow.shouldSuppressPeek());
+        Pair<BubbleEntry, Boolean> pair = new Pair(mBubbleEntry, true);
+        entryDataByKey.put(mBubbleEntry.getKey(), pair);
+
+        NotificationListenerService.RankingMap rankingMap =
+                mock(NotificationListenerService.RankingMap.class);
+        when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() });
+        mBubbleController.onRankingUpdated(rankingMap, entryDataByKey);
+
+        // Should no longer be in the stack
+        assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse();
+    }
+
     /** Creates a bubble using the userId and package. */
     private Bubble createBubble(int userId, String pkg) {
         final UserHandle userHandle = new UserHandle(userId);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index 02d8691..dff89e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -50,6 +50,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.LauncherApps;
+import android.content.pm.UserInfo;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -59,6 +60,8 @@
 import android.service.notification.ZenModeConfig;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.util.Pair;
+import android.util.SparseArray;
 import android.view.View;
 import android.view.WindowManager;
 
@@ -128,6 +131,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.Optional;
 
@@ -880,7 +884,7 @@
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
 
         // Should notify delegate that shade state changed
-        verify(mBubbleController).onBubbleNotificationSuppressionChanged(
+        verify(mBubbleController).onBubbleMetadataFlagChanged(
                 mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
     }
 
@@ -897,7 +901,7 @@
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
 
         // Should notify delegate that shade state changed
-        verify(mBubbleController).onBubbleNotificationSuppressionChanged(
+        verify(mBubbleController).onBubbleMetadataFlagChanged(
                 mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
     }
 
@@ -1267,6 +1271,69 @@
         assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE);
     }
 
+    @Test
+    public void testSetShouldAutoExpand_notifiesFlagChanged() {
+        mBubbleController.updateBubble(mBubbleEntry);
+
+        assertTrue(mBubbleController.hasBubbles());
+        Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey());
+        assertThat(b.shouldAutoExpand()).isFalse();
+
+        // Set it to the same thing
+        b.setShouldAutoExpand(false);
+
+        // Verify it doesn't notify
+        verify(mBubbleController, never()).onBubbleMetadataFlagChanged(any());
+
+        // Set it to something different
+        b.setShouldAutoExpand(true);
+        verify(mBubbleController).onBubbleMetadataFlagChanged(b);
+    }
+
+    @Test
+    public void testUpdateBubble_skipsDndSuppressListNotifs() {
+        mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
+                mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
+                mRow.shouldSuppressPeek());
+        mBubbleEntry.getBubbleMetadata().setFlags(
+                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
+
+        mBubbleController.updateBubble(mBubbleEntry);
+
+        Bubble b = mBubbleData.getPendingBubbleWithKey(mBubbleEntry.getKey());
+        assertThat(b.shouldAutoExpand()).isFalse();
+        assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey())).isNull();
+    }
+
+    @Test
+    public void testOnRankingUpdate_DndSuppressListNotif() {
+        // It's in the stack
+        mBubbleController.updateBubble(mBubbleEntry);
+        assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isTrue();
+
+        // Set current user profile
+        SparseArray<UserInfo> userInfos = new SparseArray<>();
+        userInfos.put(mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(),
+                mock(UserInfo.class));
+        mBubbleController.onCurrentProfilesChanged(userInfos);
+
+        // Send ranking update that the notif is suppressed from the list.
+        HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey = new HashMap<>();
+        mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
+                mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
+                mRow.shouldSuppressPeek());
+        Pair<BubbleEntry, Boolean> pair = new Pair(mBubbleEntry, true);
+        entryDataByKey.put(mBubbleEntry.getKey(), pair);
+
+        NotificationListenerService.RankingMap rankingMap =
+                mock(NotificationListenerService.RankingMap.class);
+        when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() });
+        mBubbleController.onRankingUpdated(rankingMap, entryDataByKey);
+
+        // Should no longer be in the stack
+        assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse();
+    }
+
     /**
      * Sets the bubble metadata flags for this entry. These flags are normally set by
      * NotificationManagerService when the notification is sent, however, these tests do not
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index b4c107c..62bb9f1 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -294,6 +294,15 @@
 
     private boolean onCompanionApplicationBindingDiedInternal(
             @UserIdInt int userId, @NonNull String packageName) {
+        // Update the current connected devices sets when binderDied, so that application is able
+        // to call notifyDeviceAppeared after re-launch the application.
+        for (AssociationInfo ai :
+                mAssociationStore.getAssociationsForPackage(userId, packageName)) {
+            int id = ai.getId();
+            Slog.i(TAG, "Removing association id: " + id + " for package: "
+                    + packageName + " due to binderDied.");
+            mDevicePresenceMonitor.removeDeviceFromMonitoring(id);
+        }
         // TODO(b/218613015): implement.
         return false;
     }
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 6371b25..24be1b6 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -206,6 +206,15 @@
     }
 
     /**
+     * Remove the current connected devices by associationId.
+     */
+    public void removeDeviceFromMonitoring(int associationId) {
+        mConnectedBtDevices.remove(associationId);
+        mNearbyBleDevices.remove(associationId);
+        mReportedSelfManagedDevices.remove(associationId);
+    }
+
+    /**
      * Implements
      * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)}
      */
@@ -217,9 +226,7 @@
             Log.d(TAG, "  > association=" + association);
         }
 
-        mConnectedBtDevices.remove(id);
-        mNearbyBleDevices.remove(id);
-        mReportedSelfManagedDevices.remove(id);
+        removeDeviceFromMonitoring(id);
 
         // Do NOT call mCallback.onDeviceDisappeared()!
         // CompanionDeviceManagerService will know that the association is removed, and will do
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ed36da7..40f3109 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -89,7 +89,6 @@
 import static android.os.Process.myPid;
 import static android.os.Process.myUid;
 import static android.os.Process.readProcFile;
-import static android.os.Process.removeAllProcessGroups;
 import static android.os.Process.sendSignal;
 import static android.os.Process.setThreadPriority;
 import static android.os.Process.setThreadScheduler;
@@ -2430,8 +2429,6 @@
     }
 
     private void start() {
-        removeAllProcessGroups();
-
         mBatteryStatsService.publish();
         mAppOpsService.publish();
         mProcessStats.publish();
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index f7abb11..6f5d87b 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -265,6 +265,20 @@
      */
     private int[] mDeviceIdleExceptIdleAllowlist = new int[0]; // No lock is needed.
 
+    /**
+     * The pre-configured system app-ids in the power-save allow list.
+     *
+     * @see #mDeviceIdleAllowlist.
+     */
+    private final ArraySet<Integer> mSystemDeviceIdleAllowlist = new ArraySet<>();
+
+    /**
+     * The pre-configured system app-ids in the power-save allow list, except-idle.
+     *
+     * @see #mDeviceIdleExceptIdleAllowlist.
+     */
+    private final ArraySet<Integer> mSystemDeviceIdleExceptIdleAllowlist = new ArraySet<>();
+
     private final Object mLock = new Object();
     private final Object mSettingsLock = new Object();
     private final Injector mInjector;
@@ -1511,14 +1525,33 @@
     }
 
     private void initBgRestrictionExemptioFromSysConfig() {
-        mBgRestrictionExemptioFromSysConfig =
-                SystemConfig.getInstance().getBgRestrictionExemption();
+        final SystemConfig sysConfig = SystemConfig.getInstance();
+        mBgRestrictionExemptioFromSysConfig = sysConfig.getBgRestrictionExemption();
         if (DEBUG_BG_RESTRICTION_CONTROLLER) {
             final ArraySet<String> exemptedPkgs = mBgRestrictionExemptioFromSysConfig;
             for (int i = exemptedPkgs.size() - 1; i >= 0; i--) {
                 Slog.i(TAG, "bg-restriction-exemption: " + exemptedPkgs.valueAt(i));
             }
         }
+        loadAppIdsFromPackageList(sysConfig.getAllowInPowerSaveExceptIdle(),
+                mSystemDeviceIdleExceptIdleAllowlist);
+        loadAppIdsFromPackageList(sysConfig.getAllowInPowerSave(), mSystemDeviceIdleAllowlist);
+    }
+
+    private void loadAppIdsFromPackageList(ArraySet<String> packages, ArraySet<Integer> apps) {
+        final PackageManager pm = mInjector.getPackageManager();
+        for (int i = packages.size() - 1; i >= 0; i--) {
+            final String pkg = packages.valueAt(i);
+            try {
+                final ApplicationInfo ai = pm.getApplicationInfo(pkg,
+                        PackageManager.MATCH_SYSTEM_ONLY);
+                if (ai == null) {
+                    continue;
+                }
+                apps.add(UserHandle.getAppId(ai.uid));
+            } catch (PackageManager.NameNotFoundException e) {
+            }
+        }
     }
 
     private boolean isExemptedFromSysConfig(String packageName) {
@@ -2685,6 +2718,13 @@
                 || Arrays.binarySearch(mDeviceIdleExceptIdleAllowlist, appId) >= 0;
     }
 
+    boolean isOnSystemDeviceIdleAllowlist(int uid) {
+        final int appId = UserHandle.getAppId(uid);
+
+        return mSystemDeviceIdleAllowlist.contains(appId)
+                || mSystemDeviceIdleExceptIdleAllowlist.contains(appId);
+    }
+
     void setDeviceIdleAllowlist(int[] allAppids, int[] exceptIdleAppids) {
         mDeviceIdleAllowlist = allAppids;
         mDeviceIdleExceptIdleAllowlist = exceptIdleAppids;
@@ -2703,6 +2743,9 @@
         if (UserHandle.isCore(uid)) {
             return REASON_SYSTEM_UID;
         }
+        if (isOnSystemDeviceIdleAllowlist(uid)) {
+            return REASON_SYSTEM_ALLOW_LISTED;
+        }
         if (isOnDeviceIdleAllowlist(uid)) {
             return REASON_ALLOWLISTED_PACKAGE;
         }
@@ -2748,7 +2791,7 @@
                 } else if (isExemptedFromSysConfig(pkg)) {
                     return REASON_SYSTEM_ALLOW_LISTED;
                 } else if (mConstantsObserver.mBgRestrictionExemptedPackages.contains(pkg)) {
-                    return REASON_ALLOWLISTED_PACKAGE;
+                    return REASON_SYSTEM_ALLOW_LISTED;
                 } else if (pm.isPackageStateProtected(pkg, userId)) {
                     return REASON_DPO_PROTECTED_APP;
                 }
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index a172018..e49497e 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -479,19 +479,33 @@
     @GuardedBy("mProcLock")
     void compactAppSome(ProcessRecord app, boolean force) {
         app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_SOME);
-        if (DEBUG_COMPACTION) {
-            Slog.d(TAG_AM, " compactAppSome requested for " + app.processName + " force: " + force);
+        compactApp(app, force, "some");
+    }
+
+    // This method returns true only if requirements are met. Note, that requirements are different
+    // from throttles applied at the time a compaction is trying to be executed in the sense that
+    // these are not subject to change dependent on time or memory as throttles usually do.
+    @GuardedBy("mProcLock")
+    boolean meetsCompactionRequirements(ProcessRecord proc) {
+        if (mAm.mInternal.isPendingTopUid(proc.uid)) {
+            // In case the OOM Adjust has not yet been propagated we see if this is
+            // pending on becoming top app in which case we should not compact.
+            if (DEBUG_COMPACTION) {
+                Slog.d(TAG_AM, "Skip compaction since UID is active for  " + proc.processName);
+            }
+            return false;
         }
-        if (force || !app.mOptRecord.hasPendingCompact()) {
-            Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
-                    "compactAppSome " + app.processName != null ? app.processName : "");
-            app.mOptRecord.setHasPendingCompact(true);
-            app.mOptRecord.setForceCompact(force);
-            mPendingCompactionProcesses.add(app);
-            mCompactionHandler.sendMessage(
-                    mCompactionHandler.obtainMessage(
-                    COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
+
+        if (proc.mState.hasForegroundActivities()) {
+            if (DEBUG_COMPACTION) {
+                Slog.e(TAG_AM,
+                        "Skip compaction as process " + proc.processName
+                                + " has foreground activities");
+            }
+            return false;
         }
+
+        return true;
     }
 
     @GuardedBy("mProcLock")
@@ -508,19 +522,7 @@
         // Apply OOM adj score throttle for Full App Compaction.
         if (force || oomAdjEnteredCached) {
             app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_FULL);
-            if (!app.mOptRecord.hasPendingCompact()) {
-                Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
-                        "compactAppFull " + app.processName != null ? app.processName : "");
-                app.mOptRecord.setHasPendingCompact(true);
-                app.mOptRecord.setForceCompact(force);
-                mPendingCompactionProcesses.add(app);
-                mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
-                        COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
-            } else if (DEBUG_COMPACTION) {
-                Slog.d(TAG_AM,
-                        " compactAppFull Skipped for " + app.processName
-                                + " since it has a pending compact");
-            }
+            compactApp(app, force, "Full");
         } else {
             if (DEBUG_COMPACTION) {
                 Slog.d(TAG_AM, "Skipping full compaction for " + app.processName
@@ -533,15 +535,34 @@
     @GuardedBy("mProcLock")
     void compactAppPersistent(ProcessRecord app) {
         app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_PERSISTENT);
-        if (!app.mOptRecord.hasPendingCompact()) {
+        compactApp(app, false, "Persistent");
+    }
+
+    @GuardedBy("mProcLock")
+    boolean compactApp(ProcessRecord app, boolean force, String compactRequestType) {
+        if (!app.mOptRecord.hasPendingCompact() && meetsCompactionRequirements(app)) {
+            final String processName = (app.processName != null ? app.processName : "");
+            if (DEBUG_COMPACTION) {
+                Slog.d(TAG_AM, "compactApp " + compactRequestType + " " + processName);
+            }
             Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
-                    "compactAppPersistent " + app.processName != null ? app.processName : "");
+                    "compactApp " + compactRequestType + " " + processName);
             app.mOptRecord.setHasPendingCompact(true);
+            app.mOptRecord.setForceCompact(force);
             mPendingCompactionProcesses.add(app);
-            mCompactionHandler.sendMessage(
-                    mCompactionHandler.obtainMessage(
+            mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
                     COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
+            return true;
         }
+
+        if (DEBUG_COMPACTION) {
+            Slog.d(TAG_AM,
+                    " compactApp Skipped for " + app.processName
+                            + " pendingCompact= " + app.mOptRecord.hasPendingCompact()
+                            + " meetsCompactionRequirements=" + meetsCompactionRequirements(app)
+                            + ". Requested compact: " + app.mOptRecord.getReqCompactAction());
+        }
+        return false;
     }
 
     @GuardedBy("mProcLock")
@@ -553,15 +574,7 @@
     @GuardedBy("mProcLock")
     void compactAppBfgs(ProcessRecord app) {
         app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_BFGS);
-        if (!app.mOptRecord.hasPendingCompact()) {
-            Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
-                    "compactAppBfgs " + app.processName != null ? app.processName : "");
-            app.mOptRecord.setHasPendingCompact(true);
-            mPendingCompactionProcesses.add(app);
-            mCompactionHandler.sendMessage(
-                    mCompactionHandler.obtainMessage(
-                    COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
-        }
+        compactApp(app, false, " Bfgs");
     }
 
     @GuardedBy("mProcLock")
@@ -572,6 +585,9 @@
 
     void compactAllSystem() {
         if (useCompaction()) {
+            if (DEBUG_COMPACTION) {
+                Slog.d(TAG_AM, "compactAllSystem");
+            }
             Trace.instantForTrack(
                     Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK, "compactAllSystem");
             mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
@@ -1175,13 +1191,13 @@
             cancelCompaction();
         }
 
-        // Perform a minor compaction when a perceptible app becomes the prev/home app
-        // Perform a major compaction when any app enters cached
         if (oldAdj <= ProcessList.PERCEPTIBLE_APP_ADJ
                 && (newAdj == ProcessList.PREVIOUS_APP_ADJ || newAdj == ProcessList.HOME_APP_ADJ)) {
+            // Perform a minor compaction when a perceptible app becomes the prev/home app
             compactAppSome(app, false);
         } else if (newAdj >= ProcessList.CACHED_APP_MIN_ADJ
                 && newAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
+            // Perform a major compaction when any app enters cached
             compactAppFull(app, false);
         }
     }
@@ -1241,12 +1257,6 @@
 
         private boolean shouldOomAdjThrottleCompaction(ProcessRecord proc, int action) {
             final String name = proc.processName;
-            if (mAm.mInternal.isPendingTopUid(proc.uid)) {
-                // In case the OOM Adjust has not yet been propagated we see if this is
-                // pending on becoming top app in which case we should not compact.
-                Slog.e(TAG_AM, "Skip compaction since UID is active for  " + name);
-                return true;
-            }
 
             // don't compact if the process has returned to perceptible
             // and this is only a cached/home/prev compaction
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index b6757c8..17fff91 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -72,7 +72,7 @@
             Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
             Context.BIND_FOREGROUND_SERVICE,
             Context.BIND_TREAT_LIKE_ACTIVITY,
-            Context.BIND_VISIBLE,
+            Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
             Context.BIND_SHOWING_UI,
             Context.BIND_NOT_VISIBLE,
             Context.BIND_NOT_PERCEPTIBLE,
@@ -225,8 +225,8 @@
         if ((flags & Context.BIND_SCHEDULE_LIKE_TOP_APP) != 0) {
             sb.append("SLTA ");
         }
-        if ((flags&Context.BIND_VISIBLE) != 0) {
-            sb.append("VIS ");
+        if ((flags & Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE) != 0) {
+            sb.append("VFGS ");
         }
         if ((flags&Context.BIND_SHOWING_UI) != 0) {
             sb.append("UI ");
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 4d10574..e7fcc59 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -39,6 +39,7 @@
 import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
@@ -2061,6 +2062,10 @@
                                     newAdj = ProcessList.PERCEPTIBLE_APP_ADJ;
                                 } else if (clientAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
                                     newAdj = clientAdj;
+                                } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)
+                                        && clientAdj <= ProcessList.VISIBLE_APP_ADJ
+                                        && adj > ProcessList.VISIBLE_APP_ADJ) {
+                                    newAdj = ProcessList.VISIBLE_APP_ADJ;
                                 } else {
                                     if (adj > ProcessList.VISIBLE_APP_ADJ) {
                                         // TODO: Is this too limiting for apps bound from TOP?
@@ -2097,7 +2102,9 @@
                                 // processes).  These should not bring the current process
                                 // into the top state, since they are not on top.  Instead
                                 // give them the best bound state after that.
-                                if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) {
+                                if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)) {
+                                    clientProcState = PROCESS_STATE_FOREGROUND_SERVICE;
+                                } else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) {
                                     clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
                                 } else if (mService.mWakefulness.get()
                                         == PowerManagerInternal.WAKEFULNESS_AWAKE
@@ -2556,20 +2563,21 @@
             // reminder: here, setAdj is previous state, curAdj is upcoming state
             if (state.getCurAdj() != state.getSetAdj()) {
                 mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app);
-            } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
-                    && state.getSetAdj() < ProcessList.FOREGROUND_APP_ADJ
-                    && !state.isRunningRemoteAnimation()
-                    // Because these can fire independent of oom_adj/procstate changes, we need
-                    // to throttle the actual dispatch of these requests in addition to the
-                    // processing of the requests. As a result, there is throttling both here
-                    // and in CachedAppOptimizer.
-                    && mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
-                mCachedAppOptimizer.compactAppPersistent(app);
-            } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
-                    && state.getCurProcState()
-                        == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
-                    && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
-                mCachedAppOptimizer.compactAppBfgs(app);
+            } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE) {
+                // See if we can compact persistent and bfgs services now that screen is off
+                if (state.getSetAdj() < ProcessList.FOREGROUND_APP_ADJ
+                        && !state.isRunningRemoteAnimation()
+                        // Because these can fire independent of oom_adj/procstate changes, we need
+                        // to throttle the actual dispatch of these requests in addition to the
+                        // processing of the requests. As a result, there is throttling both here
+                        // and in CachedAppOptimizer.
+                        && mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
+                    mCachedAppOptimizer.compactAppPersistent(app);
+                } else if (state.getCurProcState()
+                                == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+                        && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
+                    mCachedAppOptimizer.compactAppBfgs(app);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 1f3fbff..aed63ce 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9135,6 +9135,8 @@
         if (timeOutMs <= 0 || usages.length == 0) {
             throw new IllegalArgumentException("Invalid timeOutMs/usagesToMute");
         }
+        Log.i(TAG, "muteAwaitConnection dev:" + device + " timeOutMs:" + timeOutMs
+                + " usages:" + usages);
 
         if (mDeviceBroker.isDeviceConnected(device)) {
             // not throwing an exception as there could be a race between a connection (server-side,
@@ -9178,7 +9180,7 @@
                 Log.i(TAG, "cancelMuteAwaitConnection ignored, no expected device");
                 return;
             }
-            if (!device.equals(mMutingExpectedDevice)) {
+            if (!device.equalTypeAddress(mMutingExpectedDevice)) {
                 Log.e(TAG, "cancelMuteAwaitConnection ignored, got " + device
                         + "] but expected device is" + mMutingExpectedDevice);
                 throw new IllegalStateException("cancelMuteAwaitConnection for wrong device");
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 74c8999..565783f 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -1158,6 +1158,8 @@
     //==========================================================================================
     void muteAwaitConnection(@NonNull int[] usagesToMute,
             @NonNull AudioDeviceAttributes dev, long timeOutMs) {
+        sEventLogger.loglogi(
+                "muteAwaitConnection() dev:" + dev + " timeOutMs:" + timeOutMs, TAG);
         synchronized (mPlayerLock) {
             mutePlayersExpectingDevice(usagesToMute);
             // schedule timeout (remove previously scheduled first)
@@ -1169,6 +1171,7 @@
     }
 
     void cancelMuteAwaitConnection() {
+        sEventLogger.loglogi("cancelMuteAwaitConnection()", TAG);
         synchronized (mPlayerLock) {
             // cancel scheduled timeout, ignore device, only one expected device at a time
             mEventHandler.removeMessages(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION);
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index b5c8cd1..bc550d3 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -805,9 +805,10 @@
         if (isUdfps && udfpsProps.length == 3) {
             return new FingerprintSensorPropertiesInternal(sensorId,
                     Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser,
-                    componentInfo, sensorType, resetLockoutRequiresHardwareAuthToken,
-                    List.of(new SensorLocationInternal("" /* display */,
-                            udfpsProps[0], udfpsProps[1], udfpsProps[2])));
+                    componentInfo, sensorType, true /* halControlsIllumination */,
+                    resetLockoutRequiresHardwareAuthToken,
+                    List.of(new SensorLocationInternal("" /* display */, udfpsProps[0],
+                            udfpsProps[1], udfpsProps[2])));
         } else {
             return new FingerprintSensorPropertiesInternal(sensorId,
                     Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 998a8e1..a600f08 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -176,6 +176,7 @@
                             prop.commonProps.maxEnrollmentsPerUser,
                             componentInfo,
                             prop.sensorType,
+                            prop.halControlsIllumination,
                             true /* resetLockoutRequiresHardwareAuthToken */,
                             !workaroundLocations.isEmpty() ? workaroundLocations :
                                     Arrays.stream(prop.sensorLocations).map(location ->
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 485a674..bea0f4f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -400,7 +400,7 @@
                 .getInteger(R.integer.config_fingerprintMaxTemplatesPerUser);
         mSensorProperties = new FingerprintSensorPropertiesInternal(sensorProps.sensorId,
                 sensorProps.sensorStrength, maxTemplatesAllowed, sensorProps.componentInfo,
-                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, false /* halControlsIllumination */,
                 resetLockoutRequiresHardwareAuthToken, sensorProps.getAllLocations());
         mMockHalResultController = controller;
         mUserHasTrust = new SparseBooleanArray();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index f3d41c40..2cf11c6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1440,16 +1440,6 @@
             DisplayDevice device =
                     mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
             if (device != null) {
-                final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
-                if (display != null) {
-                    final int displayId = display.getDisplayIdLocked();
-                    if (mDisplayWindowPolicyControllers.contains(displayId)) {
-                        Pair<IVirtualDevice, DisplayWindowPolicyController> pair =
-                                mDisplayWindowPolicyControllers.removeReturnOld(displayId);
-                        getLocalService(VirtualDeviceManagerInternal.class)
-                                .onVirtualDisplayRemoved(pair.first, displayId);
-                    }
-                }
                 // TODO: multi-display - handle virtual displays the same as other display adapters.
                 mDisplayDeviceRepo.onDisplayDeviceEvent(device,
                         DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
@@ -1609,6 +1599,17 @@
         DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
         sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
         scheduleTraversalLocked(false);
+
+        if (mDisplayWindowPolicyControllers.contains(displayId)) {
+            final IVirtualDevice virtualDevice = mDisplayWindowPolicyControllers.removeReturnOld(
+                    displayId).first;
+            if (virtualDevice != null) {
+                mHandler.post(() -> {
+                    getLocalService(VirtualDeviceManagerInternal.class)
+                            .onVirtualDisplayRemoved(virtualDevice, displayId);
+                });
+            }
+        }
     }
 
     private void handleLogicalDisplaySwappedLocked(@NonNull LogicalDisplay display) {
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index 02f9ceb..89902f7 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -51,14 +51,16 @@
     void onNotificationSettingsViewed(String key);
     /**
      * Called when the state of {@link Notification#FLAG_BUBBLE} is changed.
+     *
+     * @param key the notification key
+     * @param isBubble whether the notification should have {@link Notification#FLAG_BUBBLE} applied
+     * @param flags the flags to apply to the notification's {@link Notification.BubbleMetadata}
      */
     void onNotificationBubbleChanged(String key, boolean isBubble, int flags);
     /**
-     * Called when the state of {@link Notification.BubbleMetadata#FLAG_SUPPRESS_NOTIFICATION}
-     * or {@link Notification.BubbleMetadata#FLAG_SUPPRESS_BUBBLE} changes.
+     * Called when the flags on {@link Notification.BubbleMetadata} are changed.
      */
-    void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed,
-            boolean isBubbleSuppressed);
+    void onBubbleMetadataFlagChanged(String key, int flags);
 
     /**
      * Grant permission to read the specified URI to the package associated with the
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index c548e7e..8a62736 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -42,4 +42,7 @@
 
     /** Does the specified package/uid have permission to post notifications? */
     boolean areNotificationsEnabledForPackage(String pkg, int uid);
+
+    /** Send a notification to the user prompting them to review their notification permissions. */
+    void sendReviewPermissionsNotification();
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0c11d8f..537385e 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
 import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
@@ -274,6 +275,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.SomeArgs;
@@ -442,6 +444,18 @@
 
     private static final int NOTIFICATION_INSTANCE_ID_MAX = (1 << 13);
 
+    // States for the review permissions notification
+    static final int REVIEW_NOTIF_STATE_UNKNOWN = -1;
+    static final int REVIEW_NOTIF_STATE_SHOULD_SHOW = 0;
+    static final int REVIEW_NOTIF_STATE_USER_INTERACTED = 1;
+    static final int REVIEW_NOTIF_STATE_DISMISSED = 2;
+    static final int REVIEW_NOTIF_STATE_RESHOWN = 3;
+
+    // Action strings for review permissions notification
+    static final String REVIEW_NOTIF_ACTION_REMIND = "REVIEW_NOTIF_ACTION_REMIND";
+    static final String REVIEW_NOTIF_ACTION_DISMISS = "REVIEW_NOTIF_ACTION_DISMISS";
+    static final String REVIEW_NOTIF_ACTION_CANCELED = "REVIEW_NOTIF_ACTION_CANCELED";
+
     /**
      * Apps that post custom toasts in the background will have those blocked. Apps can
      * still post toasts created with
@@ -652,6 +666,9 @@
     private InstanceIdSequence mNotificationInstanceIdSequence;
     private Set<String> mMsgPkgsAllowedAsConvos = new HashSet();
 
+    // Broadcast intent receiver for notification permissions review-related intents
+    private ReviewNotificationPermissionsReceiver mReviewNotificationPermissionsReceiver;
+
     static class Archive {
         final SparseArray<Boolean> mEnabled;
         final int mBufferSize;
@@ -1413,8 +1430,7 @@
         }
 
         @Override
-        public void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed,
-                boolean isBubbleSuppressed) {
+        public void onBubbleMetadataFlagChanged(String key, int flags) {
             synchronized (mNotificationLock) {
                 NotificationRecord r = mNotificationsByKey.get(key);
                 if (r != null) {
@@ -1424,17 +1440,12 @@
                         return;
                     }
 
-                    boolean flagChanged = false;
-                    if (data.isNotificationSuppressed() != isNotifSuppressed) {
-                        flagChanged = true;
-                        data.setSuppressNotification(isNotifSuppressed);
-                    }
-                    if (data.isBubbleSuppressed() != isBubbleSuppressed) {
-                        flagChanged = true;
-                        data.setSuppressBubble(isBubbleSuppressed);
-                    }
-                    if (flagChanged) {
+                    if (flags != data.getFlags()) {
+                        data.setFlags(flags);
+                        // Shouldn't alert again just because of a flag change.
                         r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
+                        // Force isAppForeground true here, because for sysui's purposes we
+                        // want to be able to adjust the flag behaviour.
                         mHandler.post(
                                 new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r,
                                         true /* isAppForeground */, SystemClock.elapsedRealtime()));
@@ -2416,6 +2427,11 @@
 
         IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
         getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter);
+
+        mReviewNotificationPermissionsReceiver = new ReviewNotificationPermissionsReceiver();
+        getContext().registerReceiver(mReviewNotificationPermissionsReceiver,
+                ReviewNotificationPermissionsReceiver.getFilter(),
+                Context.RECEIVER_NOT_EXPORTED);
     }
 
     /**
@@ -2709,6 +2725,7 @@
             mHistoryManager.onBootPhaseAppsCanStart();
             registerDeviceConfigChange();
             migrateDefaultNAS();
+            maybeShowInitialReviewPermissionsNotification();
         } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
             mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
         }
@@ -6336,6 +6353,21 @@
         public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
             return areNotificationsEnabledForPackageInt(pkg, uid);
         }
+
+        @Override
+        public void sendReviewPermissionsNotification() {
+            // This method is meant to be called from the JobService upon running the job for this
+            // notification having been rescheduled; so without checking any other state, it will
+            // send the notification.
+            checkCallerIsSystem();
+            NotificationManager nm = getContext().getSystemService(NotificationManager.class);
+            nm.notify(TAG,
+                    SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS,
+                    createReviewPermissionsNotification());
+            Settings.Global.putInt(getContext().getContentResolver(),
+                    Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                    NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
+        }
     };
 
     int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) {
@@ -7145,10 +7177,12 @@
                             && r.getNotification().isBubbleNotification())
                             || (mReason == REASON_CLICK && r.canBubble()
                             && r.isFlagBubbleRemoved())) {
-                        boolean isBubbleSuppressed = r.getNotification().getBubbleMetadata() != null
-                                && r.getNotification().getBubbleMetadata().isBubbleSuppressed();
-                        mNotificationDelegate.onBubbleNotificationSuppressionChanged(
-                                r.getKey(), true /* notifSuppressed */, isBubbleSuppressed);
+                        int flags = 0;
+                        if (r.getNotification().getBubbleMetadata() != null) {
+                            flags = r.getNotification().getBubbleMetadata().getFlags();
+                        }
+                        flags |= FLAG_SUPPRESS_NOTIFICATION;
+                        mNotificationDelegate.onBubbleMetadataFlagChanged(r.getKey(), flags);
                         return;
                     }
                     if ((r.getNotification().flags & mMustHaveFlags) != mMustHaveFlags) {
@@ -11613,6 +11647,76 @@
         out.endTag(null, LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG);
     }
 
+    // Creates a notification that informs the user about changes due to the migration to
+    // use permissions for notifications.
+    protected Notification createReviewPermissionsNotification() {
+        int title = R.string.review_notification_settings_title;
+        int content = R.string.review_notification_settings_text;
+
+        // Tapping on the notification leads to the settings screen for managing app notifications,
+        // using the intent reserved for system services to indicate it comes from this notification
+        Intent tapIntent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS_FOR_REVIEW);
+        Intent remindIntent = new Intent(REVIEW_NOTIF_ACTION_REMIND);
+        Intent dismissIntent = new Intent(REVIEW_NOTIF_ACTION_DISMISS);
+        Intent swipeIntent = new Intent(REVIEW_NOTIF_ACTION_CANCELED);
+
+        // Both "remind me" and "dismiss" actions will be actions received by the BroadcastReceiver
+        final Notification.Action remindMe = new Notification.Action.Builder(null,
+                getContext().getResources().getString(
+                        R.string.review_notification_settings_remind_me_action),
+                PendingIntent.getBroadcast(
+                        getContext(), 0, remindIntent,
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
+                .build();
+        final Notification.Action dismiss = new Notification.Action.Builder(null,
+                getContext().getResources().getString(
+                        R.string.review_notification_settings_dismiss),
+                PendingIntent.getBroadcast(
+                        getContext(), 0, dismissIntent,
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
+                .build();
+
+        return new Notification.Builder(getContext(), SystemNotificationChannels.SYSTEM_CHANGES)
+                .setSmallIcon(R.drawable.stat_sys_adb)
+                .setContentTitle(getContext().getResources().getString(title))
+                .setContentText(getContext().getResources().getString(content))
+                .setContentIntent(PendingIntent.getActivity(getContext(), 0, tapIntent,
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
+                .setStyle(new Notification.BigTextStyle())
+                .setFlag(Notification.FLAG_NO_CLEAR, true)
+                .setAutoCancel(true)
+                .addAction(remindMe)
+                .addAction(dismiss)
+                .setDeleteIntent(PendingIntent.getBroadcast(getContext(), 0, swipeIntent,
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
+                .build();
+    }
+
+    protected void maybeShowInitialReviewPermissionsNotification() {
+        int currentState = Settings.Global.getInt(getContext().getContentResolver(),
+                Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                REVIEW_NOTIF_STATE_UNKNOWN);
+
+        // now check the last known state of the notification -- this determination of whether the
+        // user is in the correct target audience occurs elsewhere, and will have written the
+        // REVIEW_NOTIF_STATE_SHOULD_SHOW to indicate it should be shown in the future.
+        //
+        // alternatively, if the user has rescheduled the notification (so it has been shown
+        // again) but not yet interacted with the new notification, then show it again on boot,
+        // as this state indicates that the user had the notification open before rebooting.
+        //
+        // sending the notification here does not record a new state for the notification;
+        // that will be written by parts of the system further down the line if at any point
+        // the user interacts with the notification.
+        if (currentState == REVIEW_NOTIF_STATE_SHOULD_SHOW
+                || currentState == REVIEW_NOTIF_STATE_RESHOWN) {
+            NotificationManager nm = getContext().getSystemService(NotificationManager.class);
+            nm.notify(TAG,
+                    SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS,
+                    createReviewPermissionsNotification());
+        }
+    }
+
     /**
      * Shows a warning on logcat. Shows the toast only once per package. This is to avoid being too
      * aggressive and annoying the user.
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 0525b1e..ef3c770 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -96,6 +96,10 @@
     private final int XML_VERSION;
     /** What version to check to do the upgrade for bubbles. */
     private static final int XML_VERSION_BUBBLES_UPGRADE = 1;
+    /** The first xml version with notification permissions enabled. */
+    private static final int XML_VERSION_NOTIF_PERMISSION = 3;
+    /** The first xml version that notifies users to review their notification permissions */
+    private static final int XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION = 4;
     @VisibleForTesting
     static final int UNKNOWN_UID = UserHandle.USER_NULL;
     private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":";
@@ -206,7 +210,7 @@
         mStatsEventBuilderFactory = statsEventBuilderFactory;
 
         if (mPermissionHelper.isMigrationEnabled()) {
-            XML_VERSION = 3;
+            XML_VERSION = 4;
         } else {
             XML_VERSION = 2;
         }
@@ -226,8 +230,16 @@
 
         final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1);
         boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE;
-        boolean migrateToPermission =
-                (xmlVersion < XML_VERSION) && mPermissionHelper.isMigrationEnabled();
+        boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION)
+                && mPermissionHelper.isMigrationEnabled();
+        if (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION) {
+            // make a note that we should show the notification at some point.
+            // it shouldn't be possible for the user to already have seen it, as the XML version
+            // would be newer then.
+            Settings.Global.putInt(mContext.getContentResolver(),
+                    Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                    NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
+        }
         ArrayList<PermissionHelper.PackagePermission> pkgPerms = new ArrayList<>();
         synchronized (mPackagePreferences) {
             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
diff --git a/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
new file mode 100644
index 0000000..fde45f71
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+
+/**
+ * JobService implementation for scheduling the notification informing users about
+ * notification permissions updates and taking them to review their existing permissions.
+ * @hide
+ */
+public class ReviewNotificationPermissionsJobService extends JobService {
+    public static final String TAG = "ReviewNotificationPermissionsJobService";
+
+    @VisibleForTesting
+    protected static final int JOB_ID = 225373531;
+
+    /**
+     *  Schedule a new job that will show a notification the specified amount of time in the future.
+     */
+    public static void scheduleJob(Context context, long rescheduleTimeMillis) {
+        JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+        // if the job already exists for some reason, cancel & reschedule
+        if (jobScheduler.getPendingJob(JOB_ID) != null) {
+            jobScheduler.cancel(JOB_ID);
+        }
+        ComponentName component = new ComponentName(
+                context, ReviewNotificationPermissionsJobService.class);
+        JobInfo newJob = new JobInfo.Builder(JOB_ID, component)
+                .setPersisted(true) // make sure it'll still get rescheduled after reboot
+                .setMinimumLatency(rescheduleTimeMillis)  // run after specified amount of time
+                .build();
+        jobScheduler.schedule(newJob);
+    }
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        // While jobs typically should be run on different threads, this
+        // job only posts a notification, which is not a long-running operation
+        // as notification posting is asynchronous.
+        NotificationManagerInternal nmi =
+                LocalServices.getService(NotificationManagerInternal.class);
+        nmi.sendReviewPermissionsNotification();
+
+        // once the notification is posted, the job is done, so no need to
+        // keep it alive afterwards
+        return false;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        // If we're interrupted for some reason, try again (though this may not
+        // ever happen due to onStartJob not leaving a job running after being
+        // called)
+        return true;
+    }
+}
diff --git a/services/core/java/com/android/server/notification/ReviewNotificationPermissionsReceiver.java b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsReceiver.java
new file mode 100644
index 0000000..b99aeac
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsReceiver.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.messages.nano.SystemMessageProto;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
+
+/**
+ * Broadcast receiver for intents that come from the "review notification permissions" notification,
+ * shown to users who upgrade to T from an earlier OS to inform them of notification setup changes
+ * and invite them to review their notification permissions.
+ */
+public class ReviewNotificationPermissionsReceiver extends BroadcastReceiver {
+    public static final String TAG = "ReviewNotifPermissions";
+    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // 7 days in millis, as the amount of time to wait before re-sending the notification
+    private static final long JOB_RESCHEDULE_TIME = 1000 /* millis */ * 60 /* seconds */
+            * 60 /* minutes */ * 24 /* hours */ * 7 /* days */;
+
+    static IntentFilter getFilter() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND);
+        filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS);
+        filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
+        return filter;
+    }
+
+    // Cancels the "review notification permissions" notification.
+    @VisibleForTesting
+    protected void cancelNotification(Context context) {
+        NotificationManager nm = context.getSystemService(NotificationManager.class);
+        if (nm != null) {
+            nm.cancel(NotificationManagerService.TAG,
+                    SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS);
+        } else {
+            Slog.w(TAG, "could not cancel notification: NotificationManager not found");
+        }
+    }
+
+    @VisibleForTesting
+    protected void rescheduleNotification(Context context) {
+        ReviewNotificationPermissionsJobService.scheduleJob(context, JOB_RESCHEDULE_TIME);
+        // log if needed
+        if (DEBUG) {
+            Slog.d(TAG, "Scheduled review permissions notification for on or after: "
+                    + LocalDateTime.now(ZoneId.systemDefault())
+                            .plus(JOB_RESCHEDULE_TIME, ChronoUnit.MILLIS));
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND)) {
+            // Reschedule the notification for 7 days in the future
+            rescheduleNotification(context);
+
+            // note that the user has interacted; no longer needed to show the initial
+            // notification
+            Settings.Global.putInt(context.getContentResolver(),
+                    Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                    NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
+            cancelNotification(context);
+        } else if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS)) {
+            // user dismissed; write to settings so we don't show ever again
+            Settings.Global.putInt(context.getContentResolver(),
+                    Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                    NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED);
+            cancelNotification(context);
+        } else if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED)) {
+            // we may get here from the user swiping away the notification,
+            // or from the notification being canceled in any other way.
+            // only in the case that the user hasn't interacted with it in
+            // any other way yet, reschedule
+            int notifState = Settings.Global.getInt(context.getContentResolver(),
+                    Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                    /* default */ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN);
+            if (notifState == NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW) {
+                // user hasn't interacted in the past, so reschedule once and then note that the
+                // user *has* interacted now so we don't re-reschedule if they swipe again
+                rescheduleNotification(context);
+                Settings.Global.putInt(context.getContentResolver(),
+                        Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                        NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
+            } else if (notifState == NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN) {
+                // swiping away on a rescheduled notification; mark as interacted and
+                // don't reschedule again.
+                Settings.Global.putInt(context.getContentResolver(),
+                        Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                        NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/AppIdSettingMap.java b/services/core/java/com/android/server/pm/AppIdSettingMap.java
index b41a0b8..20c67f8 100644
--- a/services/core/java/com/android/server/pm/AppIdSettingMap.java
+++ b/services/core/java/com/android/server/pm/AppIdSettingMap.java
@@ -19,6 +19,7 @@
 import android.os.Process;
 import android.util.Log;
 
+import com.android.server.utils.SnapshotCache;
 import com.android.server.utils.WatchedArrayList;
 import com.android.server.utils.WatchedSparseArray;
 import com.android.server.utils.Watcher;
@@ -34,10 +35,28 @@
      * index to the corresponding SettingBase object is (appId - FIRST_APPLICATION_ID). If an app ID
      * doesn't exist (i.e., app is not installed), we fill the corresponding entry with null.
      */
-    private WatchedArrayList<SettingBase> mNonSystemSettings = new WatchedArrayList<>();
-    private WatchedSparseArray<SettingBase> mSystemSettings = new WatchedSparseArray<>();
+    private final WatchedArrayList<SettingBase> mNonSystemSettings;
+    private final SnapshotCache<WatchedArrayList<SettingBase>> mNonSystemSettingsSnapshot;
+    private final WatchedSparseArray<SettingBase> mSystemSettings;
+    private final SnapshotCache<WatchedSparseArray<SettingBase>> mSystemSettingsSnapshot;
     private int mFirstAvailableAppId = Process.FIRST_APPLICATION_UID;
 
+    AppIdSettingMap() {
+        mNonSystemSettings = new WatchedArrayList<>();
+        mNonSystemSettingsSnapshot = new SnapshotCache.Auto<>(
+                mNonSystemSettings, mNonSystemSettings, "AppIdSettingMap.mNonSystemSettings");
+        mSystemSettings = new WatchedSparseArray<>();
+        mSystemSettingsSnapshot = new SnapshotCache.Auto<>(
+                mSystemSettings, mSystemSettings, "AppIdSettingMap.mSystemSettings");
+    }
+
+    AppIdSettingMap(AppIdSettingMap orig) {
+        mNonSystemSettings = orig.mNonSystemSettingsSnapshot.snapshot();
+        mNonSystemSettingsSnapshot = new SnapshotCache.Sealed<>();
+        mSystemSettings = orig.mSystemSettingsSnapshot.snapshot();
+        mSystemSettingsSnapshot = new SnapshotCache.Sealed<>();
+    }
+
     /** Returns true if the requested AppID was valid and not already registered. */
     public boolean registerExistingAppId(int appId, SettingBase setting, Object name) {
         if (appId >= Process.FIRST_APPLICATION_UID) {
@@ -134,10 +153,7 @@
     }
 
     public AppIdSettingMap snapshot() {
-        AppIdSettingMap l = new AppIdSettingMap();
-        mNonSystemSettings.snapshot(l.mNonSystemSettings, mNonSystemSettings);
-        mSystemSettings.snapshot(l.mSystemSettings, mSystemSettings);
-        return l;
+        return new AppIdSettingMap(this);
     }
 
     public void registerObserver(Watcher observer) {
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index 89f8be2..daac7c0 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -236,13 +236,6 @@
 
         verifyActivityCanHandleIntent(launchIntent, callingUid, userId);
 
-        // Always show the cross profile animation
-        if (options == null) {
-            options = ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle();
-        } else {
-            options.putAll(ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle());
-        }
-
         mInjector.getActivityTaskManagerInternal()
                 .startActivityAsUser(
                         caller,
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index fef6ce1..4df54b7 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -349,7 +349,7 @@
     private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
         final ShortcutInfo shortcut = mShortcuts.remove(id);
         if (shortcut != null) {
-            mShortcutUser.mService.removeIconLocked(shortcut);
+            removeIcon(shortcut);
             shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
                     | ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED_ALL);
         }
@@ -366,7 +366,7 @@
         forceDeleteShortcutInner(newShortcut.getId());
 
         // Extract Icon and update the icon res ID and the bitmap path.
-        s.saveIconAndFixUpShortcutLocked(newShortcut);
+        s.saveIconAndFixUpShortcutLocked(this, newShortcut);
         s.fixUpShortcutResourceNamesAndValues(newShortcut);
         saveShortcut(newShortcut);
     }
@@ -972,7 +972,8 @@
     /**
      * Return the filenames (excluding path names) of icon bitmap files from this package.
      */
-    public ArraySet<String> getUsedBitmapFiles() {
+    @GuardedBy("mLock")
+    private ArraySet<String> getUsedBitmapFilesLocked() {
         final ArraySet<String> usedFiles = new ArraySet<>(1);
         forEachShortcut(si -> {
             if (si.getBitmapPath() != null) {
@@ -982,6 +983,26 @@
         return usedFiles;
     }
 
+    public void cleanupDanglingBitmapFiles(@NonNull File path) {
+        synchronized (mLock) {
+            mShortcutBitmapSaver.waitForAllSavesLocked();
+            final ArraySet<String> usedFiles = getUsedBitmapFilesLocked();
+
+            for (File child : path.listFiles()) {
+                if (!child.isFile()) {
+                    continue;
+                }
+                final String name = child.getName();
+                if (!usedFiles.contains(name)) {
+                    if (ShortcutService.DEBUG) {
+                        Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath());
+                    }
+                    child.delete();
+                }
+            }
+        }
+    }
+
     private static String getFileName(@NonNull String path) {
         final int sep = path.lastIndexOf(File.separatorChar);
         if (sep == -1) {
@@ -1608,6 +1629,11 @@
         pw.print(" (");
         pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize[0]));
         pw.println(")");
+
+        pw.println();
+        synchronized (mLock) {
+            mShortcutBitmapSaver.dumpLocked(pw, "  ");
+        }
     }
 
     public void dumpShortcuts(@NonNull PrintWriter pw, int matchFlags) {
@@ -1729,7 +1755,7 @@
         // Note: at this point no shortcuts should have bitmaps pending save, but if they do,
         // just remove the bitmap.
         if (si.isIconPendingSave()) {
-            s.removeIconLocked(si);
+            removeIcon(si);
         }
         out.startTag(null, TAG_SHORTCUT);
         ShortcutService.writeAttr(out, ATTR_ID, si.getId());
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 6e0436f..7800183 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -16,8 +16,10 @@
 package com.android.server.pm;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.pm.PackageInfo;
 import android.content.pm.ShortcutInfo;
+import android.graphics.Bitmap;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.TypedXmlSerializer;
@@ -50,6 +52,9 @@
 
     protected ShortcutUser mShortcutUser;
 
+    @GuardedBy("mLock")
+    protected ShortcutBitmapSaver mShortcutBitmapSaver;
+
     protected final Object mLock = new Object();
 
     protected ShortcutPackageItem(@NonNull ShortcutUser shortcutUser,
@@ -59,6 +64,7 @@
         mPackageUserId = packageUserId;
         mPackageName = Preconditions.checkStringNotEmpty(packageName);
         mPackageInfo = Objects.requireNonNull(packageInfo);
+        mShortcutBitmapSaver = new ShortcutBitmapSaver(shortcutUser.mService);
     }
 
     /**
@@ -206,7 +212,7 @@
 
     void saveShortcutPackageItem() {
         // Wait for bitmap saves to conclude before proceeding to saving shortcuts.
-        mShortcutUser.mService.waitForBitmapSaves();
+        waitForBitmapSaves();
         // Save each ShortcutPackageItem in a separate Xml file.
         final File path = getShortcutPackageItemFile();
         if (ShortcutService.DEBUG || ShortcutService.DEBUG_REBOOT) {
@@ -221,6 +227,35 @@
         }
     }
 
+    public boolean waitForBitmapSaves() {
+        synchronized (mLock) {
+            return mShortcutBitmapSaver.waitForAllSavesLocked();
+        }
+    }
+
+    public void saveBitmap(ShortcutInfo shortcut,
+            int maxDimension, Bitmap.CompressFormat format, int quality) {
+        synchronized (mLock) {
+            mShortcutBitmapSaver.saveBitmapLocked(shortcut, maxDimension, format, quality);
+        }
+    }
+
+    /**
+     * Wait for all pending saves to finish, and then return the given shortcut's bitmap path.
+     */
+    @Nullable
+    public String getBitmapPathMayWait(ShortcutInfo shortcut) {
+        synchronized (mLock) {
+            return mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcut);
+        }
+    }
+
+    public void removeIcon(ShortcutInfo shortcut) {
+        synchronized (mLock) {
+            mShortcutBitmapSaver.removeIcon(shortcut);
+        }
+    }
+
     void removeShortcutPackageItem() {
         synchronized (mLock) {
             getShortcutPackageItemFile().delete();
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 780f976..f2bcf5e 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -363,7 +363,6 @@
     private final RoleManager mRoleManager;
 
     private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor;
-    private final ShortcutBitmapSaver mShortcutBitmapSaver;
     private final ShortcutDumpFiles mShortcutDumpFiles;
 
     @GuardedBy("mLock")
@@ -490,7 +489,6 @@
         mRoleManager = Objects.requireNonNull(mContext.getSystemService(RoleManager.class));
 
         mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock);
-        mShortcutBitmapSaver = new ShortcutBitmapSaver(this);
         mShortcutDumpFiles = new ShortcutDumpFiles(this);
         mIsAppSearchEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.SHORTCUT_APPSEARCH_INTEGRATION, true)
@@ -1063,8 +1061,6 @@
             Slog.d(TAG, "Saving to " + path);
         }
 
-        mShortcutBitmapSaver.waitForAllSavesLocked();
-
         path.getParentFile().mkdirs();
         final AtomicFile file = new AtomicFile(path);
         FileOutputStream os = null;
@@ -1388,15 +1384,12 @@
 
     // === Caller validation ===
 
-    void removeIconLocked(ShortcutInfo shortcut) {
-        mShortcutBitmapSaver.removeIcon(shortcut);
-    }
-
     public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) {
         final File packagePath = new File(getUserBitmapFilePath(userId), packageName);
         if (!packagePath.isDirectory()) {
             return;
         }
+        // ShortcutPackage is already removed at this point, we can safely remove the folder.
         if (!(FileUtils.deleteContents(packagePath) && packagePath.delete())) {
             Slog.w(TAG, "Unable to remove directory " + packagePath);
         }
@@ -1437,38 +1430,12 @@
                 }
                 cleanupBitmapsForPackage(userId, packageName);
             } else {
-                cleanupDanglingBitmapFilesLocked(userId, user, packageName, child);
+                user.getPackageShortcuts(packageName).cleanupDanglingBitmapFiles(child);
             }
         }
         logDurationStat(Stats.CLEANUP_DANGLING_BITMAPS, start);
     }
 
-    /**
-     * Remove dangling bitmap files for a package.
-     *
-     * Note this method must be called with the lock held after calling
-     * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap
-     * saves are going on.
-     */
-    private void cleanupDanglingBitmapFilesLocked(@UserIdInt int userId, @NonNull ShortcutUser user,
-            @NonNull String packageName, @NonNull File path) {
-        final ArraySet<String> usedFiles =
-                user.getPackageShortcuts(packageName).getUsedBitmapFiles();
-
-        for (File child : path.listFiles()) {
-            if (!child.isFile()) {
-                continue;
-            }
-            final String name = child.getName();
-            if (!usedFiles.contains(name)) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath());
-                }
-                child.delete();
-            }
-        }
-    }
-
     @VisibleForTesting
     static class FileOutputStreamWithPath extends FileOutputStream {
         private final File mFile;
@@ -1513,7 +1480,7 @@
         }
     }
 
-    void saveIconAndFixUpShortcutLocked(ShortcutInfo shortcut) {
+    void saveIconAndFixUpShortcutLocked(ShortcutPackage p, ShortcutInfo shortcut) {
         if (shortcut.hasIconFile() || shortcut.hasIconResource() || shortcut.hasIconUri()) {
             return;
         }
@@ -1521,7 +1488,7 @@
         final long token = injectClearCallingIdentity();
         try {
             // Clear icon info on the shortcut.
-            removeIconLocked(shortcut);
+            p.removeIcon(shortcut);
 
             final Icon icon = shortcut.getIcon();
             if (icon == null) {
@@ -1560,8 +1527,7 @@
                         // just in case.
                         throw ShortcutInfo.getInvalidIconException();
                 }
-                mShortcutBitmapSaver.saveBitmapLocked(shortcut,
-                        maxIconDimension, mIconPersistFormat, mIconPersistQuality);
+                p.saveBitmap(shortcut, maxIconDimension, mIconPersistFormat, mIconPersistQuality);
             } finally {
                 // Once saved, we won't use the original icon information, so null it out.
                 shortcut.clearIcon();
@@ -2110,7 +2076,7 @@
 
                     final boolean replacingIcon = (source.getIcon() != null);
                     if (replacingIcon) {
-                        removeIconLocked(target);
+                        ps.removeIcon(target);
                     }
 
                     // Note copyNonNullFieldsFrom() does the "updatable with?" check too.
@@ -2118,7 +2084,7 @@
                     target.setTimestamp(injectCurrentTimeMillis());
 
                     if (replacingIcon) {
-                        saveIconAndFixUpShortcutLocked(target);
+                        saveIconAndFixUpShortcutLocked(ps, target);
                     }
 
                     // When we're updating any resource related fields, re-extract the res
@@ -3463,7 +3429,7 @@
                 if (shortcutInfo == null) {
                     return null;
                 }
-                return getShortcutIconParcelFileDescriptor(shortcutInfo);
+                return getShortcutIconParcelFileDescriptor(p, shortcutInfo);
             }
         }
 
@@ -3476,6 +3442,7 @@
             Objects.requireNonNull(shortcutId, "shortcutId");
 
             // Checks shortcuts in memory first
+            final ShortcutPackage p;
             synchronized (mLock) {
                 throwIfUserLockedL(userId);
                 throwIfUserLockedL(launcherUserId);
@@ -3483,8 +3450,7 @@
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
                         .attemptToRestoreIfNeededAndSave();
 
-                final ShortcutPackage p = getUserShortcutsLocked(userId)
-                        .getPackageShortcutsIfExists(packageName);
+                p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName);
                 if (p == null) {
                     cb.complete(null);
                     return;
@@ -3492,24 +3458,23 @@
 
                 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
                 if (shortcutInfo != null) {
-                    cb.complete(getShortcutIconParcelFileDescriptor(shortcutInfo));
+                    cb.complete(getShortcutIconParcelFileDescriptor(p, shortcutInfo));
                     return;
                 }
             }
 
             // Otherwise check persisted shortcuts
-            getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> {
-                cb.complete(getShortcutIconParcelFileDescriptor(si));
-            });
+            getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si ->
+                    cb.complete(getShortcutIconParcelFileDescriptor(p, si)));
         }
 
         @Nullable
         private ParcelFileDescriptor getShortcutIconParcelFileDescriptor(
-                @Nullable final ShortcutInfo shortcutInfo) {
-            if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
+                @Nullable final ShortcutPackage p, @Nullable final ShortcutInfo shortcutInfo) {
+            if (p == null || shortcutInfo == null || !shortcutInfo.hasIconFile()) {
                 return null;
             }
-            final String path = mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcutInfo);
+            final String path = p.getBitmapPathMayWait(shortcutInfo);
             if (path == null) {
                 Slog.w(TAG, "null bitmap detected in getShortcutIconFd()");
                 return null;
@@ -4772,9 +4737,6 @@
                 }
 
                 pw.println();
-                mShortcutBitmapSaver.dumpLocked(pw, "  ");
-
-                pw.println();
             }
 
             for (int i = 0; i < mUsers.size(); i++) {
@@ -5347,9 +5309,11 @@
         }
     }
 
-    void waitForBitmapSaves() {
+    @VisibleForTesting
+    void waitForBitmapSavesForTest() {
         synchronized (mLock) {
-            mShortcutBitmapSaver.waitForAllSavesLocked();
+            forEachLoadedUserLocked(u ->
+                    u.forAllPackageItems(ShortcutPackageItem::waitForBitmapSaves));
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 75e18b5..b9fd2fd 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -401,6 +401,7 @@
 
     private void saveShortcutPackageItem(TypedXmlSerializer out, ShortcutPackageItem spi,
             boolean forBackup) throws IOException, XmlPullParserException {
+        spi.waitForBitmapSaves();
         if (forBackup) {
             if (spi.getPackageUserId() != spi.getOwnerUserId()) {
                 return; // Don't save cross-user information.
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 14cdbe9..b7f6b6f 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1430,6 +1430,8 @@
     /**
      * Returns a UserInfo object with the name filled in, for Owner and Guest, or the original
      * if the name is already set.
+     *
+     * Note: Currently, the resulting name can be null if a user was truly created with a null name.
      */
     private UserInfo userWithName(UserInfo orig) {
         if (orig != null && orig.name == null) {
@@ -1638,7 +1640,7 @@
     }
 
     @Override
-    public String getUserName() {
+    public @NonNull String getUserName() {
         final int callingUid = Binder.getCallingUid();
         if (!hasQueryOrCreateUsersPermission()
                 && !hasPermissionGranted(
@@ -1649,7 +1651,10 @@
         final int userId = UserHandle.getUserId(callingUid);
         synchronized (mUsersLock) {
             UserInfo userInfo = userWithName(getUserInfoLU(userId));
-            return userInfo == null ? "" : userInfo.name;
+            if (userInfo != null && userInfo.name != null) {
+                return userInfo.name;
+            }
+            return "";
         }
     }
 
@@ -4207,7 +4212,7 @@
      * @return the converted user, or {@code null} if no pre-created user could be converted.
      */
     private @Nullable UserInfo convertPreCreatedUserIfPossible(String userType,
-            @UserInfoFlag int flags, String name, @Nullable Object token) {
+            @UserInfoFlag int flags, @Nullable String name, @Nullable Object token) {
         final UserData preCreatedUserData;
         synchronized (mUsersLock) {
             preCreatedUserData = getPreCreatedUserLU(userType);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 2277d8a..7be83b0 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -2816,14 +2816,12 @@
                             }
 
                             // Remove review flag as it is not necessary anymore
-                            // TODO(b/227186603) re-enable check for notification permission once
-                            // droidfood state has been cleared
-                            //if (!NOTIFICATION_PERMISSIONS.contains(perm)) {
+                            if (!NOTIFICATION_PERMISSIONS.contains(perm)) {
                                 if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
                                     flags &= ~FLAG_PERMISSION_REVIEW_REQUIRED;
                                     wasChanged = true;
                                 }
-                            //}
+                            }
 
                             if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0
                                     && !isPermissionSplitFromNonRuntime(permName,
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f14bdca..ba4175b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -171,7 +171,6 @@
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
 import android.view.IDisplayFoldListener;
-import android.view.IWindowManager;
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyCharacterMap.FallbackAction;
@@ -383,7 +382,6 @@
     private final SparseArray<ScreenOnListener> mScreenOnListeners = new SparseArray<>();
 
     Context mContext;
-    IWindowManager mWindowManager;
     WindowManagerFuncs mWindowManagerFuncs;
     WindowManagerInternal mWindowManagerInternal;
     PowerManager mPowerManager;
@@ -1883,10 +1881,8 @@
 
     /** {@inheritDoc} */
     @Override
-    public void init(Context context, IWindowManager windowManager,
-            WindowManagerFuncs windowManagerFuncs) {
+    public void init(Context context, WindowManagerFuncs windowManagerFuncs) {
         mContext = context;
-        mWindowManager = windowManager;
         mWindowManagerFuncs = windowManagerFuncs;
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
@@ -4814,10 +4810,7 @@
         }
 
         if (enableScreen) {
-            try {
-                mWindowManager.enableScreenIfNeeded();
-            } catch (RemoteException unhandled) {
-            }
+            mWindowManagerFuncs.enableScreenIfNeeded();
         }
     }
 
@@ -5274,12 +5267,7 @@
     }
 
     void updateRotation(boolean alwaysSendConfiguration) {
-        try {
-            // Set orientation on WindowManager.
-            mWindowManager.updateRotation(alwaysSendConfiguration, false /* forceRelayout */);
-        } catch (RemoteException e) {
-            // Ignore
-        }
+        mWindowManagerFuncs.updateRotation(alwaysSendConfiguration, false /* forceRelayout */);
     }
 
     /**
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 7ca09ab..e8a3dcd 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -78,7 +78,6 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 import android.view.IDisplayFoldListener;
-import android.view.IWindowManager;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
@@ -343,6 +342,22 @@
          * @return {@code true} if app transition state is idle on the default display.
          */
         boolean isAppTransitionStateIdle();
+
+        /**
+         * Enables the screen if all conditions are met.
+         */
+        void enableScreenIfNeeded();
+
+        /**
+         * Updates the current screen rotation based on the current state of the world.
+         *
+         * @param alwaysSendConfiguration Flag to force a new configuration to be evaluated.
+         *                                This can be used when there are other parameters in
+         *                                configuration that are changing.
+         * @param forceRelayout If true, the window manager will always do a relayout of its
+         *                      windows even if the rotation hasn't changed.
+         */
+        void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout);
     }
 
     /**
@@ -391,8 +406,7 @@
      *
      * @param context The system context we are running in.
      */
-    public void init(Context context, IWindowManager windowManager,
-            WindowManagerFuncs windowManagerFuncs);
+    void init(Context context, WindowManagerFuncs windowManagerFuncs);
 
     /**
      * Check permissions when adding a window.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index b685572..d48f263 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -1650,13 +1650,11 @@
     }
 
     @Override
-    public void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed,
-            boolean isBubbleSuppressed) {
+    public void onBubbleMetadataFlagChanged(String key, int flags) {
         enforceStatusBarService();
         final long identity = Binder.clearCallingIdentity();
         try {
-            mNotificationDelegate.onBubbleNotificationSuppressionChanged(key, isNotifSuppressed,
-                    isBubbleSuppressed);
+            mNotificationDelegate.onBubbleMetadataFlagChanged(key, flags);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 5410dd8..caaaf47 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -34,6 +34,8 @@
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_RELAUNCH;
 import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE;
@@ -64,6 +66,9 @@
 import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_dreamActivityCloseExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_dreamActivityOpenExitAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindSourceAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindTargetAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation;
@@ -645,9 +650,15 @@
             @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean isVoiceInteraction,
             boolean freeform, WindowContainer container) {
 
-        if (mNextAppTransitionOverrideRequested
-                && (container.canCustomizeAppTransition() || mOverrideTaskTransition)) {
-            mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
+        final boolean canCustomizeAppTransition = container.canCustomizeAppTransition();
+
+        if (mNextAppTransitionOverrideRequested) {
+            if (canCustomizeAppTransition || mOverrideTaskTransition) {
+                mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
+            } else {
+                ProtoLog.e(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: "
+                        + " override requested, but it is prohibited by policy.");
+            }
         }
 
         Animation a;
@@ -835,14 +846,26 @@
                             ? WindowAnimation_activityCloseEnterAnimation
                             : WindowAnimation_activityCloseExitAnimation;
                     break;
+                case TRANSIT_OLD_DREAM_ACTIVITY_OPEN:
+                    animAttr = enter
+                            ? WindowAnimation_dreamActivityOpenEnterAnimation
+                            : WindowAnimation_dreamActivityOpenExitAnimation;
+                    break;
+                case TRANSIT_OLD_DREAM_ACTIVITY_CLOSE:
+                    animAttr = enter
+                            ? 0
+                            : WindowAnimation_dreamActivityCloseExitAnimation;
+                    break;
             }
-            a = animAttr != 0 ? loadAnimationAttr(lp, animAttr, transit) : null;
 
+            a = animAttr == 0 ? null : (canCustomizeAppTransition
+                    ? loadAnimationAttr(lp, animAttr, transit)
+                    : mTransitionAnimation.loadDefaultAnimationAttr(animAttr, transit));
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: anim=%s animAttr=0x%x transit=%s isEntrance=%b "
-                            + "Callers=%s",
+                            + " canCustomizeAppTransition=%b Callers=%s",
                     a, animAttr, appTransitionOldToString(transit), enter,
-                    Debug.getCallers(3));
+                    canCustomizeAppTransition, Debug.getCallers(3));
         }
         setAppTransitionFinishedCallbackIfNeeded(a);
 
@@ -1157,6 +1180,12 @@
             case TRANSIT_OLD_TASK_FRAGMENT_CHANGE: {
                 return "TRANSIT_OLD_TASK_FRAGMENT_CHANGE";
             }
+            case TRANSIT_OLD_DREAM_ACTIVITY_OPEN: {
+                return "TRANSIT_OLD_DREAM_ACTIVITY_OPEN";
+            }
+            case TRANSIT_OLD_DREAM_ACTIVITY_CLOSE: {
+                return "TRANSIT_OLD_DREAM_ACTIVITY_CLOSE";
+            }
             default: {
                 return "<UNKNOWN: " + transition + ">";
             }
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 701fc94..5640a30 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
@@ -32,6 +33,8 @@
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_RELAUNCH;
 import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE;
@@ -314,6 +317,9 @@
             ArraySet<WindowContainer> changingContainers, @Nullable WindowState wallpaperTarget,
             @Nullable WindowState oldWallpaper, boolean skipAppTransitionAnimation) {
 
+        final ActivityRecord topOpeningApp = getTopApp(openingApps, false /* ignoreHidden */);
+        final ActivityRecord topClosingApp = getTopApp(closingApps, true /* ignoreHidden */);
+
         // Determine if closing and opening app token sets are wallpaper targets, in which case
         // special animations are needed.
         final boolean openingAppHasWallpaper = canBeWallpaperTarget(openingApps)
@@ -321,7 +327,7 @@
         final boolean closingAppHasWallpaper = canBeWallpaperTarget(closingApps)
                 && wallpaperTarget != null;
 
-        // Keyguard transit has highest priority.
+        // Keyguard transit has high priority.
         switch (appTransition.getKeyguardTransition()) {
             case TRANSIT_KEYGUARD_GOING_AWAY:
                 return openingAppHasWallpaper ? TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER
@@ -336,6 +342,15 @@
                 return TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
         }
 
+        // Determine whether the top opening and closing activity is a dream activity. If so, this
+        // has higher priority than others except keyguard transit.
+        if (topOpeningApp != null && topOpeningApp.getActivityType() == ACTIVITY_TYPE_DREAM) {
+            return TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
+        } else if (topClosingApp != null
+                && topClosingApp.getActivityType() == ACTIVITY_TYPE_DREAM) {
+            return TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
+        }
+
         // This is not keyguard transition and one of the app has request to skip app transition.
         if (skipAppTransitionAnimation) {
             return WindowManager.TRANSIT_OLD_UNSET;
@@ -399,11 +414,6 @@
             }
         }
 
-        final ActivityRecord topOpeningApp = getTopApp(openingApps,
-                false /* ignoreHidden */);
-        final ActivityRecord topClosingApp = getTopApp(closingApps,
-                true /* ignoreHidden */);
-
         if (closingAppHasWallpaper && openingAppHasWallpaper) {
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Wallpaper animation!");
             switch (firstTransit) {
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index efe617d..2bae59a 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -19,7 +19,6 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
@@ -602,22 +601,12 @@
                 || mDisplayContent.getAsyncRotationController() != null) {
             return;
         }
-        boolean shouldTranslateNavBar = false;
-        final boolean isDisplayLandscape =
-                mDisplayContent.getConfiguration().orientation == ORIENTATION_LANDSCAPE;
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
             final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
             final Task task = adapter.mTask;
-            final TaskFragment adjacentTask = task.getRootTask().getAdjacentTaskFragment();
-            final boolean inSplitScreen = task.inSplitScreen();
-            if (task.isActivityTypeHomeOrRecents()
-                    // Skip if the task is in split screen and in landscape.
-                    || (inSplitScreen && isDisplayLandscape)
-                    // Skip if the task is the top task in split screen.
-                    || (inSplitScreen && task.getBounds().top < adjacentTask.getBounds().top)) {
+            if (task.isActivityTypeHomeOrRecents()) {
                 continue;
             }
-            shouldTranslateNavBar = inSplitScreen;
             mNavBarAttachedApp = task.getTopVisibleActivity();
             break;
         }
@@ -630,9 +619,7 @@
         navWindow.mToken.cancelAnimation();
         final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction();
         final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl();
-        if (shouldTranslateNavBar) {
-            navWindow.setSurfaceTranslationY(-mNavBarAttachedApp.getBounds().top);
-        }
+        navWindow.setSurfaceTranslationY(-mNavBarAttachedApp.getBounds().top);
         t.reparent(navSurfaceControl, mNavBarAttachedApp.getSurfaceControl());
         t.show(navSurfaceControl);
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 1bf26bb..ded5beb 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -28,7 +28,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
@@ -1708,13 +1707,6 @@
                 && (topTask == null || topTask.supportsSplitScreenWindowingModeInner(tda));
     }
 
-    /** Returns {@code true} if this task is currently in split-screen. */
-    boolean inSplitScreen() {
-        return getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
-                && getCreatedByOrganizerTask() != null
-                && getCreatedByOrganizerTask().getAdjacentTaskFragment() != null;
-    }
-
     private boolean supportsSplitScreenWindowingModeInner(@Nullable TaskDisplayArea tda) {
         return super.supportsSplitScreenWindowingMode()
                 && mAtmService.mSupportsSplitScreenMultiWindow
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index fdb88312..f8a02743 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1165,20 +1165,21 @@
             }
         }
 
-        // For a better split UX, If a task is launching from a created-by-organizer task, it should
-        // be launched into the same created-by-organizer task as well. Unless, the candidate task
-        // is already positioned in the split.
-        Task preferredRootInSplit = sourceTask != null && sourceTask.inSplitScreen()
-                ? sourceTask.getCreatedByOrganizerTask() : null;
-        if (preferredRootInSplit != null) {
-            if (candidateTask != null) {
-                final Task candidateRoot = candidateTask.getCreatedByOrganizerTask();
-                if (candidateRoot != null && candidateRoot != preferredRootInSplit
-                        && preferredRootInSplit == candidateRoot.getAdjacentTaskFragment()) {
-                    preferredRootInSplit = candidateRoot;
+        // If a task is launching from a created-by-organizer task, it should be launched into the
+        // same created-by-organizer task as well. Unless, the candidate task is already positioned
+        // in the another adjacent task.
+        if (sourceTask != null) {
+            Task launchTarget = sourceTask.getCreatedByOrganizerTask();
+            if (launchTarget != null && launchTarget.getAdjacentTaskFragment() != null) {
+                if (candidateTask != null) {
+                    final Task candidateRoot = candidateTask.getCreatedByOrganizerTask();
+                    if (candidateRoot != null && candidateRoot != launchTarget
+                            && launchTarget == candidateRoot.getAdjacentTaskFragment()) {
+                        launchTarget = candidateRoot;
+                    }
                 }
+                return launchTarget;
             }
-            return preferredRootInSplit;
         }
 
         return null;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 777d74a..3ab8014 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1173,7 +1173,7 @@
             @Override
             public void run() {
                 WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());
-                mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);
+                mPolicy.init(mContext, WindowManagerService.this);
             }
         }, 0);
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 91c6532..d5c07af 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3628,13 +3628,11 @@
         final int requested = mLastRequestedExclusionHeight[side];
         final int granted = mLastGrantedExclusionHeight[side];
 
-        final boolean inSplitScreen = getTask() != null && getTask().inSplitScreen();
-
         FrameworkStatsLog.write(FrameworkStatsLog.EXCLUSION_RECT_STATE_CHANGED,
                 mAttrs.packageName, requested, requested - granted /* rejected */,
                 side + 1 /* Sides are 1-indexed in atoms.proto */,
                 (getConfiguration().orientation == ORIENTATION_LANDSCAPE),
-                inSplitScreen, (int) duration);
+                false /* (deprecated param) inSplitscreen */, (int) duration);
     }
 
     private void initExclusionRestrictions() {
@@ -5572,7 +5570,7 @@
                 mLastSurfaceInsets.set(mAttrs.surfaceInsets);
             }
             if (surfaceSizeChanged && mWinAnimator.getShown() && !canPlayMoveAnimation()
-                    && okToDisplay() && !useBLASTSync()) {
+                    && okToDisplay() && mSyncState == SYNC_STATE_NONE) {
                 applyWithNextDraw(mSetSurfacePositionConsumer);
             } else {
                 mSetSurfacePositionConsumer.accept(t);
@@ -6009,7 +6007,6 @@
         // We could be more subtle with Integer.MAX_VALUE and track a seqId in the timeout.
         finishDrawing(null, Integer.MAX_VALUE);
         mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
-        if (!useBLASTSync()) return;
     }
 
     @Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 2f68306..8d59dce 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -30,6 +30,7 @@
 import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT;
 import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI;
 import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
 import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
@@ -1591,6 +1592,69 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_DoOne_TreatLikeVisFGS() {
+        final ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+        final ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+                MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+        final ProcessRecord client1 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+                MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+        final ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
+                MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
+        client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+        client2.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+
+        final ServiceRecord s1 = bindService(app1, client1, null,
+                Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, mock(IBinder.class));
+        final ServiceRecord s2 = bindService(app2, client2, null,
+                Context.BIND_IMPORTANT, mock(IBinder.class));
+
+        sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+        assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
+                SCHED_GROUP_DEFAULT);
+        assertProcStates(app2, PROCESS_STATE_PERSISTENT, PERSISTENT_SERVICE_ADJ,
+                SCHED_GROUP_DEFAULT);
+
+        bindService(app2, client1, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
+                mock(IBinder.class));
+        sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+        assertProcStates(app2, PROCESS_STATE_PERSISTENT, PERSISTENT_SERVICE_ADJ,
+                SCHED_GROUP_DEFAULT);
+
+        s1.getConnections().clear();
+        s2.getConnections().clear();
+        client1.mState.setMaxAdj(UNKNOWN_ADJ);
+        client2.mState.setMaxAdj(UNKNOWN_ADJ);
+        client1.mServices.setHasForegroundServices(true, 0);
+        client2.mState.setHasOverlayUi(true);
+
+        bindService(app1, client1, s1, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
+                mock(IBinder.class));
+        bindService(app2, client2, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
+                mock(IBinder.class));
+
+        sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+        assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+                SCHED_GROUP_DEFAULT);
+        assertProcStates(app2, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ,
+                SCHED_GROUP_DEFAULT);
+
+        client2.mState.setHasOverlayUi(false);
+        doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
+        doReturn(client2).when(sService).getTopApp();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+
+        sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+        assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
+                SCHED_GROUP_DEFAULT);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_UidIdle_StopService() {
         final ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 4094377..fdf9354 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -1975,7 +1975,7 @@
         if (si == null) {
             return null;
         }
-        mService.waitForBitmapSaves();
+        mService.waitForBitmapSavesForTest();
         return new File(si.getBitmapPath()).getName();
     }
 
@@ -1984,7 +1984,7 @@
         if (si == null) {
             return null;
         }
-        mService.waitForBitmapSaves();
+        mService.waitForBitmapSavesForTest();
         return new File(si.getBitmapPath()).getAbsolutePath();
     }
 
@@ -2139,7 +2139,7 @@
     }
 
     protected boolean bitmapDirectoryExists(String packageName, int userId) {
-        mService.waitForBitmapSaves();
+        mService.waitForBitmapSavesForTest();
         final File path = new File(mService.getUserBitmapFilePath(userId), packageName);
         return path.isDirectory();
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 411b521..867890f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -1040,7 +1040,7 @@
 
         dumpsysOnLogcat();
 
-        mService.waitForBitmapSaves();
+        mService.waitForBitmapSavesForTest();
         // Check files and directories.
         // Package 3 has no bitmaps, so we don't create a directory.
         assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
@@ -1096,7 +1096,7 @@
         makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "3").createNewFile();
         makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "4").createNewFile();
 
-        mService.waitForBitmapSaves();
+        mService.waitForBitmapSavesForTest();
         assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3,
                 "a.b.c", "d.e.f");
 
@@ -1111,7 +1111,7 @@
         // The below check is the same as above, except this time USER_0 use the CALLING_PACKAGE_3
         // directory.
 
-        mService.waitForBitmapSaves();
+        mService.waitForBitmapSavesForTest();
         assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3);
         assertBitmapDirectories(USER_10, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
 
@@ -1390,7 +1390,7 @@
                             .setIcon(Icon.createWithContentUri("test_uri"))
                             .build()
             )));
-            mService.waitForBitmapSaves();
+            mService.waitForBitmapSavesForTest();
             assertWith(getCallerShortcuts())
                     .forShortcutWithId("s1", si -> {
                         assertTrue(si.hasIconUri());
@@ -1402,13 +1402,13 @@
                             .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32))
                             .build()
             )));
-            mService.waitForBitmapSaves();
+            mService.waitForBitmapSavesForTest();
             assertWith(getCallerShortcuts())
                     .forShortcutWithId("s1", si -> {
                         assertTrue(si.hasIconResource());
                         assertEquals(R.drawable.black_32x32, si.getIconResourceId());
                     });
-            mService.waitForBitmapSaves();
+            mService.waitForBitmapSavesForTest();
 
             mInjectedCurrentTimeMillis += INTERVAL; // reset throttling
 
@@ -1419,7 +1419,7 @@
                                     getTestContext().getResources(), R.drawable.black_64x64)))
                             .build()
             )));
-            mService.waitForBitmapSaves();
+            mService.waitForBitmapSavesForTest();
             assertWith(getCallerShortcuts())
                     .forShortcutWithId("s1", si -> {
                         assertTrue(si.hasIconFile());
@@ -1437,7 +1437,7 @@
                                     getTestContext().getResources(), R.drawable.black_64x64)))
                             .build()
             )));
-            mService.waitForBitmapSaves();
+            mService.waitForBitmapSavesForTest();
             assertWith(getCallerShortcuts())
                     .forShortcutWithId("s1", si -> {
                         assertTrue(si.hasIconFile());
@@ -1451,7 +1451,7 @@
                             .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32))
                             .build()
             )));
-            mService.waitForBitmapSaves();
+            mService.waitForBitmapSavesForTest();
             assertWith(getCallerShortcuts())
                     .forShortcutWithId("s1", si -> {
                         assertTrue(si.hasIconResource());
@@ -1463,7 +1463,7 @@
                             .setIcon(Icon.createWithContentUri("test_uri"))
                             .build()
             )));
-            mService.waitForBitmapSaves();
+            mService.waitForBitmapSavesForTest();
             assertWith(getCallerShortcuts())
                     .forShortcutWithId("s1", si -> {
                         assertTrue(si.hasIconUri());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 348e015..c0cd7a7 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -198,6 +198,7 @@
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.logging.InstanceIdSequenceFake;
+import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.LocalServices;
@@ -303,6 +304,8 @@
     ActivityManagerInternal mAmi;
     @Mock
     private Looper mMainLooper;
+    @Mock
+    private NotificationManager mMockNm;
 
     @Mock
     IIntentSender pi1;
@@ -405,6 +408,7 @@
         LocalServices.removeServiceForTest(PermissionPolicyInternal.class);
         LocalServices.addService(PermissionPolicyInternal.class, mPermissionPolicyInternal);
         mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
+        mContext.addMockSystemService(NotificationManager.class, mMockNm);
 
         doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
 
@@ -7516,46 +7520,53 @@
     }
 
     @Test
-    public void testOnBubbleNotificationSuppressionChanged() throws Exception {
+    public void testOnBubbleMetadataFlagChanged() throws Exception {
         setUpPrefsForBubbles(PKG, mUid,
                 true /* global */,
                 BUBBLE_PREFERENCE_ALL /* app */,
                 true /* channel */);
 
-        // Bubble notification
+        // Post a bubble notification
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag");
-
+        // Set this so that the bubble can be suppressed
+        nr.getNotification().getBubbleMetadata().setFlags(
+                Notification.BubbleMetadata.FLAG_SUPPRESSABLE_BUBBLE);
         mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
                 nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
-        // NOT suppressed
+        // Check the flags
         Notification n =  mBinderService.getActiveNotifications(PKG)[0].getNotification();
         assertFalse(n.getBubbleMetadata().isNotificationSuppressed());
+        assertFalse(n.getBubbleMetadata().getAutoExpandBubble());
+        assertFalse(n.getBubbleMetadata().isBubbleSuppressed());
+        assertTrue(n.getBubbleMetadata().isBubbleSuppressable());
 
         // Reset as this is called when the notif is first sent
         reset(mListeners);
 
-        // Test: update suppression to true
-        mService.mNotificationDelegate.onBubbleNotificationSuppressionChanged(nr.getKey(), true,
-                false);
+        // Test: change the flags
+        int flags = Notification.BubbleMetadata.FLAG_SUPPRESSABLE_BUBBLE;
+        flags |= Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE;
+        flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
+        flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
+        mService.mNotificationDelegate.onBubbleMetadataFlagChanged(nr.getKey(), flags);
         waitForIdle();
 
         // Check
         n =  mBinderService.getActiveNotifications(PKG)[0].getNotification();
-        assertTrue(n.getBubbleMetadata().isNotificationSuppressed());
+        assertEquals(flags, n.getBubbleMetadata().getFlags());
 
         // Reset to check again
         reset(mListeners);
 
-        // Test: update suppression to false
-        mService.mNotificationDelegate.onBubbleNotificationSuppressionChanged(nr.getKey(), false,
-                false);
+        // Test: clear flags
+        mService.mNotificationDelegate.onBubbleMetadataFlagChanged(nr.getKey(), 0);
         waitForIdle();
 
         // Check
         n = mBinderService.getActiveNotifications(PKG)[0].getNotification();
-        assertFalse(n.getBubbleMetadata().isNotificationSuppressed());
+        assertEquals(0, n.getBubbleMetadata().getFlags());
     }
 
     @Test
@@ -9294,4 +9305,77 @@
         // the notifyPostedLocked function is called twice.
         verify(mListeners, times(2)).notifyPostedLocked(any(), any());
     }
+
+    @Test
+    public void testMaybeShowReviewPermissionsNotification_unknown() {
+        // Set up various possible states of the settings int and confirm whether or not the
+        // notification is shown as expected
+
+        // Initial state: default/unknown setting, make sure nothing happens
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN);
+        mService.maybeShowInitialReviewPermissionsNotification();
+        verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
+    }
+
+    @Test
+    public void testMaybeShowReviewPermissionsNotification_shouldShow() {
+        // If state is SHOULD_SHOW, it ... should show
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
+        mService.maybeShowInitialReviewPermissionsNotification();
+        verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+                eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
+                any(Notification.class));
+    }
+
+    @Test
+    public void testMaybeShowReviewPermissionsNotification_alreadyShown() {
+        // If state is either USER_INTERACTED or DISMISSED, we should not show this on boot
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
+        mService.maybeShowInitialReviewPermissionsNotification();
+
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED);
+        mService.maybeShowInitialReviewPermissionsNotification();
+
+        verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
+    }
+
+    @Test
+    public void testMaybeShowReviewPermissionsNotification_reshown() {
+        // If we have re-shown the notification and the user did not subsequently interacted with
+        // it, then make sure we show when trying on boot
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
+        mService.maybeShowInitialReviewPermissionsNotification();
+        verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+                eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
+                any(Notification.class));
+    }
+
+    @Test
+    public void testRescheduledReviewPermissionsNotification() {
+        // when rescheduled, the notification goes through the NotificationManagerInternal service
+        // this call doesn't need to know anything about previously scheduled state -- if called,
+        // it should send the notification & write the appropriate int to Settings
+        mInternalService.sendReviewPermissionsNotification();
+
+        // Notification should be sent
+        verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+                eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
+                any(Notification.class));
+
+        // write STATE_RESHOWN to settings
+        assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN,
+                Settings.Global.getInt(mContext.getContentResolver(),
+                        Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                        NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 63d7453..6d08959 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -289,6 +289,11 @@
                 .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
                 .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
                 .build();
+
+        // make sure that the settings for review notification permissions are unset to begin with
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN);
     }
 
     private ByteArrayOutputStream writeXmlAndPurge(
@@ -656,6 +661,13 @@
         verify(mPermissionHelper).setNotificationPermission(nMr1Expected);
         verify(mPermissionHelper).setNotificationPermission(oExpected);
         verify(mPermissionHelper).setNotificationPermission(pExpected);
+
+        // verify that we also write a state for review_permissions_notification to eventually
+        // show a notification
+        assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW,
+                Settings.Global.getInt(mContext.getContentResolver(),
+                        Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                        NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
     }
 
     @Test
@@ -738,7 +750,7 @@
     }
 
     @Test
-    public void testReadXml_newXml_noMigration() throws Exception {
+    public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
         when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
@@ -786,6 +798,70 @@
         compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false));
 
         verify(mPermissionHelper, never()).setNotificationPermission(any());
+
+        // verify that we do, however, write a state for review_permissions_notification to
+        // eventually show a notification, since this XML version is older than the notification
+        assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW,
+                Settings.Global.getInt(mContext.getContentResolver(),
+                        Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                        NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+    }
+
+    @Test
+    public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
+        when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
+        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
+
+        String xml = "<ranking version=\"4\">\n"
+                + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
+                + "<channel id=\"idn\" name=\"name\" importance=\"2\"/>\n"
+                + "<channel id=\"miscellaneous\" name=\"Uncategorized\" />\n"
+                + "</package>\n"
+                + "<package name=\"" + PKG_O + "\" >\n"
+                + "<channel id=\"ido\" name=\"name2\" importance=\"2\" show_badge=\"true\"/>\n"
+                + "</package>\n"
+                + "<package name=\"" + PKG_P + "\" >\n"
+                + "<channel id=\"idp\" name=\"name3\" importance=\"4\" locked=\"2\" />\n"
+                + "</package>\n"
+                + "</ranking>\n";
+        NotificationChannel idn = new NotificationChannel("idn", "name", IMPORTANCE_LOW);
+        idn.setSound(null, new AudioAttributes.Builder()
+                .setUsage(USAGE_NOTIFICATION)
+                .setContentType(CONTENT_TYPE_SONIFICATION)
+                .setFlags(0)
+                .build());
+        idn.setShowBadge(false);
+        NotificationChannel ido = new NotificationChannel("ido", "name2", IMPORTANCE_LOW);
+        ido.setShowBadge(true);
+        ido.setSound(null, new AudioAttributes.Builder()
+                .setUsage(USAGE_NOTIFICATION)
+                .setContentType(CONTENT_TYPE_SONIFICATION)
+                .setFlags(0)
+                .build());
+        NotificationChannel idp = new NotificationChannel("idp", "name3", IMPORTANCE_HIGH);
+        idp.lockFields(2);
+        idp.setSound(null, new AudioAttributes.Builder()
+                .setUsage(USAGE_NOTIFICATION)
+                .setContentType(CONTENT_TYPE_SONIFICATION)
+                .setFlags(0)
+                .build());
+
+        loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM);
+
+        assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
+
+        assertEquals(idn, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, idn.getId(), false));
+        compareChannels(ido, mHelper.getNotificationChannel(PKG_O, UID_O, ido.getId(), false));
+        compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false));
+
+        verify(mPermissionHelper, never()).setNotificationPermission(any());
+
+        // this XML is new enough, we should not be attempting to show a notification or anything
+        assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN,
+                Settings.Global.getInt(mContext.getContentResolver(),
+                        Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                        NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
     }
 
     @Test
@@ -903,7 +979,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(
                 PKG_N_MR1, UID_N_MR1, false, USER_SYSTEM);
-        String expected = "<ranking version=\"3\">\n"
+        String expected = "<ranking version=\"4\">\n"
                 + "<package name=\"com.example.o\" show_badge=\"true\" "
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
                 + "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" uid=\"1111\">\n"
@@ -984,7 +1060,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(
                 PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
-        String expected = "<ranking version=\"3\">\n"
+        String expected = "<ranking version=\"4\">\n"
                 // Importance 0 because off in permissionhelper
                 + "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1067,7 +1143,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(
                 PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
-        String expected = "<ranking version=\"3\">\n"
+        String expected = "<ranking version=\"4\">\n"
                 // Importance 0 because off in permissionhelper
                 + "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1121,7 +1197,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(
                 PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
-        String expected = "<ranking version=\"3\">\n"
+        String expected = "<ranking version=\"4\">\n"
                 // Packages that exist solely in permissionhelper
                 + "<package name=\"" + PKG_P + "\" importance=\"3\" />\n"
                 + "<package name=\"" + PKG_O + "\" importance=\"0\" />\n"
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
new file mode 100644
index 0000000..5a4ce5d
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.rule.ServiceTestRule;
+
+import com.android.server.LocalServices;
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+
+@RunWith(AndroidTestingRunner.class)
+public class ReviewNotificationPermissionsJobServiceTest extends UiServiceTestCase {
+    private ReviewNotificationPermissionsJobService mJobService;
+    private JobParameters mJobParams = new JobParameters(null,
+            ReviewNotificationPermissionsJobService.JOB_ID, null, null, null,
+            0, false, false, null, null, null);
+
+    @Captor
+    ArgumentCaptor<JobInfo> mJobInfoCaptor;
+
+    @Mock
+    private JobScheduler mMockJobScheduler;
+
+    @Mock
+    private NotificationManagerInternal mMockNotificationManagerInternal;
+
+    @Rule
+    public final ServiceTestRule mServiceRule = new ServiceTestRule();
+
+    @Before
+    public void setUp() throws Exception {
+        mJobService = new ReviewNotificationPermissionsJobService();
+        mContext.addMockSystemService(JobScheduler.class, mMockJobScheduler);
+
+        // add NotificationManagerInternal to LocalServices
+        LocalServices.removeServiceForTest(NotificationManagerInternal.class);
+        LocalServices.addService(NotificationManagerInternal.class,
+                mMockNotificationManagerInternal);
+    }
+
+    @Test
+    public void testScheduleJob() {
+        // if asked, the job doesn't currently exist yet
+        when(mMockJobScheduler.getPendingJob(anyInt())).thenReturn(null);
+
+        final int rescheduleTimeMillis = 350;  // arbitrary number
+
+        // attempt to schedule the job
+        ReviewNotificationPermissionsJobService.scheduleJob(mContext, rescheduleTimeMillis);
+        verify(mMockJobScheduler, times(1)).schedule(mJobInfoCaptor.capture());
+
+        // verify various properties of the job that is passed in to the job scheduler
+        JobInfo jobInfo = mJobInfoCaptor.getValue();
+        assertEquals(ReviewNotificationPermissionsJobService.JOB_ID, jobInfo.getId());
+        assertEquals(rescheduleTimeMillis, jobInfo.getMinLatencyMillis());
+        assertTrue(jobInfo.isPersisted());  // should continue after reboot
+        assertFalse(jobInfo.isPeriodic());  // one time
+    }
+
+    @Test
+    public void testOnStartJob() {
+        // the job need not be persisted after it does its work, so it'll return
+        // false
+        assertFalse(mJobService.onStartJob(mJobParams));
+
+        // verify that starting the job causes the notification to be sent
+        verify(mMockNotificationManagerInternal).sendReviewPermissionsNotification();
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsReceiverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsReceiverTest.java
new file mode 100644
index 0000000..12281a7
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsReceiverTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class ReviewNotificationPermissionsReceiverTest extends UiServiceTestCase {
+
+    // Simple mock class that just overrides the reschedule and cancel behavior so that it's easy
+    // to tell whether the receiver has sent requests to either reschedule or cancel the
+    // notification (or both).
+    private class MockReviewNotificationPermissionsReceiver
+            extends ReviewNotificationPermissionsReceiver {
+        boolean mCanceled = false;
+        boolean mRescheduled = false;
+
+        @Override
+        protected void cancelNotification(Context context) {
+            mCanceled = true;
+        }
+
+        @Override
+        protected void rescheduleNotification(Context context) {
+            mRescheduled = true;
+        }
+    }
+
+    private MockReviewNotificationPermissionsReceiver mReceiver;
+    private Intent mIntent;
+
+    @Before
+    public void setUp() {
+        mReceiver = new MockReviewNotificationPermissionsReceiver();
+        mIntent = new Intent();  // actions will be set in test cases
+    }
+
+    @Test
+    public void testReceive_remindMeLater_firstTime() {
+        // Test what happens when we receive a "remind me later" intent coming from
+        // a previously-not-interacted notification
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
+
+        // set up Intent action
+        mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND);
+
+        // Upon receipt of the intent, the following things should happen:
+        //   - notification rescheduled
+        //   - notification explicitly canceled
+        //   - settings state updated to indicate user has interacted
+        mReceiver.onReceive(mContext, mIntent);
+        assertTrue(mReceiver.mRescheduled);
+        assertTrue(mReceiver.mCanceled);
+        assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED,
+                Settings.Global.getInt(mContext.getContentResolver(),
+                        Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                        NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+    }
+
+    @Test
+    public void testReceive_remindMeLater_laterTimes() {
+        // Test what happens when we receive a "remind me later" intent coming from
+        // a previously-interacted notification that has been rescheduled
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
+
+        // set up Intent action
+        mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND);
+
+        // Upon receipt of the intent, the following things should still happen
+        // regardless of the fact that the user has interacted before:
+        //   - notification rescheduled
+        //   - notification explicitly canceled
+        //   - settings state still indicate user has interacted
+        mReceiver.onReceive(mContext, mIntent);
+        assertTrue(mReceiver.mRescheduled);
+        assertTrue(mReceiver.mCanceled);
+        assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED,
+                Settings.Global.getInt(mContext.getContentResolver(),
+                        Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                        NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+    }
+
+    @Test
+    public void testReceive_dismiss() {
+        // Test that dismissing the notification does *not* reschedule the notification,
+        // does cancel it, and writes that it has been dismissed to settings
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
+
+        // set up Intent action
+        mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS);
+
+        // send intent, watch what happens
+        mReceiver.onReceive(mContext, mIntent);
+        assertFalse(mReceiver.mRescheduled);
+        assertTrue(mReceiver.mCanceled);
+        assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED,
+                Settings.Global.getInt(mContext.getContentResolver(),
+                        Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                        NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+    }
+
+    @Test
+    public void testReceive_notificationCanceled_firstSwipe() {
+        // Test the basic swipe away case: the first time the user swipes the notification
+        // away, it will not have been interacted with yet, so make sure it's rescheduled
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
+
+        // set up Intent action, would be called from notification's delete intent
+        mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
+
+        // send intent, make sure it gets:
+        //   - rescheduled
+        //   - not explicitly canceled, the notification was already canceled
+        //   - noted that it's been interacted with
+        mReceiver.onReceive(mContext, mIntent);
+        assertTrue(mReceiver.mRescheduled);
+        assertFalse(mReceiver.mCanceled);
+        assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED,
+                Settings.Global.getInt(mContext.getContentResolver(),
+                        Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                        NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+    }
+
+    @Test
+    public void testReceive_notificationCanceled_secondSwipe() {
+        // Test the swipe away case for a rescheduled notification: in this case
+        // it should not be rescheduled anymore
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
+
+        // set up Intent action, would be called from notification's delete intent
+        mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
+
+        // send intent, make sure it gets:
+        //   - not rescheduled on the second+ swipe
+        //   - not explicitly canceled, the notification was already canceled
+        //   - mark as user interacted
+        mReceiver.onReceive(mContext, mIntent);
+        assertFalse(mReceiver.mRescheduled);
+        assertFalse(mReceiver.mCanceled);
+        assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED,
+                Settings.Global.getInt(mContext.getContentResolver(),
+                        Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                        NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+    }
+
+    @Test
+    public void testReceive_notificationCanceled_fromDismiss() {
+        // Test that if the notification delete intent is called due to us canceling
+        // the notification from the receiver, we don't do anything extra
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED);
+
+        // set up Intent action, would be called from notification's delete intent
+        mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
+
+        // nothing should happen, nothing at all
+        mReceiver.onReceive(mContext, mIntent);
+        assertFalse(mReceiver.mRescheduled);
+        assertFalse(mReceiver.mCanceled);
+        assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED,
+                Settings.Global.getInt(mContext.getContentResolver(),
+                        Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                        NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 8474a36..0a13a36 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -25,6 +26,8 @@
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
@@ -155,6 +158,32 @@
     }
 
     @Test
+    public void testDreamActivityOpenTransition() {
+        final ActivityRecord dreamActivity = createActivityRecord(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM);
+        mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
+        mDisplayContent.mOpeningApps.add(dreamActivity);
+
+        assertEquals(TRANSIT_OLD_DREAM_ACTIVITY_OPEN,
+                AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
+                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
+                        mDisplayContent.mChangingContainers, null, null, false));
+    }
+
+    @Test
+    public void testDreamActivityCloseTransition() {
+        final ActivityRecord dreamActivity = createActivityRecord(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM);
+        mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
+        mDisplayContent.mClosingApps.add(dreamActivity);
+
+        assertEquals(TRANSIT_OLD_DREAM_ACTIVITY_CLOSE,
+                AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
+                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
+                        mDisplayContent.mChangingContainers, null, null, false));
+    }
+
+    @Test
     public void testChangeIsNotOverwritten() {
         final ActivityRecord behind = createActivityRecord(mDisplayContent,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index ea98b6b..92457c7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -26,7 +26,6 @@
 import android.os.PowerManager.GoToSleepReason;
 import android.os.PowerManager.WakeReason;
 import android.util.proto.ProtoOutputStream;
-import android.view.IWindowManager;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 import android.view.animation.Animation;
@@ -50,8 +49,7 @@
     }
 
     @Override
-    public void init(Context context, IWindowManager windowManager,
-            WindowManagerFuncs windowManagerFuncs) {
+    public void init(Context context, WindowManagerFuncs windowManagerFuncs) {
     }
 
     public void setDefaultDisplay(DisplayContentInfo displayContentInfo) {
diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING
index bc8c639..22a6445 100644
--- a/services/voiceinteraction/TEST_MAPPING
+++ b/services/voiceinteraction/TEST_MAPPING
@@ -5,19 +5,6 @@
       "options": [
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        // TODO(b/225076204): Remove the following four test cases after fixing the test fail.
-        {
-          "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
-        },
-        {
-          "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromExternalSource_success"
-        },
-        {
-          "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
-        },
-        {
-          "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromMic_success"
         }
       ]
     },
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index 1fe13fe..06cbeb5 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -124,16 +124,6 @@
     @SmallTest
     public void testSET_ORIENTATION() {
         try {
-            mWm.updateRotation(true, false);
-            fail("IWindowManager.updateRotation did not throw SecurityException as"
-                    + " expected");
-        } catch (SecurityException e) {
-            // expected
-        } catch (RemoteException e) {
-            fail("Unexpected remote exception");
-        }
-
-        try {
             mWm.freezeRotation(-1);
             fail("IWindowManager.freezeRotation did not throw SecurityException as"
                     + " expected");