Merge "restrict items in PowerMenu to a max height"
diff --git a/apct-tests/perftests/multiuser/Android.bp b/apct-tests/perftests/multiuser/Android.bp
index c967e51..45c6b8c 100644
--- a/apct-tests/perftests/multiuser/Android.bp
+++ b/apct-tests/perftests/multiuser/Android.bp
@@ -31,6 +31,9 @@
     ],
     platform_apis: true,
     test_suites: ["device-tests"],
-    data: ["trace_configs/*"],
+    data: [
+        ":MultiUserPerfDummyApp",
+        "trace_configs/*",
+    ],
     certificate: "platform",
 }
diff --git a/apct-tests/perftests/packagemanager/Android.bp b/apct-tests/perftests/packagemanager/Android.bp
index 81cec91..e84aea1 100644
--- a/apct-tests/perftests/packagemanager/Android.bp
+++ b/apct-tests/perftests/packagemanager/Android.bp
@@ -33,7 +33,10 @@
 
     test_suites: ["device-tests"],
 
-    data: [":perfetto_artifacts"],
+    data: [
+        ":QueriesAll0",
+        ":perfetto_artifacts",
+    ],
 
     certificate: "platform",
 
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index 3fc87d3..ce381b6 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -22,7 +22,7 @@
 import android.app.AppOpsManager;
 import android.app.AppOpsManager.PackageOps;
 import android.app.IActivityManager;
-import android.app.IUidObserver;
+import android.app.UidObserver;
 import android.app.usage.UsageStatsManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -716,11 +716,7 @@
         return true;
     }
 
-    private final class UidObserver extends IUidObserver.Stub {
-        @Override
-        public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
-        }
-
+    private final class UidObserver extends android.app.UidObserver {
         @Override
         public void onUidActive(int uid) {
             mHandler.onUidActive(uid);
@@ -740,10 +736,6 @@
         public void onUidCachedChanged(int uid, boolean cached) {
             mHandler.onUidCachedChanged(uid, cached);
         }
-
-        @Override
-        public void onUidProcAdjChanged(int uid) {
-        }
     }
 
     private final class AppOpsWatcher extends IAppOpsCallback.Stub {
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 70b06cb..887ee5f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1551,16 +1551,21 @@
                     jobStatus.getNumPreviousAttempts(),
                     jobStatus.getJob().getMaxExecutionDelayMillis(),
                     /* isDeadlineConstraintSatisfied */ false,
-                    /* isCharging */ false,
-                    /* batteryNotLow */ false,
-                    /* storageNotLow */false,
+                    /* isChargingSatisfied */ false,
+                    /* batteryNotLowSatisfied */ false,
+                    /* storageNotLowSatisfied */false,
                     /* timingDelayConstraintSatisfied */ false,
-                    /* isDeviceIdle */ false,
+                    /* isDeviceIdleSatisfied */ false,
                     /* hasConnectivityConstraintSatisfied */ false,
                     /* hasContentTriggerConstraintSatisfied */ false,
-                    0,
+                    /* jobStartLatencyMs */ 0,
                     jobStatus.getJob().isUserInitiated(),
-                    /* isRunningAsUserInitiatedJob */ false);
+                    /* isRunningAsUserInitiatedJob */ false,
+                    jobStatus.getJob().isPeriodic(),
+                    jobStatus.getJob().getMinLatencyMillis(),
+                    jobStatus.getEstimatedNetworkDownloadBytes(),
+                    jobStatus.getEstimatedNetworkUploadBytes(),
+                    jobStatus.getWorkCount());
 
             // If the job is immediately ready to run, then we can just immediately
             // put it in the pending list and try to schedule it.  This is especially
@@ -1981,9 +1986,14 @@
                     cancelled.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
                     cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
                     cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
-                    0,
+                    /* jobStartLatencyMs */ 0,
                     cancelled.getJob().isUserInitiated(),
-                    /* isRunningAsUserInitiatedJob */ false);
+                    /* isRunningAsUserInitiatedJob */ false,
+                    cancelled.getJob().isPeriodic(),
+                    cancelled.getJob().getMinLatencyMillis(),
+                    cancelled.getEstimatedNetworkDownloadBytes(),
+                    cancelled.getEstimatedNetworkUploadBytes(),
+                    cancelled.getWorkCount());
         }
         // If this is a replacement, bring in the new version of the job
         if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 8355e9c..44700c8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -471,7 +471,12 @@
                     job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
                     mExecutionStartTimeElapsed - job.enqueueTime,
                     job.getJob().isUserInitiated(),
-                    job.shouldTreatAsUserInitiatedJob());
+                    job.shouldTreatAsUserInitiatedJob(),
+                    job.getJob().isPeriodic(),
+                    job.getJob().getMinLatencyMillis(),
+                    job.getEstimatedNetworkDownloadBytes(),
+                    job.getEstimatedNetworkUploadBytes(),
+                    job.getWorkCount());
             final String sourcePackage = job.getSourcePackageName();
             if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
                 final String componentPackage = job.getServiceComponent().getPackageName();
@@ -1435,9 +1440,14 @@
                 completedJob.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
                 completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
                 completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
-                0,
+                mExecutionStartTimeElapsed - completedJob.enqueueTime,
                 completedJob.getJob().isUserInitiated(),
-                completedJob.startedAsUserInitiatedJob);
+                completedJob.startedAsUserInitiatedJob,
+                completedJob.getJob().isPeriodic(),
+                completedJob.getJob().getMinLatencyMillis(),
+                completedJob.getEstimatedNetworkDownloadBytes(),
+                completedJob.getEstimatedNetworkUploadBytes(),
+                completedJob.getWorkCount());
         if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
             Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
                     getId());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 5d2c926..b9e3b76 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -682,7 +682,7 @@
         static final String KEY_RESCHEDULED_JOB_DEADLINE_MS =
                 FC_CONFIG_PREFIX + "rescheduled_job_deadline_ms";
 
-        private static final boolean DEFAULT_FLEXIBILITY_ENABLED = true;
+        private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false;
         @VisibleForTesting
         static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
         @VisibleForTesting
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 7cc2f28..6445c3b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -590,9 +590,10 @@
             this.sourceTag = tag;
         }
 
+        final String bnNamespace = namespace == null ? "" :  "@" + namespace + "@";
         this.batteryName = this.sourceTag != null
-                ? this.sourceTag + ":" + job.getService().getPackageName()
-                : job.getService().flattenToShortString();
+                ? bnNamespace + this.sourceTag + ":" + job.getService().getPackageName()
+                : bnNamespace + job.getService().flattenToShortString();
         this.tag = "*job*/" + this.batteryName + "#" + job.getId();
 
         this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index aca0a6e..175c8d1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -35,7 +35,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
-import android.app.IUidObserver;
+import android.app.UidObserver;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
@@ -382,31 +382,11 @@
                 }
             };
 
-    private class QcUidObserver extends IUidObserver.Stub {
+    private class QcUidObserver extends UidObserver {
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
             mHandler.obtainMessage(MSG_UID_PROCESS_STATE_CHANGED, uid, procState).sendToTarget();
         }
-
-        @Override
-        public void onUidGone(int uid, boolean disabled) {
-        }
-
-        @Override
-        public void onUidActive(int uid) {
-        }
-
-        @Override
-        public void onUidIdle(int uid, boolean disabled) {
-        }
-
-        @Override
-        public void onUidCachedChanged(int uid, boolean cached) {
-        }
-
-        @Override
-        public void onUidProcAdjChanged(int uid) {
-        }
     }
 
     /**
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/ProcessStateModifier.java b/apex/jobscheduler/service/java/com/android/server/tare/ProcessStateModifier.java
index 3578c8a..58536675 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/ProcessStateModifier.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/ProcessStateModifier.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.IUidObserver;
+import android.app.UidObserver;
 import android.os.RemoteException;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
@@ -61,7 +62,7 @@
     @GuardedBy("mLock")
     private final SparseIntArray mUidProcStateBucketCache = new SparseIntArray();
 
-    private final IUidObserver mUidObserver = new IUidObserver.Stub() {
+    private final IUidObserver mUidObserver = new UidObserver() {
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
             final int newBucket = getProcStateBucket(procState);
@@ -85,22 +86,6 @@
                 notifyStateChangedLocked(uid);
             }
         }
-
-        @Override
-        public void onUidActive(int uid) {
-        }
-
-        @Override
-        public void onUidIdle(int uid, boolean disabled) {
-        }
-
-        @Override
-        public void onUidCachedChanged(int uid, boolean cached) {
-        }
-
-        @Override
-        public void onUidProcAdjChanged(int uid) {
-        }
     };
 
     ProcessStateModifier(@NonNull InternalResourceService irs) {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index b631586..b1f779f 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -427,7 +427,7 @@
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void collapsePanels();
     method public void expandNotificationsPanel();
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public int getLastSystemKey();
-    method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void handleSystemKey(int);
+    method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void handleSystemKey(@NonNull android.view.KeyEvent);
     method public void sendNotificationFeedback(@Nullable String, @Nullable android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void togglePanel();
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 29f774c..a6313db 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -46,6 +46,7 @@
 import android.os.UserHandle;
 import android.util.Pair;
 import android.util.Slog;
+import android.view.KeyEvent;
 import android.view.View;
 
 import com.android.internal.statusbar.AppClipsServiceConnector;
@@ -740,7 +741,7 @@
      */
     @RequiresPermission(android.Manifest.permission.STATUS_BAR)
     @TestApi
-    public void handleSystemKey(int key) {
+    public void handleSystemKey(@NonNull KeyEvent key) {
         try {
             final IStatusBarService svc = getService();
             if (svc != null) {
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 12882a2..9efdf28 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -39,21 +39,22 @@
 import android.os.ResultReceiver;
 
 /**
- * Interface for a virtual device.
+ * Interface for a virtual device for communication between the system server and the process of
+ * the owner of the virtual device.
  *
  * @hide
  */
 interface IVirtualDevice {
 
     /**
-     * Returns the association ID for this virtual device.
+     * Returns the CDM association ID of this virtual device.
      *
      * @see AssociationInfo#getId()
      */
     int getAssociationId();
 
     /**
-     * Returns the unique device ID for this virtual device.
+     * Returns the unique ID of this virtual device.
      */
     int getDeviceId();
 
@@ -64,55 +65,99 @@
     void close();
 
     /**
-     * Notifies of an audio session being started.
+     * Notifies that an audio session being started.
      */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
-    void onAudioSessionStarting(
-            int displayId,
-            IAudioRoutingCallback routingCallback,
+    void onAudioSessionStarting(int displayId, IAudioRoutingCallback routingCallback,
             IAudioConfigChangedCallback configChangedCallback);
 
+    /**
+     * Notifies that an audio session has ended.
+     */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void onAudioSessionEnded();
 
+    /**
+     * Creates a new dpad and registers it with the input framework with the given token.
+     */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
-    void createVirtualDpad(
-            in VirtualDpadConfig config,
-            IBinder token);
+    void createVirtualDpad(in VirtualDpadConfig config, IBinder token);
+
+    /**
+     * Creates a new keyboard and registers it with the input framework with the given token.
+     */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
-    void createVirtualKeyboard(
-            in VirtualKeyboardConfig config,
-            IBinder token);
+    void createVirtualKeyboard(in VirtualKeyboardConfig config, IBinder token);
+
+    /**
+     * Creates a new mouse and registers it with the input framework with the given token.
+     */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
-    void createVirtualMouse(
-            in VirtualMouseConfig config,
-            IBinder token);
+    void createVirtualMouse(in VirtualMouseConfig config, IBinder token);
+
+    /**
+     * Creates a new touchscreen and registers it with the input framework with the given token.
+     */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
-    void createVirtualTouchscreen(
-            in VirtualTouchscreenConfig config,
-            IBinder token);
+    void createVirtualTouchscreen(in VirtualTouchscreenConfig config, IBinder token);
+
+    /**
+     * Creates a new navigation touchpad and registers it with the input framework with the given
+     * token.
+     */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
-    void createVirtualNavigationTouchpad(
-            in VirtualNavigationTouchpadConfig config,
-            IBinder token);
+    void createVirtualNavigationTouchpad(in VirtualNavigationTouchpadConfig config, IBinder token);
+
+    /**
+     * Removes the input device corresponding to the given token from the framework.
+     */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void unregisterInputDevice(IBinder token);
+
+    /**
+     * Returns the ID of the device corresponding to the given token, as registered with the input
+     * framework.
+     */
     int getInputDeviceId(IBinder token);
+
+    /**
+    * Injects a key event to the virtual dpad corresponding to the given token.
+    */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event);
+
+    /**
+    * Injects a key event to the virtual keyboard corresponding to the given token.
+    */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendKeyEvent(IBinder token, in VirtualKeyEvent event);
+
+    /**
+    * Injects a button event to the virtual mouse corresponding to the given token.
+    */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendButtonEvent(IBinder token, in VirtualMouseButtonEvent event);
+
+    /**
+    * Injects a relative event to the virtual mouse corresponding to the given token.
+    */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event);
+
+    /**
+    * Injects a scroll event to the virtual mouse corresponding to the given token.
+    */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event);
+
+    /**
+    * Injects a touch event to the virtual touch input device corresponding to the given token.
+    */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
 
     /**
-     * Returns all virtual sensors for this device.
+     * Returns all virtual sensors created for this device.
      */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     List<VirtualSensor> getVirtualSensorList();
@@ -126,8 +171,13 @@
     /**
      * Launches a pending intent on the given display that is owned by this virtual device.
      */
-    void launchPendingIntent(
-            int displayId, in PendingIntent pendingIntent, in ResultReceiver resultReceiver);
+    void launchPendingIntent(int displayId, in PendingIntent pendingIntent,
+            in ResultReceiver resultReceiver);
+
+    /**
+     * Returns the current cursor position of the mouse corresponding to the given token, in x and y
+     * coordinates.
+     */
     PointF getCursorPosition(IBinder token);
 
     /** Sets whether to show or hide the cursor while this virtual device is active. */
@@ -140,8 +190,12 @@
      * intent.
      */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
-    void registerIntentInterceptor(
-            in IVirtualDeviceIntentInterceptor intentInterceptor, in IntentFilter filter);
+    void registerIntentInterceptor(in IVirtualDeviceIntentInterceptor intentInterceptor,
+            in IntentFilter filter);
+
+    /**
+     * Unregisters a previously registered intent interceptor.
+     */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void unregisterIntentInterceptor(in IVirtualDeviceIntentInterceptor intentInterceptor);
 }
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 4f49b8d..07743cef5 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -101,7 +101,7 @@
      *
      * @param deviceId id of the virtual device.
      * @param sound effect type corresponding to
-     *     {@code android.media.AudioManager.SystemSoundEffect}
+     *   {@code android.media.AudioManager.SystemSoundEffect}
      */
     void playSoundEffect(int deviceId, int effectType);
 }
diff --git a/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl
index 91c209f..f284554 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl
@@ -28,7 +28,7 @@
      * Called when there's sound effect to be played on Virtual Device.
      *
      * @param sound effect type corresponding to
-     *     {@code android.media.AudioManager.SystemSoundEffect}
+     *   {@code android.media.AudioManager.SystemSoundEffect}
      */
     void onPlaySoundEffect(int effectType);
 }
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index 4a09186..4ee65e0 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -26,6 +26,11 @@
 
 /**
  * Details of a particular virtual device.
+ *
+ * <p>Read-only device representation exposing the properties of an existing virtual device.
+ *
+ * <p class="note">Not to be confused with {@link VirtualDeviceManager.VirtualDevice}, which is used
+ * by the virtual device creator and allows them to manage the device.
  */
 public final class VirtualDevice implements Parcelable {
 
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 6b851a1..8b6377a 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -68,7 +68,13 @@
 import java.util.function.IntConsumer;
 
 /**
- * System level service for managing virtual devices.
+ * System level service for creation and management of virtual devices.
+ *
+ * <p>VirtualDeviceManager enables interactive sharing of capabilities between the host Android
+ * device and a remote device.
+ *
+ * <p class="note">Not to be confused with the Android Studio's Virtual Device Manager, which allows
+ * for device emulation.
  */
 @SystemService(Context.VIRTUAL_DEVICE_SERVICE)
 public final class VirtualDeviceManager {
@@ -174,6 +180,9 @@
 
     /**
      * Returns the details of all available virtual devices.
+     *
+     * <p>The returned objects are read-only representations that expose the properties of all
+     * existing virtual devices.
      */
     @NonNull
     public List<android.companion.virtual.VirtualDevice> getVirtualDevices() {
@@ -252,11 +261,12 @@
      *
      * @param deviceId - id of the virtual audio device
      * @return Device specific session id to be used for audio playback (see
-     *     {@link android.media.AudioManager.generateAudioSessionId}) if virtual device has
-     *     {@link VirtualDeviceParams.POLICY_TYPE_AUDIO} set to
-     *     {@link VirtualDeviceParams.DEVICE_POLICY_CUSTOM} and Virtual Audio Device
-     *     is configured in context-aware mode.
-     *     Otherwise {@link AUDIO_SESSION_ID_GENERATE} constant is returned.
+     *   {@link AudioManager#generateAudioSessionId}) if virtual device has
+     *   {@link VirtualDeviceParams#POLICY_TYPE_AUDIO} set to
+     *   {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM} and Virtual Audio Device
+     *   is configured in context-aware mode. Otherwise
+     *   {@link AudioManager#AUDIO_SESSION_ID_GENERATE} constant is returned.
+     *
      * @hide
      */
     public int getAudioPlaybackSessionId(int deviceId) {
@@ -275,11 +285,12 @@
      *
      * @param deviceId - id of the virtual audio device
      * @return Device specific session id to be used for audio recording (see
-     *     {@link android.media.AudioManager.generateAudioSessionId}) if virtual device has
-     *     {@link VirtualDeviceParams.POLICY_TYPE_AUDIO} set to
-     *     {@link VirtualDeviceParams.DEVICE_POLICY_CUSTOM} and Virtual Audio Device
-     *     is configured in context-aware mode.
-     *     Otherwise {@link AUDIO_SESSION_ID_GENERATE} constant is returned.
+     *   {@link AudioManager#generateAudioSessionId}) if virtual device has
+     *   {@link VirtualDeviceParams#POLICY_TYPE_AUDIO} set to
+     *   {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM} and Virtual Audio Device
+     *   is configured in context-aware mode. Otherwise
+     *   {@link AudioManager#AUDIO_SESSION_ID_GENERATE} constant is returned.
+     *
      * @hide
      */
     public int getAudioRecordingSessionId(int deviceId) {
@@ -296,10 +307,11 @@
     /**
      * Requests sound effect to be played on virtual device.
      *
-     * @see android.media.AudioManager#playSoundEffect(int)
+     * @see AudioManager#playSoundEffect(int)
      *
      * @param deviceId - id of the virtual audio device
      * @param effectType the type of sound effect
+     *
      * @hide
      */
     public void playSoundEffect(int deviceId, @AudioManager.SystemSoundEffect int effectType) {
@@ -315,11 +327,18 @@
     }
 
     /**
-     * A virtual device has its own virtual display, audio output, microphone, sensors, etc. The
-     * creator of a virtual device can take the output from the virtual display and stream it over
-     * to another device, and inject input events that are received from the remote device.
+     * A representation of a virtual device.
      *
-     * TODO(b/204081582): Consider using a builder pattern for the input APIs.
+     * <p>A virtual device can have its own virtual displays, audio input/output, sensors, etc.
+     * The creator of a virtual device can take the output from the virtual display and stream it
+     * over to another device, and inject input and sensor events that are received from the remote
+     * device.
+     *
+     * <p>This object is only used by the virtual device creator and allows them to manage the
+     * device's behavior, peripherals, and the user interaction with that device.
+     *
+     * <p class="note">Not to be confused with {@link android.companion.virtual.VirtualDevice},
+     * which is a read-only representation exposing the properties of an existing virtual device.
      *
      * @hide
      */
@@ -346,8 +365,10 @@
         }
 
         /**
-         * @return A new Context bound to this device. This is a convenience method equivalent to
-         * calling {@link Context#createDeviceContext(int)} with the device id of this device.
+         * Returns a new context bound to this device.
+         *
+         * <p>This is a convenience method equivalent to calling
+         * {@link Context#createDeviceContext(int)} with the id of this device.
          */
         public @NonNull Context createContext() {
             return mVirtualDeviceInternal.createContext();
@@ -400,20 +421,19 @@
          * @param height The height of the virtual display in pixels, must be greater than 0.
          * @param densityDpi The density of the virtual display in dpi, must be greater than 0.
          * @param surface The surface to which the content of the virtual display should
-         * be rendered, or null if there is none initially. The surface can also be set later using
-         * {@link VirtualDisplay#setSurface(Surface)}.
+         *   be rendered, or null if there is none initially. The surface can also be set later
+         *   using {@link VirtualDisplay#setSurface(Surface)}.
          * @param flags A combination of virtual display flags accepted by
-         * {@link DisplayManager#createVirtualDisplay}. In addition, the following flags are
-         * automatically set for all virtual devices:
-         * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC VIRTUAL_DISPLAY_FLAG_PUBLIC} and
-         * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
-         * VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
+         *   {@link DisplayManager#createVirtualDisplay}. In addition, the following flags are
+         *   automatically set for all virtual devices:
+         *   {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC} and
+         *   {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
          * @param executor The executor on which {@code callback} will be invoked. This is ignored
-         * if {@code callback} is {@code null}. If {@code callback} is specified, this executor must
-         * not be null.
+         *   if {@code callback} is {@code null}. If {@code callback} is specified, this executor
+         *   must not be null.
          * @param callback Callback to call when the state of the {@link VirtualDisplay} changes
          * @return The newly created virtual display, or {@code null} if the application could
-         * not create the virtual display.
+         *   not create the virtual display.
          *
          * @see DisplayManager#createVirtualDisplay
          *
@@ -450,11 +470,11 @@
          *
          * @param config The configuration of the display.
          * @param executor The executor on which {@code callback} will be invoked. This is ignored
-         * if {@code callback} is {@code null}. If {@code callback} is specified, this executor must
-         * not be null.
+         *   if {@code callback} is {@code null}. If {@code callback} is specified, this executor
+         *   must not be null.
          * @param callback Callback to call when the state of the {@link VirtualDisplay} changes
          * @return The newly created virtual display, or {@code null} if the application could
-         * not create the virtual display.
+         *   not create the virtual display.
          *
          * @see DisplayManager#createVirtualDisplay
          */
@@ -478,7 +498,7 @@
         /**
          * Creates a virtual dpad.
          *
-         * @param config the configurations of the virtual Dpad.
+         * @param config the configurations of the virtual dpad.
          */
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         @NonNull
@@ -500,11 +520,10 @@
         /**
          * Creates a virtual keyboard.
          *
-         * @param display         the display that the events inputted through this device should
-         *                        target
-         * @param inputDeviceName the name to call this input device
-         * @param vendorId        the PCI vendor id
-         * @param productId       the product id, as defined by the vendor
+         * @param display the display that the events inputted through this device should target.
+         * @param inputDeviceName the name of this keyboard device.
+         * @param vendorId the PCI vendor id.
+         * @param productId the product id, as defined by the vendor.
          * @see #createVirtualKeyboard(VirtualKeyboardConfig config)
          * @deprecated Use {@link #createVirtualKeyboard(VirtualKeyboardConfig config)} instead
          */
@@ -537,14 +556,12 @@
         /**
          * Creates a virtual mouse.
          *
-         * @param display         the display that the events inputted through this device should
-         *                        target
-         * @param inputDeviceName the name to call this input device
-         * @param vendorId        the PCI vendor id
-         * @param productId       the product id, as defined by the vendor
+         * @param display the display that the events inputted through this device should target.
+         * @param inputDeviceName the name of this mouse.
+         * @param vendorId the PCI vendor id.
+         * @param productId the product id, as defined by the vendor.
          * @see #createVirtualMouse(VirtualMouseConfig config)
          * @deprecated Use {@link #createVirtualMouse(VirtualMouseConfig config)} instead
-         * *
          */
         @Deprecated
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -576,11 +593,10 @@
         /**
          * Creates a virtual touchscreen.
          *
-         * @param display         the display that the events inputted through this device should
-         *                        target
-         * @param inputDeviceName the name to call this input device
-         * @param vendorId        the PCI vendor id
-         * @param productId       the product id, as defined by the vendor
+         * @param display the display that the events inputted through this device should target.
+         * @param inputDeviceName the name of this touchscreen device.
+         * @param vendorId the PCI vendor id.
+         * @param productId the product id, as defined by the vendor.
          * @see #createVirtualTouchscreen(VirtualTouchscreenConfig config)
          * @deprecated Use {@link #createVirtualTouchscreen(VirtualTouchscreenConfig config)}
          * instead
@@ -605,11 +621,13 @@
         /**
          * Creates a virtual touchpad in navigation mode.
          *
-         * A touchpad in navigation mode means that its events are interpreted as navigation events
-         * (up, down, etc) instead of using them to update a cursor's absolute position. If the
-         * events are not consumed they are converted to DPAD events.
+         * <p>A touchpad in navigation mode means that its events are interpreted as navigation
+         * events (up, down, etc) instead of using them to update a cursor's absolute position. If
+         * the events are not consumed they are converted to DPAD events and delivered to the target
+         * again.
          *
          * @param config the configurations of the virtual navigation touchpad.
+         * @see android.view.InputDevice#SOURCE_TOUCH_NAVIGATION
          */
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         @NonNull
@@ -629,10 +647,10 @@
          *
          * @param display The target virtual display to capture from and inject into.
          * @param executor The {@link Executor} object for the thread on which to execute
-         *                the callback. If <code>null</code>, the {@link Executor} associated with
-         *                the main {@link Looper} will be used.
+         *   the callback. If <code>null</code>, the {@link Executor} associated with the main
+         *   {@link Looper} will be used.
          * @param callback Interface to be notified when playback or recording configuration of
-         *                applications running on virtual display is changed.
+         *   applications running on virtual display is changed.
          * @return A {@link VirtualAudioDevice} instance.
          */
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -648,7 +666,7 @@
          * Sets the visibility of the pointer icon for this VirtualDevice's associated displays.
          *
          * @param showPointerIcon True if the pointer should be shown; false otherwise. The default
-         *                        visibility is true.
+         *   visibility is true.
          */
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         public void setShowPointerIcon(boolean showPointerIcon) {
@@ -669,8 +687,7 @@
         }
 
         /**
-         * Removes an activity listener previously added with
-         * {@link #addActivityListener}.
+         * Removes an activity listener previously added with {@link #addActivityListener}.
          *
          * @param listener The listener to remove.
          * @see #addActivityListener(Executor, ActivityListener)
@@ -692,10 +709,10 @@
         }
 
         /**
-         * Removes a sound effect listener previously added with {@link #addActivityListener}.
+         * Removes a sound effect listener previously added with {@link #addSoundEffectListener}.
          *
          * @param soundEffectListener The listener to remove.
-         * @see #addActivityListener(Executor, ActivityListener)
+         * @see #addSoundEffectListener(Executor, SoundEffectListener)
          */
         public void removeSoundEffectListener(@NonNull SoundEffectListener soundEffectListener) {
             mVirtualDeviceInternal.removeSoundEffectListener(soundEffectListener);
@@ -722,7 +739,7 @@
         }
 
         /**
-         * Unregisters the intent interceptorCallback previously registered with
+         * Unregisters the intent interceptor previously registered with
          * {@link #registerIntentInterceptor}.
          */
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -760,9 +777,9 @@
          * {@link #onDisplayEmpty(int)} will be called. If the value topActivity is cached, it
          * should be cleared when {@link #onDisplayEmpty(int)} is called.
          *
-         * @param displayId   The display ID on which the activity change happened.
+         * @param displayId The display ID on which the activity change happened.
          * @param topActivity The component name of the top activity.
-         * @param userId      The user ID associated with the top activity.
+         * @param userId The user ID associated with the top activity.
          */
         default void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity,
                 @UserIdInt int userId) {}
@@ -799,6 +816,7 @@
 
     /**
      * Listener for system sound effect playback on virtual device.
+     *
      * @hide
      */
     @SystemApi
@@ -807,8 +825,8 @@
         /**
          * Called when there's a system sound effect to be played on virtual device.
          *
-         * @param effectType - system sound effect type, see
-         *     {@code android.media.AudioManager.SystemSoundEffect}
+         * @param effectType - system sound effect type
+         * @see android.media.AudioManager.SystemSoundEffect
          */
         void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType);
     }
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 9a34dbe..45d6dc6 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -34,6 +34,7 @@
 import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.companion.virtual.sensor.VirtualSensorDirectChannelCallback;
 import android.content.ComponentName;
+import android.content.Context;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SharedMemory;
@@ -680,7 +681,7 @@
          * {@link #NAVIGATION_POLICY_DEFAULT_ALLOWED}, meaning activities are allowed to launch
          * unless they are in {@code blockedCrossTaskNavigations}.
          *
-         * <p> This method must not be called if {@link #setAllowedCrossTaskNavigations(Set)} has
+         * <p>This method must not be called if {@link #setAllowedCrossTaskNavigations(Set)} has
          * been called.
          *
          * @throws IllegalArgumentException if {@link #setAllowedCrossTaskNavigations(Set)} has
@@ -847,11 +848,11 @@
          * <p>Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_AUDIO},
          * otherwise {@link #build()} method will throw {@link IllegalArgumentException} if
          * the playback session id is set to value other than
-         * {@link android.media.AudioManager.AUDIO_SESSION_ID_GENERATE}.
+         * {@link android.media.AudioManager#AUDIO_SESSION_ID_GENERATE}.
          *
          * @param playbackSessionId requested device-specific audio session id for playback
-         * @see android.media.AudioManager.generateAudioSessionId()
-         * @see android.media.AudioTrack.Builder.setContext(Context)
+         * @see android.media.AudioManager#generateAudioSessionId()
+         * @see android.media.AudioTrack.Builder#setContext(Context)
          */
         @NonNull
         public Builder setAudioPlaybackSessionId(int playbackSessionId) {
@@ -871,11 +872,11 @@
          * <p>Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_AUDIO},
          * otherwise {@link #build()} method will throw {@link IllegalArgumentException} if
          * the recording session id is set to value other than
-         * {@link android.media.AudioManager.AUDIO_SESSION_ID_GENERATE}.
+         * {@link android.media.AudioManager#AUDIO_SESSION_ID_GENERATE}.
          *
          * @param recordingSessionId requested device-specific audio session id for playback
-         * @see android.media.AudioManager.generateAudioSessionId()
-         * @see android.media.AudioRecord.Builder.setContext(Context)
+         * @see android.media.AudioManager#generateAudioSessionId()
+         * @see android.media.AudioRecord.Builder#setContext(Context)
          */
         @NonNull
         public Builder setAudioRecordingSessionId(int recordingSessionId) {
diff --git a/core/java/android/companion/virtual/audio/AudioCapture.java b/core/java/android/companion/virtual/audio/AudioCapture.java
index d6d0d2b..dd5e660 100644
--- a/core/java/android/companion/virtual/audio/AudioCapture.java
+++ b/core/java/android/companion/virtual/audio/AudioCapture.java
@@ -56,12 +56,12 @@
 
     /**
      * Sets the {@link AudioRecord} to handle audio capturing.
-     * Callers may call this multiple times with different audio records to change
-     * the underlying {@link AudioRecord} without stopping and re-starting recording.
      *
-     * @param audioRecord The underlying {@link AudioRecord} to use for capture,
-     * or null if no audio (i.e. silence) should be captured while still keeping the
-     * record in a recording state.
+     * <p>Callers may call this multiple times with different audio records to change the underlying
+     * {@link AudioRecord} without stopping and re-starting recording.
+     *
+     * @param audioRecord The underlying {@link AudioRecord} to use for capture, or null if no audio
+     *   (i.e. silence) should be captured while still keeping the record in a recording state.
      */
     void setAudioRecord(@Nullable AudioRecord audioRecord) {
         Log.d(TAG, "set AudioRecord with " + audioRecord);
diff --git a/core/java/android/companion/virtual/audio/AudioInjection.java b/core/java/android/companion/virtual/audio/AudioInjection.java
index 9d6a3eb..5de5f7e 100644
--- a/core/java/android/companion/virtual/audio/AudioInjection.java
+++ b/core/java/android/companion/virtual/audio/AudioInjection.java
@@ -65,12 +65,12 @@
 
     /**
      * Sets the {@link AudioTrack} to handle audio injection.
-     * Callers may call this multiple times with different audio tracks to change
-     * the underlying {@link AudioTrack} without stopping and re-starting injection.
      *
-     * @param audioTrack The underlying {@link AudioTrack} to use for injection,
-     * or null if no audio (i.e. silence) should be injected while still keeping the
-     * record in a playing state.
+     * <p>Callers may call this multiple times with different audio tracks to change the underlying
+     * {@link AudioTrack} without stopping and re-starting injection.
+     *
+     * @param audioTrack The underlying {@link AudioTrack} to use for injection, or null if no audio
+     *   (i.e. silence) should be injected while still keeping the record in a playing state.
      */
     void setAudioTrack(@Nullable AudioTrack audioTrack) {
         Log.d(TAG, "set AudioTrack with " + audioTrack);
diff --git a/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl b/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
index 3cb0572..dcdb6c6 100644
--- a/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
+++ b/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
@@ -33,7 +33,7 @@
      * @param enabled Whether the sensor is enabled.
      * @param samplingPeriodMicros The requested sensor's sampling period in microseconds.
      * @param batchReportingLatencyMicros The requested maximum time interval in microseconds
-     * between the delivery of two batches of sensor events.
+     *   between the delivery of two batches of sensor events.
      */
     void onConfigurationChanged(in VirtualSensor sensor, boolean enabled, int samplingPeriodMicros,
             int batchReportLatencyMicros);
@@ -60,7 +60,7 @@
      * @param sensor The sensor, for which the channel was configured.
      * @param rateLevel The rate level used to configure the direct sensor channel.
      * @param reportToken A positive sensor report token, used to differentiate between events from
-     * different sensors within the same channel.
+     *   different sensors within the same channel.
      */
     void onDirectChannelConfigured(int channelHandle, in VirtualSensor sensor, int rateLevel,
             int reportToken);
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
index bda44d4..eaa1792 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensor.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -30,7 +30,7 @@
  * Representation of a sensor on a remote device, capable of sending events, such as an
  * accelerometer or a gyroscope.
  *
- * This registers the sensor device with the sensor framework as a runtime sensor.
+ * <p>A virtual sensor device is registered with the sensor framework as a runtime sensor.
  *
  * @hide
  */
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java b/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
index e6bd6da..4d586f6 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
@@ -45,10 +45,10 @@
      *
      * @param sensor The sensor whose requested injection parameters have changed.
      * @param enabled Whether the sensor is enabled. True if any listeners are currently registered,
-     * and false otherwise.
+     *   and false otherwise.
      * @param samplingPeriod The requested sampling period of the sensor.
      * @param batchReportLatency The requested maximum time interval between the delivery of two
-     * batches of sensor events.
+     *   batches of sensor events.
      */
     void onConfigurationChanged(@NonNull VirtualSensor sensor, boolean enabled,
             @NonNull Duration samplingPeriod, @NonNull Duration batchReportLatency);
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
index ef55ca9..3bdf9aa 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -31,7 +31,9 @@
 
 /**
  * Configuration for creation of a virtual sensor.
+ *
  * @see VirtualSensor
+ *
  * @hide
  */
 @SystemApi
@@ -122,6 +124,7 @@
 
     /**
      * Returns the vendor string of the sensor.
+     *
      * @see Builder#setVendor
      */
     @Nullable
@@ -130,7 +133,8 @@
     }
 
     /**
-     * Returns maximum range of the sensor in the sensor's unit.
+     * Returns the maximum range of the sensor in the sensor's unit.
+     *
      * @see Sensor#getMaximumRange
      */
     public float getMaximumRange() {
@@ -138,7 +142,8 @@
     }
 
     /**
-     * Returns The resolution of the sensor in the sensor's unit.
+     * Returns the resolution of the sensor in the sensor's unit.
+     *
      * @see Sensor#getResolution
      */
     public float getResolution() {
@@ -146,7 +151,8 @@
     }
 
     /**
-     * Returns The power in mA used by this sensor while in use.
+     * Returns the power in mA used by this sensor while in use.
+     *
      * @see Sensor#getPower
      */
     public float getPower() {
@@ -154,8 +160,9 @@
     }
 
     /**
-     * Returns The minimum delay allowed between two events in microseconds, or zero depending on
+     * Returns the minimum delay allowed between two events in microseconds, or zero depending on
      * the sensor type.
+     *
      * @see Sensor#getMinDelay
      */
     public int getMinDelay() {
@@ -163,7 +170,8 @@
     }
 
     /**
-     * Returns The maximum delay between two sensor events in microseconds.
+     * Returns the maximum delay between two sensor events in microseconds.
+     *
      * @see Sensor#getMaxDelay
      */
     public int getMaxDelay() {
@@ -201,6 +209,7 @@
 
     /**
      * Returns the sensor flags.
+     *
      * @hide
      */
     public int getFlags() {
@@ -233,7 +242,7 @@
          *
          * @param type The type of the sensor, matching {@link Sensor#getType}.
          * @param name The name of the sensor. Must be unique among all sensors with the same type
-         *             that belong to the same virtual device.
+         *   that belong to the same virtual device.
          */
         public Builder(@IntRange(from = 1) int type, @NonNull String name) {
             if (type <= 0) {
@@ -275,6 +284,7 @@
 
         /**
          * Sets the maximum range of the sensor in the sensor's unit.
+         *
          * @see Sensor#getMaximumRange
          */
         @NonNull
@@ -285,6 +295,7 @@
 
         /**
          * Sets the resolution of the sensor in the sensor's unit.
+         *
          * @see Sensor#getResolution
          */
         @NonNull
@@ -295,6 +306,7 @@
 
         /**
          * Sets the power in mA used by this sensor while in use.
+         *
          * @see Sensor#getPower
          */
         @NonNull
@@ -305,6 +317,7 @@
 
         /**
          * Sets the minimum delay allowed between two events in microseconds.
+         *
          * @see Sensor#getMinDelay
          */
         @NonNull
@@ -315,6 +328,7 @@
 
         /**
          * Sets the maximum delay between two sensor events in microseconds.
+         *
          * @see Sensor#getMaxDelay
          */
         @NonNull
@@ -339,11 +353,11 @@
          * Sets whether direct sensor channel of the given types is supported.
          *
          * @param memoryTypes A combination of {@link SensorDirectChannel.MemoryType} flags
-         * indicating the types of shared memory supported for creating direct channels. Only
-         * {@link SensorDirectChannel#TYPE_MEMORY_FILE} direct channels may be supported for virtual
-         * sensors.
+         *   indicating the types of shared memory supported for creating direct channels. Only
+         *   {@link SensorDirectChannel#TYPE_MEMORY_FILE} direct channels may be supported for
+         *   virtual sensors.
          * @throws IllegalArgumentException if {@link SensorDirectChannel#TYPE_HARDWARE_BUFFER} is
-         * set to be supported.
+         *   set to be supported.
          */
         @NonNull
         public VirtualSensorConfig.Builder setDirectChannelTypesSupported(
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelCallback.java b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelCallback.java
index d352f94f..f10e9d0 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelCallback.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelCallback.java
@@ -45,6 +45,8 @@
  * <p>The callback is tied to the VirtualDevice's lifetime as the virtual sensors are created when
  * the device is created and destroyed when the device is destroyed.
  *
+ * @see VirtualSensorDirectChannelWriter
+ *
  * @hide
  */
 @SystemApi
@@ -94,7 +96,7 @@
      * @param sensor The sensor, for which the channel was configured.
      * @param rateLevel The rate level used to configure the direct sensor channel.
      * @param reportToken A positive sensor report token, used to differentiate between events from
-     * different sensors within the same channel.
+     *   different sensors within the same channel.
      *
      * @see VirtualSensorConfig.Builder#setHighestDirectReportRateLevel(int)
      * @see VirtualSensorConfig.Builder#setDirectChannelTypesSupported(int)
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
index 6aed96f..bf78dd0 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
@@ -41,6 +41,41 @@
  * write the events from the relevant sensors directly to the shared memory regions of the
  * corresponding {@link SensorDirectChannel} instances.
  *
+ * <p>Example:
+ * <p>During sensor and virtual device creation:
+ * <pre>
+ * VirtualSensorDirectChannelWriter writer = new VirtualSensorDirectChannelWriter();
+ * VirtualSensorDirectChannelCallback callback = new VirtualSensorDirectChannelCallback() {
+ *     @Override
+ *     public void onDirectChannelCreated(int channelHandle, SharedMemory sharedMemory) {
+ *         writer.addChannel(channelHandle, sharedMemory);
+ *     }
+ *     @Override
+ *     public void onDirectChannelDestroyed(int channelHandle);
+ *         writer.removeChannel(channelHandle);
+ *     }
+ *     @Override
+ *     public void onDirectChannelConfigured(int channelHandle, VirtualSensor sensor, int rateLevel,
+ *             int reportToken)
+ *         if (!writer.configureChannel(channelHandle, sensor, rateLevel, reportToken)) {
+ *              // handle error
+ *         }
+ *     }
+ * }
+ * </pre>
+ * <p>During the virtual device lifetime:
+ * <pre>
+ * VirtualSensor sensor = ...
+ * while (shouldInjectEvents(sensor)) {
+ *     if (!writer.writeSensorEvent(sensor, event)) {
+ *         // handle error
+ *     }
+ * }
+ * writer.close();
+ * </pre>
+ * <p>Note that the virtual device owner should take the currently configured rate level into
+ * account when deciding whether and how often to inject events for a particular sensor.
+ *
  * @see android.hardware.SensorDirectChannel#configure
  * @see VirtualSensorDirectChannelCallback
  *
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
index 01b4975..a368467e 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
@@ -121,7 +121,7 @@
          * monotonically increasing using the same time base as
          * {@link android.os.SystemClock#elapsedRealtimeNanos()}.
          *
-         * If not explicitly set, the current timestamp is used for the sensor event.
+         * <p>If not explicitly set, the current timestamp is used for the sensor event.
          *
          * @see android.hardware.SensorEvent#timestamp
          */
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 6ff4271..f946754 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -531,8 +531,9 @@
         mInstantAppVisibility = o.mInstantAppVisibility;
     }
 
-    /** {@inheritDoc} */
-    public String toString() {
+    /** @hide */
+    public String toLongString() {
+        // Not implemented directly as toString() due to potential memory regression
         final StringBuilder sb = new StringBuilder();
         sb.append("IntentFilter {");
         sb.append(" pri=");
diff --git a/core/java/android/credentials/ui/CancelUiRequest.java b/core/java/android/credentials/ui/CancelUiRequest.java
index 6bd9de4..d4c249e 100644
--- a/core/java/android/credentials/ui/CancelUiRequest.java
+++ b/core/java/android/credentials/ui/CancelUiRequest.java
@@ -40,24 +40,50 @@
     @NonNull
     private final IBinder mToken;
 
+    private final boolean mShouldShowCancellationUi;
+
+    @NonNull
+    private final String mAppPackageName;
+
     /** Returns the request token matching the user request that should be cancelled. */
     @NonNull
     public IBinder getToken() {
         return mToken;
     }
 
-    public CancelUiRequest(@NonNull IBinder token) {
+    @NonNull
+    public String getAppPackageName() {
+        return mAppPackageName;
+    }
+
+    /**
+     * Returns whether the UI should render a cancellation UI upon the request. If false, the UI
+     * will be silently cancelled.
+     */
+    public boolean shouldShowCancellationUi() {
+        return mShouldShowCancellationUi;
+    }
+
+    public CancelUiRequest(@NonNull IBinder token, boolean shouldShowCancellationUi,
+            @NonNull String appPackageName) {
         mToken = token;
+        mShouldShowCancellationUi = shouldShowCancellationUi;
+        mAppPackageName = appPackageName;
     }
 
     private CancelUiRequest(@NonNull Parcel in) {
         mToken = in.readStrongBinder();
         AnnotationValidations.validate(NonNull.class, null, mToken);
+        mShouldShowCancellationUi = in.readBoolean();
+        mAppPackageName = in.readString8();
+        AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeStrongBinder(mToken);
+        dest.writeBoolean(mShouldShowCancellationUi);
+        dest.writeString8(mAppPackageName);
     }
 
     @Override
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index dcfef56..5e8372d 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -72,7 +72,8 @@
      * @hide
      */
     @NonNull
-    public static Intent createCancelUiIntent(@NonNull IBinder requestToken) {
+    public static Intent createCancelUiIntent(@NonNull IBinder requestToken,
+            boolean shouldShowCancellationUi, @NonNull String appPackageName) {
         Intent intent = new Intent();
         ComponentName componentName =
                 ComponentName.unflattenFromString(
@@ -81,7 +82,8 @@
                                         com.android.internal.R.string
                                                 .config_credentialManagerDialogComponent));
         intent.setComponent(componentName);
-        intent.putExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST, new CancelUiRequest(requestToken));
+        intent.putExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
+                new CancelUiRequest(requestToken, shouldShowCancellationUi, appPackageName));
         return intent;
     }
 
diff --git a/core/java/android/service/remotelockscreenvalidation/RemoteLockscreenValidationClientImpl.java b/core/java/android/service/remotelockscreenvalidation/RemoteLockscreenValidationClientImpl.java
index 140ef39..a5acf54 100644
--- a/core/java/android/service/remotelockscreenvalidation/RemoteLockscreenValidationClientImpl.java
+++ b/core/java/android/service/remotelockscreenvalidation/RemoteLockscreenValidationClientImpl.java
@@ -179,7 +179,7 @@
                     PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
         } catch (PackageManager.NameNotFoundException e) {
             Log.w(TAG, TextUtils.formatSimple("Cannot resolve service %s",
-                    serviceComponent.getClass().getName()));
+                    serviceComponent.getClassName()));
             return null;
         }
     }
diff --git a/core/java/android/util/TeeWriter.java b/core/java/android/util/TeeWriter.java
new file mode 100644
index 0000000..439a0c2
--- /dev/null
+++ b/core/java/android/util/TeeWriter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.annotation.NonNull;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Objects;
+
+/**
+ * Writer that offers to "tee" identical output to multiple underlying
+ * {@link Writer} instances.
+ *
+ * @see https://man7.org/linux/man-pages/man1/tee.1.html
+ * @hide
+ */
+public class TeeWriter extends Writer {
+    private final @NonNull Writer[] mWriters;
+
+    public TeeWriter(@NonNull Writer... writers) {
+        for (Writer writer : writers) {
+            Objects.requireNonNull(writer);
+        }
+        mWriters = writers;
+    }
+
+    @Override
+    public void write(char[] cbuf, int off, int len) throws IOException {
+        for (Writer writer : mWriters) {
+            writer.write(cbuf, off, len);
+        }
+    }
+
+    @Override
+    public void flush() throws IOException {
+        for (Writer writer : mWriters) {
+            writer.flush();
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        for (Writer writer : mWriters) {
+            writer.close();
+        }
+    }
+}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 5476088..baefd85 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1218,47 +1218,51 @@
     }
 
     /**
-     * Returns the display's HDR capabilities.
+     * Returns the current display mode's HDR capabilities.
      *
      * @see #isHdr()
      */
     public HdrCapabilities getHdrCapabilities() {
         synchronized (mLock) {
             updateDisplayInfoLocked();
-            if (mDisplayInfo.userDisabledHdrTypes.length == 0) {
-                return mDisplayInfo.hdrCapabilities;
-            }
-
             if (mDisplayInfo.hdrCapabilities == null) {
                 return null;
             }
-
-            ArraySet<Integer> enabledTypesSet = new ArraySet<>();
-            for (int supportedType : mDisplayInfo.hdrCapabilities.getSupportedHdrTypes()) {
-                boolean typeDisabled = false;
-                for (int userDisabledType : mDisplayInfo.userDisabledHdrTypes) {
-                    if (supportedType == userDisabledType) {
-                        typeDisabled = true;
-                        break;
+            int[] supportedHdrTypes;
+            if (mDisplayInfo.userDisabledHdrTypes.length == 0) {
+                int[] modeSupportedHdrTypes = getMode().getSupportedHdrTypes();
+                supportedHdrTypes = Arrays.copyOf(modeSupportedHdrTypes,
+                        modeSupportedHdrTypes.length);
+            } else {
+                ArraySet<Integer> enabledTypesSet = new ArraySet<>();
+                for (int supportedType : getMode().getSupportedHdrTypes()) {
+                    if (!contains(mDisplayInfo.userDisabledHdrTypes, supportedType)) {
+                        enabledTypesSet.add(supportedType);
                     }
                 }
-                if (!typeDisabled) {
-                    enabledTypesSet.add(supportedType);
+
+                supportedHdrTypes = new int[enabledTypesSet.size()];
+                int index = 0;
+                for (int enabledType : enabledTypesSet) {
+                    supportedHdrTypes[index++] = enabledType;
                 }
             }
-
-            int[] enabledTypes = new int[enabledTypesSet.size()];
-            int index = 0;
-            for (int enabledType : enabledTypesSet) {
-                enabledTypes[index++] = enabledType;
-            }
-            return new HdrCapabilities(enabledTypes,
+            return new HdrCapabilities(supportedHdrTypes,
                     mDisplayInfo.hdrCapabilities.mMaxLuminance,
                     mDisplayInfo.hdrCapabilities.mMaxAverageLuminance,
                     mDisplayInfo.hdrCapabilities.mMinLuminance);
         }
     }
 
+    private boolean contains(int[] disabledHdrTypes, int hdrType) {
+        for (Integer disabledHdrFormat : disabledHdrTypes) {
+            if (disabledHdrFormat == hdrType) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * @hide
      * Returns the current mode's supported HDR types.
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 24a0355..d35aff9 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -158,6 +158,14 @@
      */
     public Matrix transform;
 
+    /**
+     * The input token for the window to which focus should be transferred when this input window
+     * can be successfully focused. If null, this input window will not transfer its focus to
+     * any other window.
+     */
+    @Nullable
+    public IBinder focusTransferTarget;
+
     private native void nativeDispose();
 
     public InputWindowHandle(InputApplicationHandle inputApplicationHandle, int displayId) {
@@ -195,6 +203,7 @@
             transform = new Matrix();
             transform.set(other.transform);
         }
+        focusTransferTarget = other.focusTransferTarget;
     }
 
     @Override
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 0db52aa..bc6a3b5 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -265,7 +265,7 @@
             int transformHint);
     private static native void nativeRemoveCurrentInputFocus(long nativeObject, int displayId);
     private static native void nativeSetFocusedWindow(long transactionObj, IBinder toToken,
-            String windowName, IBinder focusedToken, String focusedWindowName, int displayId);
+            String windowName, int displayId);
     private static native void nativeSetFrameTimelineVsync(long transactionObj,
             long frameTimelineVsyncId);
     private static native void nativeAddJankDataListener(long nativeListener,
@@ -3604,28 +3604,7 @@
          */
         public Transaction setFocusedWindow(@NonNull IBinder token, String windowName,
                 int displayId) {
-            nativeSetFocusedWindow(mNativeObject, token,  windowName,
-                    null /* focusedToken */, null /* focusedWindowName */, displayId);
-            return this;
-        }
-
-        /**
-         * Set focus on the window identified by the input {@code token} if the window identified by
-         * the input {@code focusedToken} is currently focused. If the {@code focusedToken} does not
-         * have focus, the request is dropped.
-         *
-         * This is used by forward focus transfer requests from clients that host embedded windows,
-         * and want to transfer focus to/from them.
-         *
-         * @hide
-         */
-        public Transaction requestFocusTransfer(@NonNull IBinder token,
-                                                String windowName,
-                                                @NonNull IBinder focusedToken,
-                                                String focusedWindowName,
-                                                int displayId) {
-            nativeSetFocusedWindow(mNativeObject, token, windowName, focusedToken,
-                    focusedWindowName, displayId);
+            nativeSetFocusedWindow(mNativeObject, token, windowName, displayId);
             return this;
         }
 
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 0560caf..9868144 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -57,18 +57,16 @@
         SurfaceControl mLeash;
         Rect mFrame;
         Rect mAttachedFrame;
+        IBinder mFocusGrantToken;
 
-        State(SurfaceControl sc, WindowManager.LayoutParams p,
-                int displayId, IBinder inputChannelToken, IWindow client, SurfaceControl leash,
-                Rect frame, Rect attachedFrame) {
+        State(SurfaceControl sc, WindowManager.LayoutParams p, int displayId, IWindow client,
+                SurfaceControl leash, Rect frame) {
             mSurfaceControl = sc;
             mParams.copyFrom(p);
             mDisplayId = displayId;
-            mInputChannelToken = inputChannelToken;
             mClient = client;
             mLeash = leash;
             mFrame = frame;
-            mAttachedFrame = attachedFrame;
         }
     };
 
@@ -182,45 +180,53 @@
                 .setParent(leash)
                 .build();
 
+        final State state = new State(sc, attrs, displayId, window, leash, /* frame= */ new Rect());
+        synchronized (this) {
+            State parentState = mStateForWindow.get(attrs.token);
+            if (parentState != null) {
+                state.mAttachedFrame = parentState.mFrame;
+            }
+
+            // Give the first window the mFocusGrantToken since that's the token the host can use
+            // to give focus to the embedded.
+            if (mStateForWindow.isEmpty()) {
+                state.mFocusGrantToken = mFocusGrantToken;
+            } else {
+                state.mFocusGrantToken = new Binder();
+            }
+
+            mStateForWindow.put(window.asBinder(), state);
+        }
+
+        if (state.mAttachedFrame == null) {
+            outAttachedFrame.set(0, 0, -1, -1);
+        } else {
+            outAttachedFrame.set(state.mAttachedFrame);
+        }
+        outSizeCompatScale[0] = 1f;
+
         if (((attrs.inputFeatures &
                 WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0)) {
             try {
                 if (mRealWm instanceof IWindowSession.Stub) {
                     mRealWm.grantInputChannel(displayId,
                             new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"),
-                            window, mHostInputToken,
-                            attrs.flags, attrs.privateFlags, attrs.inputFeatures, attrs.type,
-                            attrs.token, mFocusGrantToken, attrs.getTitle().toString(),
+                            window, mHostInputToken, attrs.flags, attrs.privateFlags,
+                            attrs.inputFeatures, attrs.type,
+                            attrs.token, state.mFocusGrantToken, attrs.getTitle().toString(),
                             outInputChannel);
                 } else {
                     mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags,
                             attrs.privateFlags, attrs.inputFeatures, attrs.type, attrs.token,
-                            mFocusGrantToken, attrs.getTitle().toString(), outInputChannel);
+                            state.mFocusGrantToken, attrs.getTitle().toString(), outInputChannel);
                 }
+                state.mInputChannelToken =
+                        outInputChannel != null ? outInputChannel.getToken() : null;
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to grant input to surface: ", e);
             }
         }
 
-        final State state = new State(sc, attrs, displayId,
-                outInputChannel != null ? outInputChannel.getToken() : null, window,
-                leash, /* frame= */ new Rect(), /* attachedFrame= */ null);
-        Rect parentFrame = null;
-        synchronized (this) {
-            State parentState = mStateForWindow.get(attrs.token);
-            if (parentState != null) {
-                parentFrame = parentState.mFrame;
-            }
-            mStateForWindow.put(window.asBinder(), state);
-        }
-        state.mAttachedFrame = parentFrame;
-        if (parentFrame == null) {
-            outAttachedFrame.set(0, 0, -1, -1);
-        } else {
-            outAttachedFrame.set(parentFrame);
-        }
-        outSizeCompatScale[0] = 1f;
-
         final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE |
                         WindowManagerGlobal.ADD_FLAG_USE_BLAST;
 
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 6872536..1840bcb 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -1217,9 +1217,11 @@
      * notify cursor/anchor locations.
      *
      * @param cursorUpdateMode any combination of update modes and filters:
-     * {@link #CURSOR_UPDATE_IMMEDIATE}, {@link #CURSOR_UPDATE_MONITOR}, and date filters:
+     * {@link #CURSOR_UPDATE_IMMEDIATE}, {@link #CURSOR_UPDATE_MONITOR}, and data filters:
      * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS},
-     * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}.
+     * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER},
+     * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
+     * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}.
      * Pass {@code 0} to disable them. However, if an unknown flag is provided, request will be
      * rejected and method will return {@code false}.
      * @return {@code true} if the request is scheduled. {@code false} to indicate that when the
@@ -1240,7 +1242,9 @@
      * {@link #CURSOR_UPDATE_IMMEDIATE}, {@link #CURSOR_UPDATE_MONITOR}
      * @param cursorUpdateFilter any combination of data filters:
      * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS},
-     * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}.
+     * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER},
+     * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
+     * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}.
      *
      * <p>Pass {@code 0} to disable them. However, if an unknown flag is provided, request will be
      * rejected and method will return {@code false}.</p>
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 515b95c..82cf073 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1648,6 +1648,7 @@
      *
      * @param userId user ID to query
      * @return {@link List} of {@link InputMethodInfo}.
+     * @see #getEnabledInputMethodSubtypeListAsUser(String, boolean, int)
      * @hide
      */
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
@@ -1676,6 +1677,27 @@
     }
 
     /**
+     * Returns a list of enabled input method subtypes for the specified input method info for the
+     * specified user.
+     *
+     * @param imeId IME ID to be queried about.
+     * @param allowsImplicitlyEnabledSubtypes {@code true} to include implicitly enabled subtypes.
+     * @param userId user ID to be queried about.
+     *               {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required if this is
+     *               different from the calling process user ID.
+     * @return {@link List} of {@link InputMethodSubtype}.
+     * @see #getEnabledInputMethodListAsUser(int)
+     * @hide
+     */
+    @NonNull
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+    public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(
+            @NonNull String imeId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
+        return IInputMethodManagerGlobalInvoker.getEnabledInputMethodSubtypeList(
+                Objects.requireNonNull(imeId), allowsImplicitlyEnabledSubtypes, userId);
+    }
+
+    /**
      * @deprecated Use {@link InputMethodService#showStatusIcon(int)} instead. This method was
      * intended for IME developers who should be accessing APIs through the service. APIs in this
      * class are intended for app developers interacting with the IME.
diff --git a/core/java/android/window/RemoteTransition.java b/core/java/android/window/RemoteTransition.java
index 4bd15f2..4cc7ec5 100644
--- a/core/java/android/window/RemoteTransition.java
+++ b/core/java/android/window/RemoteTransition.java
@@ -38,9 +38,18 @@
     /** The application thread that will be running the remote transition. */
     private @Nullable IApplicationThread mAppThread;
 
+    /** A name for this that can be used for debugging. */
+    private @Nullable String mDebugName;
+
     /** Constructs with no app thread (animation runs in shell). */
     public RemoteTransition(@NonNull IRemoteTransition remoteTransition) {
-        this(remoteTransition, null /* appThread */);
+        this(remoteTransition, null /* appThread */, null /* debugName */);
+    }
+
+    /** Constructs with no app thread (animation runs in shell). */
+    public RemoteTransition(@NonNull IRemoteTransition remoteTransition,
+            @Nullable String debugName) {
+        this(remoteTransition, null /* appThread */, debugName);
     }
 
     /** Get the IBinder associated with the underlying IRemoteTransition. */
@@ -70,15 +79,19 @@
      *   The actual remote-transition interface used to run the transition animation.
      * @param appThread
      *   The application thread that will be running the remote transition.
+     * @param debugName
+     *   A name for this that can be used for debugging.
      */
     @DataClass.Generated.Member
     public RemoteTransition(
             @NonNull IRemoteTransition remoteTransition,
-            @Nullable IApplicationThread appThread) {
+            @Nullable IApplicationThread appThread,
+            @Nullable String debugName) {
         this.mRemoteTransition = remoteTransition;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mRemoteTransition);
         this.mAppThread = appThread;
+        this.mDebugName = debugName;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -100,6 +113,14 @@
     }
 
     /**
+     * A name for this that can be used for debugging.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getDebugName() {
+        return mDebugName;
+    }
+
+    /**
      * The actual remote-transition interface used to run the transition animation.
      */
     @DataClass.Generated.Member
@@ -119,6 +140,15 @@
         return this;
     }
 
+    /**
+     * A name for this that can be used for debugging.
+     */
+    @DataClass.Generated.Member
+    public @NonNull RemoteTransition setDebugName(@NonNull String value) {
+        mDebugName = value;
+        return this;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -127,7 +157,8 @@
 
         return "RemoteTransition { " +
                 "remoteTransition = " + mRemoteTransition + ", " +
-                "appThread = " + mAppThread +
+                "appThread = " + mAppThread + ", " +
+                "debugName = " + mDebugName +
         " }";
     }
 
@@ -139,9 +170,11 @@
 
         byte flg = 0;
         if (mAppThread != null) flg |= 0x2;
+        if (mDebugName != null) flg |= 0x4;
         dest.writeByte(flg);
         dest.writeStrongInterface(mRemoteTransition);
         if (mAppThread != null) dest.writeStrongInterface(mAppThread);
+        if (mDebugName != null) dest.writeString(mDebugName);
     }
 
     @Override
@@ -158,11 +191,13 @@
         byte flg = in.readByte();
         IRemoteTransition remoteTransition = IRemoteTransition.Stub.asInterface(in.readStrongBinder());
         IApplicationThread appThread = (flg & 0x2) == 0 ? null : IApplicationThread.Stub.asInterface(in.readStrongBinder());
+        String debugName = (flg & 0x4) == 0 ? null : in.readString();
 
         this.mRemoteTransition = remoteTransition;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mRemoteTransition);
         this.mAppThread = appThread;
+        this.mDebugName = debugName;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -182,10 +217,10 @@
     };
 
     @DataClass.Generated(
-            time = 1630690027011L,
+            time = 1678926409863L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/window/RemoteTransition.java",
-            inputSignatures = "private @android.annotation.NonNull android.window.IRemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.app.IApplicationThread mAppThread\npublic @android.annotation.Nullable android.os.IBinder asBinder()\nclass RemoteTransition extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
+            inputSignatures = "private @android.annotation.NonNull android.window.IRemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.app.IApplicationThread mAppThread\nprivate @android.annotation.Nullable java.lang.String mDebugName\npublic @android.annotation.Nullable android.os.IBinder asBinder()\nclass RemoteTransition extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 4c482460..0f3eef7 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -149,8 +149,11 @@
     /** The task is launching behind home. */
     public static final int FLAG_TASK_LAUNCHING_BEHIND = 1 << 19;
 
+    /** The task became the top-most task even if it didn't change visibility. */
+    public static final int FLAG_MOVED_TO_TOP = 1 << 20;
+
     /** The first unused bit. This can be used by remotes to attach custom flags to this change. */
-    public static final int FLAG_FIRST_CUSTOM = 1 << 20;
+    public static final int FLAG_FIRST_CUSTOM = 1 << 21;
 
     /** The change belongs to a window that won't contain activities. */
     public static final int FLAGS_IS_NON_APP_WINDOW =
@@ -179,6 +182,7 @@
             FLAG_BACK_GESTURE_ANIMATED,
             FLAG_NO_ANIMATION,
             FLAG_TASK_LAUNCHING_BEHIND,
+            FLAG_MOVED_TO_TOP,
             FLAG_FIRST_CUSTOM
     })
     public @interface ChangeFlags {}
@@ -190,6 +194,9 @@
 
     private AnimationOptions mOptions;
 
+    /** This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work! */
+    private int mDebugId = -1;
+
     /** @hide */
     public TransitionInfo(@TransitionType int type, @TransitionFlags int flags) {
         mType = type;
@@ -202,6 +209,7 @@
         in.readTypedList(mChanges, Change.CREATOR);
         in.readTypedList(mRoots, Root.CREATOR);
         mOptions = in.readTypedObject(AnimationOptions.CREATOR);
+        mDebugId = in.readInt();
     }
 
     @Override
@@ -212,6 +220,7 @@
         dest.writeTypedList(mChanges);
         dest.writeTypedList(mRoots, flags);
         dest.writeTypedObject(mOptions, flags);
+        dest.writeInt(mDebugId);
     }
 
     @NonNull
@@ -347,11 +356,24 @@
         return (mFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0;
     }
 
+    /**
+     * Set an arbitrary "debug" id for this info. This id will not be used for any "real work",
+     * it is just for debugging and logging.
+     */
+    public void setDebugId(int id) {
+        mDebugId = id;
+    }
+
+    /** Get the "debug" id of this info. Do NOT use this for real work, only use for debugging. */
+    public int getDebugId() {
+        return mDebugId;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
-        sb.append("{t=").append(transitTypeToString(mType)).append(" f=0x")
-                .append(Integer.toHexString(mFlags)).append(" r=[");
+        sb.append("{id=").append(mDebugId).append(" t=").append(transitTypeToString(mType))
+                .append(" f=0x").append(Integer.toHexString(mFlags)).append(" r=[");
         for (int i = 0; i < mRoots.size(); ++i) {
             if (i > 0) {
                 sb.append(',');
@@ -510,6 +532,7 @@
      */
     public TransitionInfo localRemoteCopy() {
         final TransitionInfo out = new TransitionInfo(mType, mFlags);
+        out.mDebugId = mDebugId;
         for (int i = 0; i < mChanges.size(); ++i) {
             out.mChanges.add(mChanges.get(i).localRemoteCopy());
         }
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index ad20432..9f804b1 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -1586,61 +1586,109 @@
             return mShortcutInfo;
         }
 
+        /** Gets a string representation of a hierarchy-op type. */
+        public static String hopToString(int type) {
+            switch (type) {
+                case HIERARCHY_OP_TYPE_REPARENT: return "reparent";
+                case HIERARCHY_OP_TYPE_REORDER: return "reorder";
+                case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: return "ChildrenTasksReparent";
+                case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: return "SetLaunchRoot";
+                case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "SetAdjacentRoot";
+                case HIERARCHY_OP_TYPE_LAUNCH_TASK: return "LaunchTask";
+                case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: return "SetAdjacentFlagRoot";
+                case HIERARCHY_OP_TYPE_PENDING_INTENT: return "PendingIntent";
+                case HIERARCHY_OP_TYPE_START_SHORTCUT: return "StartShortcut";
+                case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER: return "addInsetsFrameProvider";
+                case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER:
+                    return "removeInsetsFrameProvider";
+                case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: return "setAlwaysOnTop";
+                case HIERARCHY_OP_TYPE_REMOVE_TASK: return "RemoveTask";
+                case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: return "finishActivity";
+                case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: return "ClearAdjacentRoot";
+                case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
+                    return "setReparentLeafTaskIfRelaunch";
+                case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION:
+                    return "addTaskFragmentOperation";
+                default: return "HOP(" + type + ")";
+            }
+        }
+
         @Override
         public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("{").append(hopToString(mType)).append(": ");
             switch (mType) {
                 case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
-                    return "{ChildrenTasksReparent: from=" + mContainer + " to=" + mReparent
-                            + " mToTop=" + mToTop + " mReparentTopOnly=" + mReparentTopOnly
-                            + " mWindowingMode=" + Arrays.toString(mWindowingModes)
-                            + " mActivityType=" + Arrays.toString(mActivityTypes) + "}";
+                    sb.append("from=").append(mContainer).append(" to=").append(mReparent)
+                            .append(" mToTop=").append(mToTop)
+                            .append(" mReparentTopOnly=").append(mReparentTopOnly)
+                            .append(" mWindowingMode=").append(Arrays.toString(mWindowingModes))
+                            .append(" mActivityType=").append(Arrays.toString(mActivityTypes));
+                    break;
                 case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT:
-                    return "{SetLaunchRoot: container=" + mContainer
-                            + " mWindowingMode=" + Arrays.toString(mWindowingModes)
-                            + " mActivityType=" + Arrays.toString(mActivityTypes) + "}";
+                    sb.append("container=").append(mContainer)
+                            .append(" mWindowingMode=").append(Arrays.toString(mWindowingModes))
+                            .append(" mActivityType=").append(Arrays.toString(mActivityTypes));
+                    break;
                 case HIERARCHY_OP_TYPE_REPARENT:
-                    return "{reparent: " + mContainer + " to " + (mToTop ? "top of " : "bottom of ")
-                            + mReparent + "}";
+                    sb.append(mContainer).append(" to ").append(mToTop ? "top of " : "bottom of ")
+                            .append(mReparent);
+                    break;
                 case HIERARCHY_OP_TYPE_REORDER:
-                    return "{reorder: " + mContainer + " to " + (mToTop ? "top" : "bottom") + "}";
+                    sb.append(mContainer).append(" to ").append(mToTop ? "top" : "bottom");
+                    break;
                 case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
-                    return "{SetAdjacentRoot: container=" + mContainer
-                            + " adjacentRoot=" + mReparent + "}";
+                    sb.append("container=").append(mContainer)
+                            .append(" adjacentRoot=").append(mReparent);
+                    break;
                 case HIERARCHY_OP_TYPE_LAUNCH_TASK:
-                    return "{LaunchTask: " + mLaunchOptions + "}";
+                    sb.append(mLaunchOptions);
+                    break;
                 case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT:
-                    return "{SetAdjacentFlagRoot: container=" + mContainer + " clearRoot=" + mToTop
-                            + "}";
+                    sb.append("container=").append(mContainer).append(" clearRoot=").append(mToTop);
+                    break;
                 case HIERARCHY_OP_TYPE_START_SHORTCUT:
-                    return "{StartShortcut: options=" + mLaunchOptions + " info=" + mShortcutInfo
-                            + "}";
+                    sb.append("options=").append(mLaunchOptions)
+                            .append(" info=").append(mShortcutInfo);
+                    break;
+                case HIERARCHY_OP_TYPE_PENDING_INTENT:
+                    sb.append("options=").append(mLaunchOptions);
+                    break;
                 case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER:
-                    return "{addRectInsetsProvider: container=" + mContainer
-                            + " provider=" + mInsetsFrameProvider + "}";
                 case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER:
-                    return "{removeLocalInsetsProvider: container=" + mContainer
-                            + " provider=" + mInsetsFrameProvider + "}";
+                    sb.append("container=").append(mContainer)
+                            .append(" provider=").append(mInsetsFrameProvider);
+                    break;
                 case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
-                    return "{setAlwaysOnTop: container=" + mContainer
-                            + " alwaysOnTop=" + mAlwaysOnTop + "}";
+                    sb.append("container=").append(mContainer)
+                            .append(" alwaysOnTop=").append(mAlwaysOnTop);
+                    break;
                 case HIERARCHY_OP_TYPE_REMOVE_TASK:
-                    return "{RemoveTask: task=" + mContainer + "}";
+                    sb.append("task=").append(mContainer);
+                    break;
                 case HIERARCHY_OP_TYPE_FINISH_ACTIVITY:
-                    return "{finishActivity: activity=" + mContainer + "}";
+                    sb.append("activity=").append(mContainer);
+                    break;
                 case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS:
-                    return "{ClearAdjacentRoot: container=" + mContainer + "}";
+                    sb.append("container=").append(mContainer);
+                    break;
                 case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
-                    return "{setReparentLeafTaskIfRelaunch: container= " + mContainer
-                            + " reparentLeafTaskIfRelaunch= " + mReparentLeafTaskIfRelaunch + "}";
+                    sb.append("container= ").append(mContainer)
+                            .append(" reparentLeafTaskIfRelaunch= ")
+                            .append(mReparentLeafTaskIfRelaunch);
+                    break;
                 case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION:
-                    return "{addTaskFragmentOperation: fragmentToken= " + mContainer
-                            + " operation= " + mTaskFragmentOperation + "}";
+                    sb.append("fragmentToken= ").append(mContainer)
+                            .append(" operation= ").append(mTaskFragmentOperation);
+                    break;
                 default:
-                    return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
-                            + " mToTop=" + mToTop
-                            + " mWindowingMode=" + Arrays.toString(mWindowingModes)
-                            + " mActivityType=" + Arrays.toString(mActivityTypes) + "}";
+                    sb.append("container=").append(mContainer)
+                            .append(" reparent=").append(mReparent)
+                            .append(" mToTop=").append(mToTop)
+                            .append(" mWindowingMode=").append(Arrays.toString(mWindowingModes))
+                            .append(" mActivityType=").append(Arrays.toString(mActivityTypes));
             }
+            return sb.append("}").toString();
         }
 
         @Override
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 0cb87fe..7ad2a68 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -524,11 +524,6 @@
     public static final String DEFAULT_QR_CODE_SCANNER = "default_qr_code_scanner";
 
     /**
-     * (boolean) Whether the task manager entrypoint is enabled.
-     */
-    public static final String TASK_MANAGER_ENABLED = "task_manager_enabled";
-
-    /**
      * (boolean) Whether the task manager should show an attention grabbing dot when tasks changed.
      */
     public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot";
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 6344568..4b9e77e 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -36,6 +36,7 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
@@ -244,6 +245,7 @@
     public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
     public static final int CUJ_IME_INSETS_ANIMATION = 69;
     public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70;
+    public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71;
 
     private static final int NO_STATSD_LOGGING = -1;
 
@@ -323,6 +325,7 @@
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT,
     };
 
     private static class InstanceHolder {
@@ -418,6 +421,7 @@
             CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
             CUJ_IME_INSETS_ANIMATION,
             CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION,
+            CUJ_LAUNCHER_OPEN_SEARCH_RESULT,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -968,6 +972,8 @@
                 return "IME_INSETS_ANIMATION";
             case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION:
                 return "LOCKSCREEN_CLOCK_MOVE_ANIMATION";
+            case CUJ_LAUNCHER_OPEN_SEARCH_RESULT:
+                return "LAUNCHER_OPEN_SEARCH_RESULT";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index f7c03cd..ae58626 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -28,6 +28,7 @@
 import android.media.MediaRoute2Info;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.view.KeyEvent;
 import android.service.notification.StatusBarNotification;
 
 import com.android.internal.statusbar.IAddTileResultCallback;
@@ -141,7 +142,7 @@
     void addQsTile(in ComponentName tile);
     void remQsTile(in ComponentName tile);
     void clickQsTile(in ComponentName tile);
-    void handleSystemKey(in int key);
+    void handleSystemKey(in KeyEvent key);
 
     /**
      * Methods to show toast messages for screen pinning
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index c1dbc87..3708859 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -29,6 +29,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.view.KeyEvent;
 import android.service.notification.StatusBarNotification;
 
 import com.android.internal.logging.InstanceId;
@@ -110,7 +111,7 @@
     void remTile(in ComponentName tile);
     void clickTile(in ComponentName tile);
     @UnsupportedAppUsage
-    void handleSystemKey(in int key);
+    void handleSystemKey(in KeyEvent key);
     int getLastSystemKey();
 
     /**
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 241320f..416d991 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -74,6 +74,7 @@
     WeakRefHandleField touchableRegionSurfaceControl;
     jfieldID transform;
     jfieldID windowToken;
+    jfieldID focusTransferTarget;
 } gInputWindowHandleClassInfo;
 
 static struct {
@@ -216,6 +217,17 @@
         mInfo.windowToken.clear();
     }
 
+    ScopedLocalRef<jobject>
+            focusTransferTargetObj(env,
+                                   env->GetObjectField(obj,
+                                                       gInputWindowHandleClassInfo
+                                                               .focusTransferTarget));
+    if (focusTransferTargetObj.get()) {
+        mInfo.focusTransferTarget = ibinderForJavaObject(env, focusTransferTargetObj.get());
+    } else {
+        mInfo.focusTransferTarget.clear();
+    }
+
     env->DeleteLocalRef(obj);
     return true;
 }
@@ -433,6 +445,9 @@
     GET_FIELD_ID(gInputWindowHandleClassInfo.windowToken, clazz, "windowToken",
                  "Landroid/os/IBinder;");
 
+    GET_FIELD_ID(gInputWindowHandleClassInfo.focusTransferTarget, clazz, "focusTransferTarget",
+                 "Landroid/os/IBinder;");
+
     jclass weakRefClazz;
     FIND_CLASS(weakRefClazz, "java/lang/ref/Reference");
 
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 03d6eec..e42c6f1 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1820,17 +1820,11 @@
 }
 
 static void nativeSetFocusedWindow(JNIEnv* env, jclass clazz, jlong transactionObj,
-                                   jobject toTokenObj, jstring windowNameJstr,
-                                   jobject focusedTokenObj, jstring focusedWindowNameJstr,
-                                   jint displayId) {
+                                   jobject toTokenObj, jstring windowNameJstr, jint displayId) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
     if (toTokenObj == NULL) return;
 
     sp<IBinder> toToken(ibinderForJavaObject(env, toTokenObj));
-    sp<IBinder> focusedToken;
-    if (focusedTokenObj != NULL) {
-        focusedToken = ibinderForJavaObject(env, focusedTokenObj);
-    }
 
     FocusRequest request;
     request.token = toToken;
@@ -1839,11 +1833,6 @@
         request.windowName = windowName.c_str();
     }
 
-    request.focusedToken = focusedToken;
-    if (focusedWindowNameJstr != NULL) {
-        ScopedUtfChars focusedWindowName(env, focusedWindowNameJstr);
-        request.focusedWindowName = focusedWindowName.c_str();
-    }
     request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
     request.displayId = displayId;
     transaction->setFocusedWindow(request);
@@ -2236,7 +2225,7 @@
             (void*)nativeGetHandle },
     {"nativeSetFixedTransformHint", "(JJI)V",
             (void*)nativeSetFixedTransformHint},
-    {"nativeSetFocusedWindow", "(JLandroid/os/IBinder;Ljava/lang/String;Landroid/os/IBinder;Ljava/lang/String;I)V",
+    {"nativeSetFocusedWindow", "(JLandroid/os/IBinder;Ljava/lang/String;I)V",
             (void*)nativeSetFocusedWindow},
     {"nativeRemoveCurrentInputFocus", "(JI)V",
             (void*)nativeRemoveCurrentInputFocus},
diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto
index e62af74..bab4b6e 100644
--- a/core/proto/android/providers/settings.proto
+++ b/core/proto/android/providers/settings.proto
@@ -21,6 +21,7 @@
 option java_outer_classname = "SettingsServiceProto";
 
 import "frameworks/base/core/proto/android/providers/settings/config.proto";
+import "frameworks/base/core/proto/android/providers/settings/generation.proto";
 import "frameworks/base/core/proto/android/providers/settings/global.proto";
 import "frameworks/base/core/proto/android/providers/settings/secure.proto";
 import "frameworks/base/core/proto/android/providers/settings/system.proto";
@@ -37,6 +38,9 @@
 
     // Config settings
     optional ConfigSettingsProto config_settings = 3;
+
+    // Generation registry stats
+    optional GenerationRegistryProto generation_registry = 4;
 }
 
 message UserSettingsProto {
diff --git a/core/proto/android/providers/settings/generation.proto b/core/proto/android/providers/settings/generation.proto
new file mode 100644
index 0000000..9dcbad2
--- /dev/null
+++ b/core/proto/android/providers/settings/generation.proto
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.providers.settings;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+message GenerationRegistryProto {
+  option (android.msg_privacy).dest = DEST_EXPLICIT;
+  optional int32 num_backing_stores = 1;
+  optional int32 num_max_backing_stores = 2;
+  repeated BackingStoreProto backing_stores = 3;
+}
+
+message BackingStoreProto {
+  optional int32 key = 1;
+  optional int32 backing_store_size = 2;
+  optional int32 num_cached_entries = 3;
+  repeated CacheEntryProto cache_entries = 4;
+}
+
+message CacheEntryProto {
+  optional string name = 1;
+  optional int32 generation = 2;
+}
\ No newline at end of file
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index e6c8557..bb3089b 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -391,6 +391,9 @@
     optional int32 last_drop_input_mode = 36;
     optional int32 override_orientation = 37 [(.android.typedef) = "android.content.pm.ActivityInfo.ScreenOrientation"];
     optional bool should_send_compat_fake_focus = 38;
+    optional bool should_force_rotate_for_camera_compat = 39;
+    optional bool should_refresh_activity_for_camera_compat = 40;
+    optional bool should_refresh_activity_via_pause_for_camera_compat = 41;
 }
 
 /* represents WindowToken */
diff --git a/core/res/res/drawable-hdpi/pointer_alias.png b/core/res/res/drawable-hdpi/pointer_alias.png
new file mode 100644
index 0000000..d33fe3c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_alias.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_all_scroll.png b/core/res/res/drawable-hdpi/pointer_all_scroll.png
new file mode 100644
index 0000000..095aadc
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_arrow.png b/core/res/res/drawable-hdpi/pointer_arrow.png
index 85d066e..a949a1a 100644
--- a/core/res/res/drawable-hdpi/pointer_arrow.png
+++ b/core/res/res/drawable-hdpi/pointer_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_cell.png b/core/res/res/drawable-hdpi/pointer_cell.png
new file mode 100644
index 0000000..76910e6
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_cell.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_context_menu.png b/core/res/res/drawable-hdpi/pointer_context_menu.png
new file mode 100644
index 0000000..c45d29b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_copy.png b/core/res/res/drawable-hdpi/pointer_copy.png
new file mode 100644
index 0000000..c5eda2e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_copy.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_crosshair.png b/core/res/res/drawable-hdpi/pointer_crosshair.png
new file mode 100644
index 0000000..be767b2
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_crosshair.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_grab.png b/core/res/res/drawable-hdpi/pointer_grab.png
new file mode 100644
index 0000000..26da04d
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_grab.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_grabbing.png b/core/res/res/drawable-hdpi/pointer_grabbing.png
new file mode 100644
index 0000000..f4031a9
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_grabbing.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_hand.png b/core/res/res/drawable-hdpi/pointer_hand.png
new file mode 100644
index 0000000..a7ae55f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_hand.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_help.png b/core/res/res/drawable-hdpi/pointer_help.png
new file mode 100644
index 0000000..a3afdb6
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png
new file mode 100644
index 0000000..9388f16
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_nodrop.png b/core/res/res/drawable-hdpi/pointer_nodrop.png
new file mode 100644
index 0000000..7043323
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_nodrop.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_spot_anchor.png b/core/res/res/drawable-hdpi/pointer_spot_anchor.png
index 784f613..4b74e2d 100644
--- a/core/res/res/drawable-hdpi/pointer_spot_anchor.png
+++ b/core/res/res/drawable-hdpi/pointer_spot_anchor.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_spot_hover.png b/core/res/res/drawable-hdpi/pointer_spot_hover.png
index 0e8353c..68d6e4a 100644
--- a/core/res/res/drawable-hdpi/pointer_spot_hover.png
+++ b/core/res/res/drawable-hdpi/pointer_spot_hover.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_spot_touch.png b/core/res/res/drawable-hdpi/pointer_spot_touch.png
index 3ad9b10..fda831f 100644
--- a/core/res/res/drawable-hdpi/pointer_spot_touch.png
+++ b/core/res/res/drawable-hdpi/pointer_spot_touch.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_text.png b/core/res/res/drawable-hdpi/pointer_text.png
new file mode 100644
index 0000000..ab0c80a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_text.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png
new file mode 100644
index 0000000..ab52bff
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png
new file mode 100644
index 0000000..1250d35
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png
new file mode 100644
index 0000000..6730c7b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_vertical_text.png b/core/res/res/drawable-hdpi/pointer_vertical_text.png
new file mode 100644
index 0000000..f079bc1
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_vertical_text.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_zoom_in.png b/core/res/res/drawable-hdpi/pointer_zoom_in.png
new file mode 100644
index 0000000..a3dc84b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_zoom_in.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_zoom_out.png b/core/res/res/drawable-hdpi/pointer_zoom_out.png
new file mode 100644
index 0000000..3ee31bd
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_zoom_out.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_alias.png b/core/res/res/drawable-mdpi/pointer_alias.png
index 8f61a39..619567a 100644
--- a/core/res/res/drawable-mdpi/pointer_alias.png
+++ b/core/res/res/drawable-mdpi/pointer_alias.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_alias_large.png b/core/res/res/drawable-mdpi/pointer_alias_large.png
index 606774d..b3f382e 100644
--- a/core/res/res/drawable-mdpi/pointer_alias_large.png
+++ b/core/res/res/drawable-mdpi/pointer_alias_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_all_scroll.png b/core/res/res/drawable-mdpi/pointer_all_scroll.png
index a897ef4..3db456e 100644
--- a/core/res/res/drawable-mdpi/pointer_all_scroll.png
+++ b/core/res/res/drawable-mdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_all_scroll_large.png b/core/res/res/drawable-mdpi/pointer_all_scroll_large.png
index c29db87..120e1d7 100644
--- a/core/res/res/drawable-mdpi/pointer_all_scroll_large.png
+++ b/core/res/res/drawable-mdpi/pointer_all_scroll_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_arrow.png b/core/res/res/drawable-mdpi/pointer_arrow.png
index 7a74ec1..77e4354 100644
--- a/core/res/res/drawable-mdpi/pointer_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_arrow_large.png b/core/res/res/drawable-mdpi/pointer_arrow_large.png
index 9f59c4c..e28a7a5 100644
--- a/core/res/res/drawable-mdpi/pointer_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_cell.png b/core/res/res/drawable-mdpi/pointer_cell.png
index b521389..e5ce946 100644
--- a/core/res/res/drawable-mdpi/pointer_cell.png
+++ b/core/res/res/drawable-mdpi/pointer_cell.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_cell_large.png b/core/res/res/drawable-mdpi/pointer_cell_large.png
index 3dec5e5..fcb9fc8 100644
--- a/core/res/res/drawable-mdpi/pointer_cell_large.png
+++ b/core/res/res/drawable-mdpi/pointer_cell_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_context_menu.png b/core/res/res/drawable-mdpi/pointer_context_menu.png
index 4e1ba4e..e0e849d 100644
--- a/core/res/res/drawable-mdpi/pointer_context_menu.png
+++ b/core/res/res/drawable-mdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_context_menu_large.png b/core/res/res/drawable-mdpi/pointer_context_menu_large.png
index 7c9e250..e8c9be4 100644
--- a/core/res/res/drawable-mdpi/pointer_context_menu_large.png
+++ b/core/res/res/drawable-mdpi/pointer_context_menu_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_copy.png b/core/res/res/drawable-mdpi/pointer_copy.png
index 254485c..e731108 100644
--- a/core/res/res/drawable-mdpi/pointer_copy.png
+++ b/core/res/res/drawable-mdpi/pointer_copy.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_copy_large.png b/core/res/res/drawable-mdpi/pointer_copy_large.png
index 2f0e082..15ccb04 100644
--- a/core/res/res/drawable-mdpi/pointer_copy_large.png
+++ b/core/res/res/drawable-mdpi/pointer_copy_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_crosshair.png b/core/res/res/drawable-mdpi/pointer_crosshair.png
index 8a06c77..be6bd34 100644
--- a/core/res/res/drawable-mdpi/pointer_crosshair.png
+++ b/core/res/res/drawable-mdpi/pointer_crosshair.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_crosshair_large.png b/core/res/res/drawable-mdpi/pointer_crosshair_large.png
index 51faf96..657df8d 100644
--- a/core/res/res/drawable-mdpi/pointer_crosshair_large.png
+++ b/core/res/res/drawable-mdpi/pointer_crosshair_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_grab.png b/core/res/res/drawable-mdpi/pointer_grab.png
index 0e0ea2e..d625b55 100644
--- a/core/res/res/drawable-mdpi/pointer_grab.png
+++ b/core/res/res/drawable-mdpi/pointer_grab.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_grab_large.png b/core/res/res/drawable-mdpi/pointer_grab_large.png
index 44a171c..9d36df0 100644
--- a/core/res/res/drawable-mdpi/pointer_grab_large.png
+++ b/core/res/res/drawable-mdpi/pointer_grab_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_grabbing.png b/core/res/res/drawable-mdpi/pointer_grabbing.png
index 9deb64c..71bb17b 100644
--- a/core/res/res/drawable-mdpi/pointer_grabbing.png
+++ b/core/res/res/drawable-mdpi/pointer_grabbing.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_grabbing_large.png b/core/res/res/drawable-mdpi/pointer_grabbing_large.png
index b602d2f..5574b07 100644
--- a/core/res/res/drawable-mdpi/pointer_grabbing_large.png
+++ b/core/res/res/drawable-mdpi/pointer_grabbing_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_hand.png b/core/res/res/drawable-mdpi/pointer_hand.png
index c614d9e..d7f7bed 100644
--- a/core/res/res/drawable-mdpi/pointer_hand.png
+++ b/core/res/res/drawable-mdpi/pointer_hand.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_hand_large.png b/core/res/res/drawable-mdpi/pointer_hand_large.png
index 464ec28..f775464 100644
--- a/core/res/res/drawable-mdpi/pointer_hand_large.png
+++ b/core/res/res/drawable-mdpi/pointer_hand_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_help.png b/core/res/res/drawable-mdpi/pointer_help.png
index d54b2b1..286242c 100644
--- a/core/res/res/drawable-mdpi/pointer_help.png
+++ b/core/res/res/drawable-mdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_help_large.png b/core/res/res/drawable-mdpi/pointer_help_large.png
index 69d1e41..27f4a84 100644
--- a/core/res/res/drawable-mdpi/pointer_help_large.png
+++ b/core/res/res/drawable-mdpi/pointer_help_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
index a2951a9..20f319a 100644
--- a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png
index 7086106..33ef5c9 100644
--- a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_nodrop.png b/core/res/res/drawable-mdpi/pointer_nodrop.png
index aa92895..931b740 100644
--- a/core/res/res/drawable-mdpi/pointer_nodrop.png
+++ b/core/res/res/drawable-mdpi/pointer_nodrop.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_nodrop_large.png b/core/res/res/drawable-mdpi/pointer_nodrop_large.png
index e150d04..88f77d3 100644
--- a/core/res/res/drawable-mdpi/pointer_nodrop_large.png
+++ b/core/res/res/drawable-mdpi/pointer_nodrop_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_anchor.png b/core/res/res/drawable-mdpi/pointer_spot_anchor.png
index 48d638b..a8bc03b 100644
--- a/core/res/res/drawable-mdpi/pointer_spot_anchor.png
+++ b/core/res/res/drawable-mdpi/pointer_spot_anchor.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_hover.png b/core/res/res/drawable-mdpi/pointer_spot_hover.png
index b304815..c3672b2 100644
--- a/core/res/res/drawable-mdpi/pointer_spot_hover.png
+++ b/core/res/res/drawable-mdpi/pointer_spot_hover.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_touch.png b/core/res/res/drawable-mdpi/pointer_spot_touch.png
index 659f809..1f146d2 100644
--- a/core/res/res/drawable-mdpi/pointer_spot_touch.png
+++ b/core/res/res/drawable-mdpi/pointer_spot_touch.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_text.png b/core/res/res/drawable-mdpi/pointer_text.png
index 34d1698..68a5535 100644
--- a/core/res/res/drawable-mdpi/pointer_text.png
+++ b/core/res/res/drawable-mdpi/pointer_text.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_text_large.png b/core/res/res/drawable-mdpi/pointer_text_large.png
index 2fba190..599dc69 100644
--- a/core/res/res/drawable-mdpi/pointer_text_large.png
+++ b/core/res/res/drawable-mdpi/pointer_text_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
index b0cd92c..fe7d496 100644
--- a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png
index eecaa89..7b2e20c 100644
--- a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
index f8d3527..95a6620 100644
--- a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png
index 9d47ecf..2e2904b 100644
--- a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
index 48c9379..ae6bfed 100644
--- a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png
index fd777b1..3beb1d1 100644
--- a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_vertical_text.png b/core/res/res/drawable-mdpi/pointer_vertical_text.png
index 9fcbcba..06a536b 100644
--- a/core/res/res/drawable-mdpi/pointer_vertical_text.png
+++ b/core/res/res/drawable-mdpi/pointer_vertical_text.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_vertical_text_large.png b/core/res/res/drawable-mdpi/pointer_vertical_text_large.png
index 1cbe49a..f03179b 100644
--- a/core/res/res/drawable-mdpi/pointer_vertical_text_large.png
+++ b/core/res/res/drawable-mdpi/pointer_vertical_text_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_0.png b/core/res/res/drawable-mdpi/pointer_wait_0.png
index ae32a44..aefdb46 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_0.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_0.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_1.png b/core/res/res/drawable-mdpi/pointer_wait_1.png
index afadc31..1939660 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_1.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_1.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_10.png b/core/res/res/drawable-mdpi/pointer_wait_10.png
index 4e5f3b0..cd3a8f5 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_10.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_10.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_11.png b/core/res/res/drawable-mdpi/pointer_wait_11.png
index f895e53..e8894ed 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_11.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_11.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_12.png b/core/res/res/drawable-mdpi/pointer_wait_12.png
index 7a155f5..f5af8b0 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_12.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_12.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_13.png b/core/res/res/drawable-mdpi/pointer_wait_13.png
index a9ae639..06e4462 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_13.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_13.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_14.png b/core/res/res/drawable-mdpi/pointer_wait_14.png
index 6761dda..7f0ac45 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_14.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_14.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_15.png b/core/res/res/drawable-mdpi/pointer_wait_15.png
index 98821ed..577fb78 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_15.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_15.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_16.png b/core/res/res/drawable-mdpi/pointer_wait_16.png
index 72f3853..d111d0b 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_16.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_16.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_17.png b/core/res/res/drawable-mdpi/pointer_wait_17.png
index a7452ed..55bd35a 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_17.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_17.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_18.png b/core/res/res/drawable-mdpi/pointer_wait_18.png
index ecb4f72..a1870ca 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_18.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_18.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_19.png b/core/res/res/drawable-mdpi/pointer_wait_19.png
index 1ce5d70..ffc4435 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_19.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_19.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_2.png b/core/res/res/drawable-mdpi/pointer_wait_2.png
index d42278a..04314b7 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_2.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_2.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_20.png b/core/res/res/drawable-mdpi/pointer_wait_20.png
index 2736fea..c98abab 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_20.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_20.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_21.png b/core/res/res/drawable-mdpi/pointer_wait_21.png
index e2fafd1..778e829 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_21.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_21.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_22.png b/core/res/res/drawable-mdpi/pointer_wait_22.png
index 24bd01a..9d61756 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_22.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_22.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_23.png b/core/res/res/drawable-mdpi/pointer_wait_23.png
index 26c6129..68c1def 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_23.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_23.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_24.png b/core/res/res/drawable-mdpi/pointer_wait_24.png
index 2597963..cb4e59f 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_24.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_24.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_25.png b/core/res/res/drawable-mdpi/pointer_wait_25.png
index c925d82..64662b2 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_25.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_25.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_26.png b/core/res/res/drawable-mdpi/pointer_wait_26.png
index 7c3735d..8a4a730 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_26.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_26.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_27.png b/core/res/res/drawable-mdpi/pointer_wait_27.png
index d4f2f65..6578225 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_27.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_27.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_28.png b/core/res/res/drawable-mdpi/pointer_wait_28.png
index 582c276..233efc3 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_28.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_28.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_29.png b/core/res/res/drawable-mdpi/pointer_wait_29.png
index f79f715..2513064 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_29.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_29.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_3.png b/core/res/res/drawable-mdpi/pointer_wait_3.png
index efc766e..a4b3de5 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_3.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_3.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_30.png b/core/res/res/drawable-mdpi/pointer_wait_30.png
index 636d793..f3dcdbb 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_30.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_30.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_31.png b/core/res/res/drawable-mdpi/pointer_wait_31.png
index 8f41a53..c0709aa 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_31.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_31.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_32.png b/core/res/res/drawable-mdpi/pointer_wait_32.png
index deef9b7..2456313 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_32.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_32.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_33.png b/core/res/res/drawable-mdpi/pointer_wait_33.png
index 6cad76b..d5506a8 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_33.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_33.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_34.png b/core/res/res/drawable-mdpi/pointer_wait_34.png
index 4b25825..365213d 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_34.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_34.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_35.png b/core/res/res/drawable-mdpi/pointer_wait_35.png
index ccfaf74..577c7b6 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_35.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_35.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_36.png b/core/res/res/drawable-mdpi/pointer_wait_36.png
new file mode 100644
index 0000000..9f7202b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_36.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_37.png b/core/res/res/drawable-mdpi/pointer_wait_37.png
new file mode 100644
index 0000000..7a041af
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_37.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_38.png b/core/res/res/drawable-mdpi/pointer_wait_38.png
new file mode 100644
index 0000000..d615452
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_38.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_39.png b/core/res/res/drawable-mdpi/pointer_wait_39.png
new file mode 100644
index 0000000..b6ecf0f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_39.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_4.png b/core/res/res/drawable-mdpi/pointer_wait_4.png
index d39d13a..f2a7759 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_4.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_4.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_40.png b/core/res/res/drawable-mdpi/pointer_wait_40.png
new file mode 100644
index 0000000..408ac15
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_40.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_41.png b/core/res/res/drawable-mdpi/pointer_wait_41.png
new file mode 100644
index 0000000..54f38cf
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_41.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_42.png b/core/res/res/drawable-mdpi/pointer_wait_42.png
new file mode 100644
index 0000000..7894cf5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_42.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_43.png b/core/res/res/drawable-mdpi/pointer_wait_43.png
new file mode 100644
index 0000000..8bd34af
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_43.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_44.png b/core/res/res/drawable-mdpi/pointer_wait_44.png
new file mode 100644
index 0000000..b79e17b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_44.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_45.png b/core/res/res/drawable-mdpi/pointer_wait_45.png
new file mode 100644
index 0000000..7105121
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_45.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_46.png b/core/res/res/drawable-mdpi/pointer_wait_46.png
new file mode 100644
index 0000000..09b2f61
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_46.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_47.png b/core/res/res/drawable-mdpi/pointer_wait_47.png
new file mode 100644
index 0000000..2415548a
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_47.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_48.png b/core/res/res/drawable-mdpi/pointer_wait_48.png
new file mode 100644
index 0000000..7b51615
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_48.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_49.png b/core/res/res/drawable-mdpi/pointer_wait_49.png
new file mode 100644
index 0000000..985c7bc
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_49.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_5.png b/core/res/res/drawable-mdpi/pointer_wait_5.png
index 1c5a7de..a6f9941 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_5.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_5.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_50.png b/core/res/res/drawable-mdpi/pointer_wait_50.png
new file mode 100644
index 0000000..c8b031f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_50.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_51.png b/core/res/res/drawable-mdpi/pointer_wait_51.png
new file mode 100644
index 0000000..74e74c9
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_51.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_52.png b/core/res/res/drawable-mdpi/pointer_wait_52.png
new file mode 100644
index 0000000..e42252d
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_52.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_53.png b/core/res/res/drawable-mdpi/pointer_wait_53.png
new file mode 100644
index 0000000..860de2a
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_53.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_54.png b/core/res/res/drawable-mdpi/pointer_wait_54.png
new file mode 100644
index 0000000..eb47cc1
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_54.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_55.png b/core/res/res/drawable-mdpi/pointer_wait_55.png
new file mode 100644
index 0000000..ce853acc
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_55.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_56.png b/core/res/res/drawable-mdpi/pointer_wait_56.png
new file mode 100644
index 0000000..e17ec11
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_56.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_57.png b/core/res/res/drawable-mdpi/pointer_wait_57.png
new file mode 100644
index 0000000..f450f04
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_57.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_58.png b/core/res/res/drawable-mdpi/pointer_wait_58.png
new file mode 100644
index 0000000..f3d26dd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_58.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_59.png b/core/res/res/drawable-mdpi/pointer_wait_59.png
new file mode 100644
index 0000000..5220f3c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_59.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_6.png b/core/res/res/drawable-mdpi/pointer_wait_6.png
index 5113b27..5f2b1f9 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_6.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_6.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_60.png b/core/res/res/drawable-mdpi/pointer_wait_60.png
new file mode 100644
index 0000000..b01a0cd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_60.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_61.png b/core/res/res/drawable-mdpi/pointer_wait_61.png
new file mode 100644
index 0000000..07a132d
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_61.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_62.png b/core/res/res/drawable-mdpi/pointer_wait_62.png
new file mode 100644
index 0000000..52e9768
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_62.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_63.png b/core/res/res/drawable-mdpi/pointer_wait_63.png
new file mode 100644
index 0000000..85b36f7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_63.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_64.png b/core/res/res/drawable-mdpi/pointer_wait_64.png
new file mode 100644
index 0000000..d684752
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_64.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_65.png b/core/res/res/drawable-mdpi/pointer_wait_65.png
new file mode 100644
index 0000000..7c0ee30
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_65.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_66.png b/core/res/res/drawable-mdpi/pointer_wait_66.png
new file mode 100644
index 0000000..54a7204
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_66.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_67.png b/core/res/res/drawable-mdpi/pointer_wait_67.png
new file mode 100644
index 0000000..8416304
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_67.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_68.png b/core/res/res/drawable-mdpi/pointer_wait_68.png
new file mode 100644
index 0000000..af2e5e2
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_68.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_69.png b/core/res/res/drawable-mdpi/pointer_wait_69.png
new file mode 100644
index 0000000..dd440d7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_69.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_7.png b/core/res/res/drawable-mdpi/pointer_wait_7.png
index 766a716..d950c53 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_7.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_7.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_70.png b/core/res/res/drawable-mdpi/pointer_wait_70.png
new file mode 100644
index 0000000..3b3843b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_70.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_71.png b/core/res/res/drawable-mdpi/pointer_wait_71.png
new file mode 100644
index 0000000..e8aba4c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_71.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_72.png b/core/res/res/drawable-mdpi/pointer_wait_72.png
new file mode 100644
index 0000000..17c30d50
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_72.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_73.png b/core/res/res/drawable-mdpi/pointer_wait_73.png
new file mode 100644
index 0000000..9336a80
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_73.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_74.png b/core/res/res/drawable-mdpi/pointer_wait_74.png
new file mode 100644
index 0000000..1f18ea5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_74.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_75.png b/core/res/res/drawable-mdpi/pointer_wait_75.png
new file mode 100644
index 0000000..24d2b774
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_75.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_76.png b/core/res/res/drawable-mdpi/pointer_wait_76.png
new file mode 100644
index 0000000..21cd4b1
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_76.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_77.png b/core/res/res/drawable-mdpi/pointer_wait_77.png
new file mode 100644
index 0000000..53d6532
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_77.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_78.png b/core/res/res/drawable-mdpi/pointer_wait_78.png
new file mode 100644
index 0000000..769f1c8
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_78.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_79.png b/core/res/res/drawable-mdpi/pointer_wait_79.png
new file mode 100644
index 0000000..3f89a84
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_79.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_8.png b/core/res/res/drawable-mdpi/pointer_wait_8.png
index 80fb2d9..c3026ea 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_8.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_8.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_80.png b/core/res/res/drawable-mdpi/pointer_wait_80.png
new file mode 100644
index 0000000..4cf0e15
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_80.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_9.png b/core/res/res/drawable-mdpi/pointer_wait_9.png
index db07e87..f1fb134 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_9.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_zoom_in.png b/core/res/res/drawable-mdpi/pointer_zoom_in.png
index 17c4e66..ef4e4a5 100644
--- a/core/res/res/drawable-mdpi/pointer_zoom_in.png
+++ b/core/res/res/drawable-mdpi/pointer_zoom_in.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_zoom_in_large.png b/core/res/res/drawable-mdpi/pointer_zoom_in_large.png
index 9b0fa7f..c484ce8 100644
--- a/core/res/res/drawable-mdpi/pointer_zoom_in_large.png
+++ b/core/res/res/drawable-mdpi/pointer_zoom_in_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_zoom_out.png b/core/res/res/drawable-mdpi/pointer_zoom_out.png
index 742f705..af725a4 100644
--- a/core/res/res/drawable-mdpi/pointer_zoom_out.png
+++ b/core/res/res/drawable-mdpi/pointer_zoom_out.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_zoom_out_large.png b/core/res/res/drawable-mdpi/pointer_zoom_out_large.png
index 1a9ec86..448ea45 100644
--- a/core/res/res/drawable-mdpi/pointer_zoom_out_large.png
+++ b/core/res/res/drawable-mdpi/pointer_zoom_out_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_alias.png b/core/res/res/drawable-xhdpi/pointer_alias.png
index fe0fd25..d71ba71 100644
--- a/core/res/res/drawable-xhdpi/pointer_alias.png
+++ b/core/res/res/drawable-xhdpi/pointer_alias.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_alias_large.png b/core/res/res/drawable-xhdpi/pointer_alias_large.png
index ad595ed..b178901 100644
--- a/core/res/res/drawable-xhdpi/pointer_alias_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_alias_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_all_scroll.png b/core/res/res/drawable-xhdpi/pointer_all_scroll.png
index e2374ec..e9d05d5 100644
--- a/core/res/res/drawable-xhdpi/pointer_all_scroll.png
+++ b/core/res/res/drawable-xhdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png b/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png
index 6029f52..1fd54fb 100644
--- a/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_arrow.png b/core/res/res/drawable-xhdpi/pointer_arrow.png
index 1cfd033..b2608c5 100644
--- a/core/res/res/drawable-xhdpi/pointer_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_arrow_large.png
index dc9007a..6019c5a 100644
--- a/core/res/res/drawable-xhdpi/pointer_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_cell.png b/core/res/res/drawable-xhdpi/pointer_cell.png
index 4ca09e3..e96b8bd 100644
--- a/core/res/res/drawable-xhdpi/pointer_cell.png
+++ b/core/res/res/drawable-xhdpi/pointer_cell.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_cell_large.png b/core/res/res/drawable-xhdpi/pointer_cell_large.png
index 50119b7..01d0270 100644
--- a/core/res/res/drawable-xhdpi/pointer_cell_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_cell_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_context_menu.png b/core/res/res/drawable-xhdpi/pointer_context_menu.png
index 05a59f8..d4b2bde 100644
--- a/core/res/res/drawable-xhdpi/pointer_context_menu.png
+++ b/core/res/res/drawable-xhdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_context_menu_large.png b/core/res/res/drawable-xhdpi/pointer_context_menu_large.png
index 148cf87..977df10 100644
--- a/core/res/res/drawable-xhdpi/pointer_context_menu_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_context_menu_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_copy.png b/core/res/res/drawable-xhdpi/pointer_copy.png
index 0cdbf21..5b6cc5b 100644
--- a/core/res/res/drawable-xhdpi/pointer_copy.png
+++ b/core/res/res/drawable-xhdpi/pointer_copy.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_copy_large.png b/core/res/res/drawable-xhdpi/pointer_copy_large.png
index 0e1350c..d78a410 100644
--- a/core/res/res/drawable-xhdpi/pointer_copy_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_copy_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_crosshair.png b/core/res/res/drawable-xhdpi/pointer_crosshair.png
index 86c649c..d384c94 100644
--- a/core/res/res/drawable-xhdpi/pointer_crosshair.png
+++ b/core/res/res/drawable-xhdpi/pointer_crosshair.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_crosshair_large.png b/core/res/res/drawable-xhdpi/pointer_crosshair_large.png
index fc59291..165daf3 100644
--- a/core/res/res/drawable-xhdpi/pointer_crosshair_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_crosshair_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_grab.png b/core/res/res/drawable-xhdpi/pointer_grab.png
index b5c28ba..46dd3ee 100644
--- a/core/res/res/drawable-xhdpi/pointer_grab.png
+++ b/core/res/res/drawable-xhdpi/pointer_grab.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_grab_large.png b/core/res/res/drawable-xhdpi/pointer_grab_large.png
index df98d89..1c7e63e 100644
--- a/core/res/res/drawable-xhdpi/pointer_grab_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_grab_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_grabbing.png b/core/res/res/drawable-xhdpi/pointer_grabbing.png
index 6aba589..2fb8a9c 100644
--- a/core/res/res/drawable-xhdpi/pointer_grabbing.png
+++ b/core/res/res/drawable-xhdpi/pointer_grabbing.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_grabbing_large.png b/core/res/res/drawable-xhdpi/pointer_grabbing_large.png
index f2d043c..3467a03 100644
--- a/core/res/res/drawable-xhdpi/pointer_grabbing_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_grabbing_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_hand.png b/core/res/res/drawable-xhdpi/pointer_hand.png
index 486cb24..926310c 100644
--- a/core/res/res/drawable-xhdpi/pointer_hand.png
+++ b/core/res/res/drawable-xhdpi/pointer_hand.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_hand_large.png b/core/res/res/drawable-xhdpi/pointer_hand_large.png
index 3113589..546b222 100644
--- a/core/res/res/drawable-xhdpi/pointer_hand_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_hand_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_help.png b/core/res/res/drawable-xhdpi/pointer_help.png
index abcf923..5a6805c 100644
--- a/core/res/res/drawable-xhdpi/pointer_help.png
+++ b/core/res/res/drawable-xhdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_help_large.png b/core/res/res/drawable-xhdpi/pointer_help_large.png
index b745e1e..4bdc3d1 100644
--- a/core/res/res/drawable-xhdpi/pointer_help_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_help_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
index 299ae11..caf2a97 100644
--- a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png
index 9c8fa5c..2f22640 100644
--- a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_nodrop.png b/core/res/res/drawable-xhdpi/pointer_nodrop.png
index 8e93f33..fdfc267 100644
--- a/core/res/res/drawable-xhdpi/pointer_nodrop.png
+++ b/core/res/res/drawable-xhdpi/pointer_nodrop.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_nodrop_large.png b/core/res/res/drawable-xhdpi/pointer_nodrop_large.png
index a392da7..2b5e8a4 100644
--- a/core/res/res/drawable-xhdpi/pointer_nodrop_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_nodrop_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_spot_anchor.png b/core/res/res/drawable-xhdpi/pointer_spot_anchor.png
index a1dcc14..c740755 100644
--- a/core/res/res/drawable-xhdpi/pointer_spot_anchor.png
+++ b/core/res/res/drawable-xhdpi/pointer_spot_anchor.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_spot_hover.png b/core/res/res/drawable-xhdpi/pointer_spot_hover.png
index 668f841..e734427 100644
--- a/core/res/res/drawable-xhdpi/pointer_spot_hover.png
+++ b/core/res/res/drawable-xhdpi/pointer_spot_hover.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_spot_touch.png b/core/res/res/drawable-xhdpi/pointer_spot_touch.png
index 2e922db..4394050 100644
--- a/core/res/res/drawable-xhdpi/pointer_spot_touch.png
+++ b/core/res/res/drawable-xhdpi/pointer_spot_touch.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_text.png b/core/res/res/drawable-xhdpi/pointer_text.png
index 0cebeae..2c0184d 100644
--- a/core/res/res/drawable-xhdpi/pointer_text.png
+++ b/core/res/res/drawable-xhdpi/pointer_text.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_text_large.png b/core/res/res/drawable-xhdpi/pointer_text_large.png
index d9ce209..97db3ec 100644
--- a/core/res/res/drawable-xhdpi/pointer_text_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_text_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
index 5454a8b..a36deb3 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png
index fcfa405..6870e23 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
index a4268e4..c8d6d1f 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png
index 39c5f1a..5bfb771 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
index 95ca954..720df91 100644
--- a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png
index 191f103..82b30d1 100644
--- a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_text.png b/core/res/res/drawable-xhdpi/pointer_vertical_text.png
index a07d091..4ec8eea 100644
--- a/core/res/res/drawable-xhdpi/pointer_vertical_text.png
+++ b/core/res/res/drawable-xhdpi/pointer_vertical_text.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_text_large.png b/core/res/res/drawable-xhdpi/pointer_vertical_text_large.png
index d3f729a..fab7412 100644
--- a/core/res/res/drawable-xhdpi/pointer_vertical_text_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_vertical_text_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_0.png b/core/res/res/drawable-xhdpi/pointer_wait_0.png
index 013aaf6..4c558b2 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_0.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_0.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_1.png b/core/res/res/drawable-xhdpi/pointer_wait_1.png
index 7fb0300..e86769d 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_1.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_1.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_10.png b/core/res/res/drawable-xhdpi/pointer_wait_10.png
index 90efa0a..b0f9d87 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_10.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_10.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_11.png b/core/res/res/drawable-xhdpi/pointer_wait_11.png
index 7c303a2..4b853ab 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_11.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_11.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_12.png b/core/res/res/drawable-xhdpi/pointer_wait_12.png
index 20a1fa8..59bf8a8 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_12.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_12.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_13.png b/core/res/res/drawable-xhdpi/pointer_wait_13.png
index 4585a39..5c618d7 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_13.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_13.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_14.png b/core/res/res/drawable-xhdpi/pointer_wait_14.png
index bdf0e5c..c4fb4ac 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_14.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_14.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_15.png b/core/res/res/drawable-xhdpi/pointer_wait_15.png
index e39c28c..24bdc15 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_15.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_15.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_16.png b/core/res/res/drawable-xhdpi/pointer_wait_16.png
index 7288141..a55f280 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_16.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_16.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_17.png b/core/res/res/drawable-xhdpi/pointer_wait_17.png
index b35da18..fc61645 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_17.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_17.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_18.png b/core/res/res/drawable-xhdpi/pointer_wait_18.png
index 0a61dd6..6f72345 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_18.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_18.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_19.png b/core/res/res/drawable-xhdpi/pointer_wait_19.png
index 1920c77..05d2dbb 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_19.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_19.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_2.png b/core/res/res/drawable-xhdpi/pointer_wait_2.png
index c6b3861..5b018a3 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_2.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_2.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_20.png b/core/res/res/drawable-xhdpi/pointer_wait_20.png
index b27358b..af141d8 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_20.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_20.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_21.png b/core/res/res/drawable-xhdpi/pointer_wait_21.png
index c1f9076..90cd89c 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_21.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_21.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_22.png b/core/res/res/drawable-xhdpi/pointer_wait_22.png
index 6cc0c13..41165cb 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_22.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_22.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_23.png b/core/res/res/drawable-xhdpi/pointer_wait_23.png
index 7df525c..46305db 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_23.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_23.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_24.png b/core/res/res/drawable-xhdpi/pointer_wait_24.png
index 008e273..53ecd9b 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_24.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_24.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_25.png b/core/res/res/drawable-xhdpi/pointer_wait_25.png
index 59f02d9..e413997 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_25.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_25.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_26.png b/core/res/res/drawable-xhdpi/pointer_wait_26.png
index 852b0bf..081ef84 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_26.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_26.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_27.png b/core/res/res/drawable-xhdpi/pointer_wait_27.png
index aba6292..19f29ac 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_27.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_27.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_28.png b/core/res/res/drawable-xhdpi/pointer_wait_28.png
index e59af43..9f9f906 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_28.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_28.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_29.png b/core/res/res/drawable-xhdpi/pointer_wait_29.png
index a195c29..07ff05e 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_29.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_29.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_3.png b/core/res/res/drawable-xhdpi/pointer_wait_3.png
index fb40e6f..e77332e 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_3.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_3.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_30.png b/core/res/res/drawable-xhdpi/pointer_wait_30.png
index 0d19d28..7d31416 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_30.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_30.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_31.png b/core/res/res/drawable-xhdpi/pointer_wait_31.png
index 49066b6..651a33d 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_31.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_31.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_32.png b/core/res/res/drawable-xhdpi/pointer_wait_32.png
index 43f7543..b09b961 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_32.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_32.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_33.png b/core/res/res/drawable-xhdpi/pointer_wait_33.png
index 3664ea8..5b260c4a 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_33.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_33.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_34.png b/core/res/res/drawable-xhdpi/pointer_wait_34.png
index e4c3919..fdcfc64 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_34.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_34.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_35.png b/core/res/res/drawable-xhdpi/pointer_wait_35.png
index e58777a..e3da4b8 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_35.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_35.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_36.png b/core/res/res/drawable-xhdpi/pointer_wait_36.png
new file mode 100644
index 0000000..1a1d7b7
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_36.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_37.png b/core/res/res/drawable-xhdpi/pointer_wait_37.png
new file mode 100644
index 0000000..c53c43c
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_37.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_38.png b/core/res/res/drawable-xhdpi/pointer_wait_38.png
new file mode 100644
index 0000000..16cf47f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_38.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_39.png b/core/res/res/drawable-xhdpi/pointer_wait_39.png
new file mode 100644
index 0000000..2adb861
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_39.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_4.png b/core/res/res/drawable-xhdpi/pointer_wait_4.png
index 552b735..18bd826 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_4.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_4.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_40.png b/core/res/res/drawable-xhdpi/pointer_wait_40.png
new file mode 100644
index 0000000..da7861f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_40.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_41.png b/core/res/res/drawable-xhdpi/pointer_wait_41.png
new file mode 100644
index 0000000..d0ba242
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_41.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_42.png b/core/res/res/drawable-xhdpi/pointer_wait_42.png
new file mode 100644
index 0000000..a90a138
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_42.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_43.png b/core/res/res/drawable-xhdpi/pointer_wait_43.png
new file mode 100644
index 0000000..4256b25
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_43.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_44.png b/core/res/res/drawable-xhdpi/pointer_wait_44.png
new file mode 100644
index 0000000..9e5fe21
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_44.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_45.png b/core/res/res/drawable-xhdpi/pointer_wait_45.png
new file mode 100644
index 0000000..19dd43f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_45.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_46.png b/core/res/res/drawable-xhdpi/pointer_wait_46.png
new file mode 100644
index 0000000..c91001f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_46.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_47.png b/core/res/res/drawable-xhdpi/pointer_wait_47.png
new file mode 100644
index 0000000..d481e42
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_47.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_48.png b/core/res/res/drawable-xhdpi/pointer_wait_48.png
new file mode 100644
index 0000000..299585b
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_48.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_49.png b/core/res/res/drawable-xhdpi/pointer_wait_49.png
new file mode 100644
index 0000000..cd03213
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_49.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_5.png b/core/res/res/drawable-xhdpi/pointer_wait_5.png
index cd2bfa1..3abd5db 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_5.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_5.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_50.png b/core/res/res/drawable-xhdpi/pointer_wait_50.png
new file mode 100644
index 0000000..72122a0
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_50.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_51.png b/core/res/res/drawable-xhdpi/pointer_wait_51.png
new file mode 100644
index 0000000..94398fd
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_51.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_52.png b/core/res/res/drawable-xhdpi/pointer_wait_52.png
new file mode 100644
index 0000000..a4561bd
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_52.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_53.png b/core/res/res/drawable-xhdpi/pointer_wait_53.png
new file mode 100644
index 0000000..a8f417a
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_53.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_54.png b/core/res/res/drawable-xhdpi/pointer_wait_54.png
new file mode 100644
index 0000000..cedab09
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_54.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_55.png b/core/res/res/drawable-xhdpi/pointer_wait_55.png
new file mode 100644
index 0000000..76e1bb6
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_55.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_56.png b/core/res/res/drawable-xhdpi/pointer_wait_56.png
new file mode 100644
index 0000000..c4f1d12
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_56.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_57.png b/core/res/res/drawable-xhdpi/pointer_wait_57.png
new file mode 100644
index 0000000..46ab8e2
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_57.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_58.png b/core/res/res/drawable-xhdpi/pointer_wait_58.png
new file mode 100644
index 0000000..9d36957
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_58.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_59.png b/core/res/res/drawable-xhdpi/pointer_wait_59.png
new file mode 100644
index 0000000..c0f342b
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_59.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_6.png b/core/res/res/drawable-xhdpi/pointer_wait_6.png
index f7c71b9..ad5ecb4 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_6.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_6.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_60.png b/core/res/res/drawable-xhdpi/pointer_wait_60.png
new file mode 100644
index 0000000..202c512
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_60.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_61.png b/core/res/res/drawable-xhdpi/pointer_wait_61.png
new file mode 100644
index 0000000..c48c3f2
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_61.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_62.png b/core/res/res/drawable-xhdpi/pointer_wait_62.png
new file mode 100644
index 0000000..80dee22
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_62.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_63.png b/core/res/res/drawable-xhdpi/pointer_wait_63.png
new file mode 100644
index 0000000..d5b0ee5
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_63.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_64.png b/core/res/res/drawable-xhdpi/pointer_wait_64.png
new file mode 100644
index 0000000..b327a57
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_64.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_65.png b/core/res/res/drawable-xhdpi/pointer_wait_65.png
new file mode 100644
index 0000000..92d832b
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_65.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_66.png b/core/res/res/drawable-xhdpi/pointer_wait_66.png
new file mode 100644
index 0000000..253b8a0
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_66.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_67.png b/core/res/res/drawable-xhdpi/pointer_wait_67.png
new file mode 100644
index 0000000..a8b3e4e
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_67.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_68.png b/core/res/res/drawable-xhdpi/pointer_wait_68.png
new file mode 100644
index 0000000..cfd0343
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_68.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_69.png b/core/res/res/drawable-xhdpi/pointer_wait_69.png
new file mode 100644
index 0000000..cb01ee5
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_69.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_7.png b/core/res/res/drawable-xhdpi/pointer_wait_7.png
index 3fa202e..bca504e 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_7.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_7.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_70.png b/core/res/res/drawable-xhdpi/pointer_wait_70.png
new file mode 100644
index 0000000..f38082f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_70.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_71.png b/core/res/res/drawable-xhdpi/pointer_wait_71.png
new file mode 100644
index 0000000..e8b56db
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_71.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_72.png b/core/res/res/drawable-xhdpi/pointer_wait_72.png
new file mode 100644
index 0000000..9dff9e0
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_72.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_73.png b/core/res/res/drawable-xhdpi/pointer_wait_73.png
new file mode 100644
index 0000000..e989fcc
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_73.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_74.png b/core/res/res/drawable-xhdpi/pointer_wait_74.png
new file mode 100644
index 0000000..953bbd3
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_74.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_75.png b/core/res/res/drawable-xhdpi/pointer_wait_75.png
new file mode 100644
index 0000000..7df2d9e
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_75.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_76.png b/core/res/res/drawable-xhdpi/pointer_wait_76.png
new file mode 100644
index 0000000..b241ef6
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_76.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_77.png b/core/res/res/drawable-xhdpi/pointer_wait_77.png
new file mode 100644
index 0000000..f5018b8
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_77.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_78.png b/core/res/res/drawable-xhdpi/pointer_wait_78.png
new file mode 100644
index 0000000..06209a0
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_78.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_79.png b/core/res/res/drawable-xhdpi/pointer_wait_79.png
new file mode 100644
index 0000000..37887b8
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_79.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_8.png b/core/res/res/drawable-xhdpi/pointer_wait_8.png
index e0e50ae..0844944 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_8.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_8.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_80.png b/core/res/res/drawable-xhdpi/pointer_wait_80.png
new file mode 100644
index 0000000..115ef9d
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_80.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_9.png b/core/res/res/drawable-xhdpi/pointer_wait_9.png
index e3de26f..bf6d022 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_9.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_zoom_in.png b/core/res/res/drawable-xhdpi/pointer_zoom_in.png
index 9c29fcb..d436387 100644
--- a/core/res/res/drawable-xhdpi/pointer_zoom_in.png
+++ b/core/res/res/drawable-xhdpi/pointer_zoom_in.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_zoom_in_large.png b/core/res/res/drawable-xhdpi/pointer_zoom_in_large.png
index beabbd2..e451a33 100644
--- a/core/res/res/drawable-xhdpi/pointer_zoom_in_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_zoom_in_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_zoom_out.png b/core/res/res/drawable-xhdpi/pointer_zoom_out.png
index 710552b..afaaedc 100644
--- a/core/res/res/drawable-xhdpi/pointer_zoom_out.png
+++ b/core/res/res/drawable-xhdpi/pointer_zoom_out.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_zoom_out_large.png b/core/res/res/drawable-xhdpi/pointer_zoom_out_large.png
index b748f07..0ed8327 100644
--- a/core/res/res/drawable-xhdpi/pointer_zoom_out_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_zoom_out_large.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_alias.png b/core/res/res/drawable-xxhdpi/pointer_alias.png
new file mode 100644
index 0000000..c329002
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_alias.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_all_scroll.png b/core/res/res/drawable-xxhdpi/pointer_all_scroll.png
new file mode 100644
index 0000000..808143a
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_arrow.png b/core/res/res/drawable-xxhdpi/pointer_arrow.png
index 4031f16..56dfedc 100644
--- a/core/res/res/drawable-xxhdpi/pointer_arrow.png
+++ b/core/res/res/drawable-xxhdpi/pointer_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_cell.png b/core/res/res/drawable-xxhdpi/pointer_cell.png
new file mode 100644
index 0000000..4180882
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_cell.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_context_menu.png b/core/res/res/drawable-xxhdpi/pointer_context_menu.png
new file mode 100644
index 0000000..6ebfaab
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_copy.png b/core/res/res/drawable-xxhdpi/pointer_copy.png
new file mode 100644
index 0000000..bef4fb4
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_copy.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_crosshair.png b/core/res/res/drawable-xxhdpi/pointer_crosshair.png
new file mode 100644
index 0000000..6cb8b83
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_crosshair.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_grab.png b/core/res/res/drawable-xxhdpi/pointer_grab.png
new file mode 100644
index 0000000..6caa1ba
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_grab.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_grabbing.png b/core/res/res/drawable-xxhdpi/pointer_grabbing.png
new file mode 100644
index 0000000..b52f751
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_grabbing.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_hand.png b/core/res/res/drawable-xxhdpi/pointer_hand.png
new file mode 100644
index 0000000..f3ee077
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_hand.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_help.png b/core/res/res/drawable-xxhdpi/pointer_help.png
new file mode 100644
index 0000000..96b2a71
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png
new file mode 100644
index 0000000..677ccad
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_nodrop.png b/core/res/res/drawable-xxhdpi/pointer_nodrop.png
new file mode 100644
index 0000000..ef54301
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_nodrop.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_spot_anchor.png b/core/res/res/drawable-xxhdpi/pointer_spot_anchor.png
new file mode 100644
index 0000000..5b46ce5
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_spot_anchor.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_spot_hover.png b/core/res/res/drawable-xxhdpi/pointer_spot_hover.png
new file mode 100644
index 0000000..6c1ab15
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_spot_hover.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_spot_touch.png b/core/res/res/drawable-xxhdpi/pointer_spot_touch.png
new file mode 100644
index 0000000..ab69fd8
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_spot_touch.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_text.png b/core/res/res/drawable-xxhdpi/pointer_text.png
new file mode 100644
index 0000000..0145886
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_text.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png
new file mode 100644
index 0000000..e01aa64
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png
new file mode 100644
index 0000000..e947e0e
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png
new file mode 100644
index 0000000..c867247
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_vertical_text.png b/core/res/res/drawable-xxhdpi/pointer_vertical_text.png
new file mode 100644
index 0000000..77ff389
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_vertical_text.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_zoom_in.png b/core/res/res/drawable-xxhdpi/pointer_zoom_in.png
new file mode 100644
index 0000000..7852abe
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_zoom_in.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_zoom_out.png b/core/res/res/drawable-xxhdpi/pointer_zoom_out.png
new file mode 100644
index 0000000..83c621e
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_zoom_out.png
Binary files differ
diff --git a/core/res/res/drawable/pointer_alias_icon.xml b/core/res/res/drawable/pointer_alias_icon.xml
index 8ba9301..4c701c0 100644
--- a/core/res/res/drawable/pointer_alias_icon.xml
+++ b/core/res/res/drawable/pointer_alias_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_alias"
-    android:hotSpotX="8dp"
-    android:hotSpotY="6dp" />
+    android:hotSpotX="11.5dp"
+    android:hotSpotY="11.5dp" />
diff --git a/core/res/res/drawable/pointer_alias_large_icon.xml b/core/res/res/drawable/pointer_alias_large_icon.xml
index 149f58c..8dca8a5 100644
--- a/core/res/res/drawable/pointer_alias_large_icon.xml
+++ b/core/res/res/drawable/pointer_alias_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_alias_large"
-    android:hotSpotX="19dp"
-    android:hotSpotY="11dp" />
+    android:hotSpotX="28.75dp"
+    android:hotSpotY="28.75dp" />
diff --git a/core/res/res/drawable/pointer_all_scroll_icon.xml b/core/res/res/drawable/pointer_all_scroll_icon.xml
index e946948..a0cf318 100644
--- a/core/res/res/drawable/pointer_all_scroll_icon.xml
+++ b/core/res/res/drawable/pointer_all_scroll_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_all_scroll"
-    android:hotSpotX="11dp"
-    android:hotSpotY="11dp" />
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_all_scroll_large_icon.xml b/core/res/res/drawable/pointer_all_scroll_large_icon.xml
index c580f76..1fe7dad 100644
--- a/core/res/res/drawable/pointer_all_scroll_large_icon.xml
+++ b/core/res/res/drawable/pointer_all_scroll_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_all_scroll_large"
-    android:hotSpotX="32dp"
-    android:hotSpotY="31dp" />
+    android:hotSpotX="30dp"
+    android:hotSpotY="30dp" />
diff --git a/core/res/res/drawable/pointer_arrow_icon.xml b/core/res/res/drawable/pointer_arrow_icon.xml
index 72af0c1..61eb1cd 100644
--- a/core/res/res/drawable/pointer_arrow_icon.xml
+++ b/core/res/res/drawable/pointer_arrow_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_arrow"
-    android:hotSpotX="5dp"
-    android:hotSpotY="5dp" />
+    android:hotSpotX="4.5dp"
+    android:hotSpotY="3.5dp" />
diff --git a/core/res/res/drawable/pointer_arrow_large_icon.xml b/core/res/res/drawable/pointer_arrow_large_icon.xml
index 22c7bfe..0f2997b 100644
--- a/core/res/res/drawable/pointer_arrow_large_icon.xml
+++ b/core/res/res/drawable/pointer_arrow_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_arrow_large"
-    android:hotSpotX="10dp"
-    android:hotSpotY="10dp" />
+    android:hotSpotX="13.5dp"
+    android:hotSpotY="10.5dp" />
diff --git a/core/res/res/drawable/pointer_cell_icon.xml b/core/res/res/drawable/pointer_cell_icon.xml
index da1e320..35a61c2 100644
--- a/core/res/res/drawable/pointer_cell_icon.xml
+++ b/core/res/res/drawable/pointer_cell_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_cell"
-    android:hotSpotX="11dp"
-    android:hotSpotY="11dp" />
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_context_menu_icon.xml b/core/res/res/drawable/pointer_context_menu_icon.xml
index 330b627..badc736 100644
--- a/core/res/res/drawable/pointer_context_menu_icon.xml
+++ b/core/res/res/drawable/pointer_context_menu_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_context_menu"
-    android:hotSpotX="4dp"
-    android:hotSpotY="4dp" />
+    android:hotSpotX="4.5dp"
+    android:hotSpotY="3.5dp" />
diff --git a/core/res/res/drawable/pointer_context_menu_large_icon.xml b/core/res/res/drawable/pointer_context_menu_large_icon.xml
index c57e615..e07e5b6 100644
--- a/core/res/res/drawable/pointer_context_menu_large_icon.xml
+++ b/core/res/res/drawable/pointer_context_menu_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_context_menu_large"
-    android:hotSpotX="11dp"
-    android:hotSpotY="11dp" />
+    android:hotSpotX="13.5dp"
+    android:hotSpotY="10.5dp" />
diff --git a/core/res/res/drawable/pointer_copy_icon.xml b/core/res/res/drawable/pointer_copy_icon.xml
index e299db5..da32939 100644
--- a/core/res/res/drawable/pointer_copy_icon.xml
+++ b/core/res/res/drawable/pointer_copy_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_copy"
-    android:hotSpotX="9dp"
-    android:hotSpotY="9dp" />
+    android:hotSpotX="7.5dp"
+    android:hotSpotY="7.5dp" />
diff --git a/core/res/res/drawable/pointer_copy_large_icon.xml b/core/res/res/drawable/pointer_copy_large_icon.xml
index 4ee3f18..55d47b4 100644
--- a/core/res/res/drawable/pointer_copy_large_icon.xml
+++ b/core/res/res/drawable/pointer_copy_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_copy_large"
-    android:hotSpotX="10dp"
-    android:hotSpotY="10dp" />
+    android:hotSpotX="22.5dp"
+    android:hotSpotY="22.5dp" />
diff --git a/core/res/res/drawable/pointer_crosshair_large_icon.xml b/core/res/res/drawable/pointer_crosshair_large_icon.xml
index 6a71b7b..1073fff 100644
--- a/core/res/res/drawable/pointer_crosshair_large_icon.xml
+++ b/core/res/res/drawable/pointer_crosshair_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_crosshair_large"
-    android:hotSpotX="31dp"
+    android:hotSpotX="30dp"
     android:hotSpotY="30dp" />
diff --git a/core/res/res/drawable/pointer_grab_icon.xml b/core/res/res/drawable/pointer_grab_icon.xml
index d437b3a..b3d4e78 100644
--- a/core/res/res/drawable/pointer_grab_icon.xml
+++ b/core/res/res/drawable/pointer_grab_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_grab"
-    android:hotSpotX="8dp"
-    android:hotSpotY="5dp" />
+    android:hotSpotX="13.5dp"
+    android:hotSpotY="13.5dp" />
diff --git a/core/res/res/drawable/pointer_grab_large_icon.xml b/core/res/res/drawable/pointer_grab_large_icon.xml
index f2df1cb..343c7d2 100644
--- a/core/res/res/drawable/pointer_grab_large_icon.xml
+++ b/core/res/res/drawable/pointer_grab_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_grab_large"
-    android:hotSpotX="21dp"
-    android:hotSpotY="11dp" />
+    android:hotSpotX="33.75dp"
+    android:hotSpotY="33.75dp" />
diff --git a/core/res/res/drawable/pointer_grabbing_icon.xml b/core/res/res/drawable/pointer_grabbing_icon.xml
index 38f4c3a..93818d7 100644
--- a/core/res/res/drawable/pointer_grabbing_icon.xml
+++ b/core/res/res/drawable/pointer_grabbing_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_grabbing"
-    android:hotSpotX="9dp"
-    android:hotSpotY="9dp" />
+    android:hotSpotX="8.5dp"
+    android:hotSpotY="7.5dp" />
diff --git a/core/res/res/drawable/pointer_grabbing_large_icon.xml b/core/res/res/drawable/pointer_grabbing_large_icon.xml
index e4042bf..ac16265 100644
--- a/core/res/res/drawable/pointer_grabbing_large_icon.xml
+++ b/core/res/res/drawable/pointer_grabbing_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_grabbing_large"
-    android:hotSpotX="20dp"
-    android:hotSpotY="12dp" />
+    android:hotSpotX="25.5dp"
+    android:hotSpotY="22.5dp" />
diff --git a/core/res/res/drawable/pointer_hand_icon.xml b/core/res/res/drawable/pointer_hand_icon.xml
index 3d90b88..3f9d1a6 100644
--- a/core/res/res/drawable/pointer_hand_icon.xml
+++ b/core/res/res/drawable/pointer_hand_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_hand"
-    android:hotSpotX="9dp"
-    android:hotSpotY="4dp" />
+    android:hotSpotX="9.5dp"
+    android:hotSpotY="1.5dp" />
diff --git a/core/res/res/drawable/pointer_hand_large_icon.xml b/core/res/res/drawable/pointer_hand_large_icon.xml
index e34422a..cd49762 100644
--- a/core/res/res/drawable/pointer_hand_large_icon.xml
+++ b/core/res/res/drawable/pointer_hand_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_hand_large"
-    android:hotSpotX="25dp"
-    android:hotSpotY="7dp" />
+    android:hotSpotX="23.75dp"
+    android:hotSpotY="3.75dp" />
diff --git a/core/res/res/drawable/pointer_help_icon.xml b/core/res/res/drawable/pointer_help_icon.xml
index 49ae554..0a4bdb0 100644
--- a/core/res/res/drawable/pointer_help_icon.xml
+++ b/core/res/res/drawable/pointer_help_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_help"
-    android:hotSpotX="4dp"
-    android:hotSpotY="4dp" />
+    android:hotSpotX="4.5dp"
+    android:hotSpotY="3.5dp" />
diff --git a/core/res/res/drawable/pointer_help_large_icon.xml b/core/res/res/drawable/pointer_help_large_icon.xml
index 4c60a55..43a1261 100644
--- a/core/res/res/drawable/pointer_help_large_icon.xml
+++ b/core/res/res/drawable/pointer_help_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_help_large"
-    android:hotSpotX="10dp"
-    android:hotSpotY="11dp" />
+    android:hotSpotX="13.5dp"
+    android:hotSpotY="10.5dp" />
diff --git a/core/res/res/drawable/pointer_horizontal_double_arrow_icon.xml b/core/res/res/drawable/pointer_horizontal_double_arrow_icon.xml
index 93b3fe5..6ddcc42 100644
--- a/core/res/res/drawable/pointer_horizontal_double_arrow_icon.xml
+++ b/core/res/res/drawable/pointer_horizontal_double_arrow_icon.xml
@@ -2,4 +2,4 @@
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_horizontal_double_arrow"
     android:hotSpotX="12dp"
-    android:hotSpotY="11dp" />
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_horizontal_double_arrow_large_icon.xml b/core/res/res/drawable/pointer_horizontal_double_arrow_large_icon.xml
index a2039e6..854be26 100644
--- a/core/res/res/drawable/pointer_horizontal_double_arrow_large_icon.xml
+++ b/core/res/res/drawable/pointer_horizontal_double_arrow_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_horizontal_double_arrow_large"
-    android:hotSpotX="35dp"
-    android:hotSpotY="29dp" />
+    android:hotSpotX="30dp"
+    android:hotSpotY="30dp" />
diff --git a/core/res/res/drawable/pointer_nodrop_icon.xml b/core/res/res/drawable/pointer_nodrop_icon.xml
index 955b40f..4dffd23 100644
--- a/core/res/res/drawable/pointer_nodrop_icon.xml
+++ b/core/res/res/drawable/pointer_nodrop_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_nodrop"
-    android:hotSpotX="9dp"
-    android:hotSpotY="9dp" />
+    android:hotSpotX="7.5dp"
+    android:hotSpotY="7.5dp" />
diff --git a/core/res/res/drawable/pointer_nodrop_large_icon.xml b/core/res/res/drawable/pointer_nodrop_large_icon.xml
index cde2e41..602c744 100644
--- a/core/res/res/drawable/pointer_nodrop_large_icon.xml
+++ b/core/res/res/drawable/pointer_nodrop_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_nodrop_large"
-    android:hotSpotX="10dp"
-    android:hotSpotY="10dp" />
+    android:hotSpotX="22.5dp"
+    android:hotSpotY="22.5dp" />
diff --git a/core/res/res/drawable/pointer_spot_anchor_icon.xml b/core/res/res/drawable/pointer_spot_anchor_icon.xml
index 73c0c11..b7d000f 100644
--- a/core/res/res/drawable/pointer_spot_anchor_icon.xml
+++ b/core/res/res/drawable/pointer_spot_anchor_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_spot_anchor"
-    android:hotSpotX="22dp"
-    android:hotSpotY="22dp" />
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_spot_hover_icon.xml b/core/res/res/drawable/pointer_spot_hover_icon.xml
index 1d7440b..0d641b2 100644
--- a/core/res/res/drawable/pointer_spot_hover_icon.xml
+++ b/core/res/res/drawable/pointer_spot_hover_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_spot_hover"
-    android:hotSpotX="22dp"
-    android:hotSpotY="22dp" />
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_spot_touch_icon.xml b/core/res/res/drawable/pointer_spot_touch_icon.xml
index f4f0639..b1c8c0b 100644
--- a/core/res/res/drawable/pointer_spot_touch_icon.xml
+++ b/core/res/res/drawable/pointer_spot_touch_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_spot_touch"
-    android:hotSpotX="16dp"
-    android:hotSpotY="16dp" />
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_text_icon.xml b/core/res/res/drawable/pointer_text_icon.xml
index d948c89..1daba65 100644
--- a/core/res/res/drawable/pointer_text_icon.xml
+++ b/core/res/res/drawable/pointer_text_icon.xml
@@ -2,4 +2,4 @@
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_text"
     android:hotSpotX="12dp"
-    android:hotSpotY="12dp" />
+    android:hotSpotY="11dp" />
diff --git a/core/res/res/drawable/pointer_text_large_icon.xml b/core/res/res/drawable/pointer_text_large_icon.xml
index 24d35b0..5b32ea34 100644
--- a/core/res/res/drawable/pointer_text_large_icon.xml
+++ b/core/res/res/drawable/pointer_text_large_icon.xml
@@ -2,4 +2,4 @@
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_text_large"
     android:hotSpotX="30dp"
-    android:hotSpotY="32dp" />
+    android:hotSpotY="27.5dp" />
diff --git a/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_icon.xml b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_icon.xml
index de5efe2..1598766e 100644
--- a/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_icon.xml
+++ b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_top_left_diagonal_double_arrow"
-    android:hotSpotX="11dp"
-    android:hotSpotY="11dp" />
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_large_icon.xml b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_large_icon.xml
index 270ccc9..5e0d37b 100644
--- a/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_large_icon.xml
+++ b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_top_left_diagonal_double_arrow_large"
-    android:hotSpotX="32dp"
+    android:hotSpotX="30dp"
     android:hotSpotY="30dp" />
diff --git a/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_icon.xml b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_icon.xml
index e87b526..83324ef9 100644
--- a/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_icon.xml
+++ b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_icon.xml
@@ -2,4 +2,4 @@
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_top_right_diagonal_double_arrow"
     android:hotSpotX="12dp"
-    android:hotSpotY="11dp" />
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_large_icon.xml b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_large_icon.xml
index e350a43..6ed5a0b 100644
--- a/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_large_icon.xml
+++ b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_top_right_diagonal_double_arrow_large"
-    android:hotSpotX="32dp"
-    android:hotSpotY="31dp" />
+    android:hotSpotX="30dp"
+    android:hotSpotY="30dp" />
diff --git a/core/res/res/drawable/pointer_vertical_double_arrow_icon.xml b/core/res/res/drawable/pointer_vertical_double_arrow_icon.xml
index 5759079..c746fd8 100644
--- a/core/res/res/drawable/pointer_vertical_double_arrow_icon.xml
+++ b/core/res/res/drawable/pointer_vertical_double_arrow_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_vertical_double_arrow"
-    android:hotSpotX="11dp"
+    android:hotSpotX="12dp"
     android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_vertical_double_arrow_large_icon.xml b/core/res/res/drawable/pointer_vertical_double_arrow_large_icon.xml
index 65728ad..190d181 100644
--- a/core/res/res/drawable/pointer_vertical_double_arrow_large_icon.xml
+++ b/core/res/res/drawable/pointer_vertical_double_arrow_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_vertical_double_arrow_large"
-    android:hotSpotX="29dp"
-    android:hotSpotY="32dp" />
+    android:hotSpotX="30dp"
+    android:hotSpotY="30dp" />
diff --git a/core/res/res/drawable/pointer_vertical_text_icon.xml b/core/res/res/drawable/pointer_vertical_text_icon.xml
index 3b48dc8..275b979 100644
--- a/core/res/res/drawable/pointer_vertical_text_icon.xml
+++ b/core/res/res/drawable/pointer_vertical_text_icon.xml
@@ -2,4 +2,4 @@
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_vertical_text"
     android:hotSpotX="12dp"
-    android:hotSpotY="11dp" />
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_vertical_text_large_icon.xml b/core/res/res/drawable/pointer_vertical_text_large_icon.xml
index 48211cb..a1b5dc2 100644
--- a/core/res/res/drawable/pointer_vertical_text_large_icon.xml
+++ b/core/res/res/drawable/pointer_vertical_text_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_vertical_text_large"
-    android:hotSpotX="32dp"
+    android:hotSpotX="30dp"
     android:hotSpotY="30dp" />
diff --git a/core/res/res/drawable/pointer_wait.xml b/core/res/res/drawable/pointer_wait.xml
index 8955ce8..c769be5 100644
--- a/core/res/res/drawable/pointer_wait.xml
+++ b/core/res/res/drawable/pointer_wait.xml
@@ -1,38 +1,84 @@
 <?xml version="1.0" encoding="utf-8"?>
 <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
-  <item android:drawable="@drawable/pointer_wait_1" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_2" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_3" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_4" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_5" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_6" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_7" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_8" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_9" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_10" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_11" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_12" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_13" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_14" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_15" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_16" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_17" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_18" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_19" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_20" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_21" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_22" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_23" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_24" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_25" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_26" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_27" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_28" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_29" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_30" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_31" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_32" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_33" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_34" android:duration="25"/>
-  <item android:drawable="@drawable/pointer_wait_35" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_0" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_1" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_2" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_3" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_4" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_5" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_6" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_7" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_8" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_9" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_10" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_11" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_12" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_13" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_14" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_15" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_16" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_17" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_18" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_19" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_20" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_21" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_22" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_23" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_24" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_25" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_26" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_27" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_28" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_29" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_30" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_31" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_32" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_33" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_34" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_35" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_36" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_37" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_38" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_39" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_40" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_41" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_42" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_43" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_44" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_45" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_46" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_47" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_48" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_49" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_50" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_51" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_52" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_53" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_54" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_55" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_56" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_57" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_58" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_59" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_60" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_61" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_62" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_63" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_64" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_65" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_66" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_67" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_68" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_69" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_70" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_71" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_72" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_73" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_74" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_75" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_76" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_77" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_78" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_79" android:duration="16"/>
+  <item android:drawable="@drawable/pointer_wait_80" android:duration="16"/>
 </animation-list>
diff --git a/core/res/res/drawable/pointer_wait_icon.xml b/core/res/res/drawable/pointer_wait_icon.xml
index d9b03b0..e52c4e0 100644
--- a/core/res/res/drawable/pointer_wait_icon.xml
+++ b/core/res/res/drawable/pointer_wait_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_wait"
-    android:hotSpotX="7dp"
-    android:hotSpotY="7dp" />
+    android:hotSpotX="8dp"
+    android:hotSpotY="8dp" />
diff --git a/core/res/res/drawable/pointer_zoom_in_icon.xml b/core/res/res/drawable/pointer_zoom_in_icon.xml
index e2dcb49..322fab8 100644
--- a/core/res/res/drawable/pointer_zoom_in_icon.xml
+++ b/core/res/res/drawable/pointer_zoom_in_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_zoom_in"
-    android:hotSpotX="10dp"
-    android:hotSpotY="10dp" />
+    android:hotSpotX="10.5dp"
+    android:hotSpotY="9.5dp" />
diff --git a/core/res/res/drawable/pointer_zoom_in_large_icon.xml b/core/res/res/drawable/pointer_zoom_in_large_icon.xml
index 3eb89f56..4505892 100644
--- a/core/res/res/drawable/pointer_zoom_in_large_icon.xml
+++ b/core/res/res/drawable/pointer_zoom_in_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_zoom_in_large"
-    android:hotSpotX="25dp"
-    android:hotSpotY="26dp" />
+    android:hotSpotX="26.25dp"
+    android:hotSpotY="23.75dp" />
diff --git a/core/res/res/drawable/pointer_zoom_out_icon.xml b/core/res/res/drawable/pointer_zoom_out_icon.xml
index b805df3..bfb5aa6 100644
--- a/core/res/res/drawable/pointer_zoom_out_icon.xml
+++ b/core/res/res/drawable/pointer_zoom_out_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_zoom_out"
-    android:hotSpotX="10dp"
-    android:hotSpotY="10dp" />
+    android:hotSpotX="10.5dp"
+    android:hotSpotY="9.5dp" />
diff --git a/core/res/res/drawable/pointer_zoom_out_large_icon.xml b/core/res/res/drawable/pointer_zoom_out_large_icon.xml
index df6412e..90057e6 100644
--- a/core/res/res/drawable/pointer_zoom_out_large_icon.xml
+++ b/core/res/res/drawable/pointer_zoom_out_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_zoom_out_large"
-    android:hotSpotX="26dp"
-    android:hotSpotY="26dp" />
+    android:hotSpotX="26.25dp"
+    android:hotSpotY="23.75dp" />
diff --git a/core/res/res/layout-watch/app_anr_dialog.xml b/core/res/res/layout-watch/app_anr_dialog.xml
new file mode 100644
index 0000000..f9605af
--- /dev/null
+++ b/core/res/res/layout-watch/app_anr_dialog.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:showDividers="middle"
+    android:divider="@drawable/global_action_item_divider">
+    <Button
+        android:id="@+id/aerr_close"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_close_app"
+        android:drawableStart="@drawable/ic_close"
+        style="@style/aerr_list_item"/>
+    <Button
+        android:id="@+id/aerr_wait"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_wait"
+        android:drawableStart="@drawable/ic_schedule"
+        style="@style/aerr_list_item"/>
+    <Button
+        android:id="@+id/aerr_report"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_report"
+        android:drawableStart="@drawable/ic_feedback"
+        style="@style/aerr_list_item"/>
+</LinearLayout>
diff --git a/core/res/res/layout-watch/app_error_dialog.xml b/core/res/res/layout-watch/app_error_dialog.xml
new file mode 100644
index 0000000..8857b5f
--- /dev/null
+++ b/core/res/res/layout-watch/app_error_dialog.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:showDividers="middle"
+    android:divider="@drawable/global_action_item_divider">
+    <Button
+        android:id="@+id/aerr_restart"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_restart"
+        android:drawableStart="@drawable/ic_refresh"
+        style="@style/aerr_list_item" />
+    <Button
+        android:id="@+id/aerr_app_info"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/app_info"
+        android:drawableStart="@drawable/ic_info_outline_24"
+        style="@style/aerr_list_item" />
+    <Button
+        android:id="@+id/aerr_close"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_close_app"
+        android:drawableStart="@drawable/ic_close"
+        style="@style/aerr_list_item" />
+    <Button
+        android:id="@+id/aerr_report"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_report"
+        android:drawableStart="@drawable/ic_feedback"
+        style="@style/aerr_list_item" />
+    <Button
+        android:id="@+id/aerr_mute"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_mute"
+        android:drawableStart="@drawable/ic_eject_24dp"
+        style="@style/aerr_list_item" />
+</LinearLayout>
diff --git a/core/res/res/layout-watch/watch_base_error_dialog.xml b/core/res/res/layout-watch/watch_base_error_dialog.xml
new file mode 100644
index 0000000..0f3fb42
--- /dev/null
+++ b/core/res/res/layout-watch/watch_base_error_dialog.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<com.android.internal.widget.WatchListDecorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/parentPanel"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <ScrollView
+        android:id="@+id/scrollView"
+        android:fillViewport="true"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingLeft="?dialogPreferredPadding"
+            android:paddingRight="?dialogPreferredPadding"
+            android:paddingTop="@dimen/base_error_dialog_top_padding"
+            android:paddingBottom="@dimen/base_error_dialog_bottom_padding"
+            android:orientation="vertical" >
+            <!-- Top Panel -->
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:id="@+id/topPanel">
+                <include android:id="@+id/title_template"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    layout="@layout/watch_base_error_dialog_title"/>
+            </FrameLayout>
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="16dp">
+            </FrameLayout>
+            <!-- Content Panel -->
+            <FrameLayout
+                android:id="@+id/contentPanel"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:clipToPadding="false">
+                <TextView
+                    android:id="@+id/message"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="center_horizontal|top"
+                    android:textAppearance="@style/TextAppearance.DeviceDefault.Body1"
+                    android:paddingTop="8dip"
+                    android:paddingBottom="8dip"/>
+            </FrameLayout>
+            <!-- Custom Panel, to replace content panel if needed -->
+            <FrameLayout
+                android:id="@+id/customPanel"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:minHeight="64dp">
+                <FrameLayout
+                    android:id="@+android:id/custom"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"/>
+            </FrameLayout>
+
+            <!-- Button Panel -->
+            <FrameLayout
+                android:id="@+id/buttonPanel"
+                android:layout_weight="1"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="bottom"
+                    android:orientation="vertical"
+                    style="?android:attr/buttonBarStyle"
+                    android:measureWithLargestChild="true">
+                    <Button
+                        android:id="@+id/button1"
+                        android:layout_gravity="start"
+                        android:layout_weight="1"
+                        style="?android:attr/buttonBarButtonStyle"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"/>
+                    <Button
+                        android:id="@+id/button3"
+                        android:layout_gravity="start"
+                        android:layout_weight="1"
+                        style="?android:attr/buttonBarButtonStyle"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"/>
+                    <Button
+                        android:id="@+id/button2"
+                        android:layout_gravity="start"
+                        android:layout_weight="1"
+                        style="?android:attr/buttonBarButtonStyle"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"/>
+                </LinearLayout>
+            </FrameLayout>
+        </LinearLayout>
+    </ScrollView>
+</com.android.internal.widget.WatchListDecorLayout>
diff --git a/core/res/res/layout-watch/watch_base_error_dialog_title.xml b/core/res/res/layout-watch/watch_base_error_dialog_title.xml
new file mode 100644
index 0000000..aa14c08
--- /dev/null
+++ b/core/res/res/layout-watch/watch_base_error_dialog_title.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingLeft="@dimen/base_error_dialog_contents_padding"
+    android:paddingRight="@dimen/base_error_dialog_contents_padding"
+    android:orientation="vertical"
+    android:gravity="top|center_horizontal">
+    <FrameLayout
+        android:adjustViewBounds="true"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <ImageView
+            android:id="@+id/icon"
+            android:adjustViewBounds="true"
+            android:maxHeight="24dp"
+            android:maxWidth="24dp"
+            android:layout_marginTop="@dimen/screen_percentage_10"
+            android:layout_gravity="center_horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@null"/>
+    </FrameLayout>
+    <TextView
+        android:id="@+id/alertTitle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="38dp"
+        android:textAppearance="@style/TextAppearance.Watch.BaseErrorDialog.Title"
+        android:maxLines="3"
+        android:gravity="center_horizontal|top"/>
+</LinearLayout>
diff --git a/core/res/res/values-w180dp-notround-watch/dimens.xml b/core/res/res/values-w180dp-notround-watch/dimens.xml
new file mode 100644
index 0000000..5887661
--- /dev/null
+++ b/core/res/res/values-w180dp-notround-watch/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<resources>
+    <!-- 14.4% of display size -->
+    <dimen name="base_error_dialog_top_padding">26dp</dimen>
+    <!-- 2.8% of display size -->
+    <dimen name="base_error_dialog_padding">5dp</dimen>
+    <!-- 35.56% of display size -->
+    <dimen name="base_error_dialog_bottom_padding">64dp</dimen>
+</resources>
diff --git a/core/res/res/values-w192dp-round-watch/dimens.xml b/core/res/res/values-w192dp-round-watch/dimens.xml
new file mode 100644
index 0000000..5aed20e
--- /dev/null
+++ b/core/res/res/values-w192dp-round-watch/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<resources>
+    <!-- 16.7% of display size -->
+    <dimen name="base_error_dialog_top_padding">32dp</dimen>
+    <!-- 5.2% of display size -->
+    <dimen name="base_error_dialog_padding">10dp</dimen>
+    <!-- 20.83% of display size -->
+    <dimen name="base_error_dialog_bottom_padding">40dp</dimen>
+</resources>
diff --git a/core/res/res/values-w213dp-round-watch/dimens.xml b/core/res/res/values-w213dp-round-watch/dimens.xml
new file mode 100644
index 0000000..27fff75
--- /dev/null
+++ b/core/res/res/values-w213dp-round-watch/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<resources>
+    <!-- 16.7% of display size -->
+    <dimen name="base_error_dialog_top_padding">36dp</dimen>
+    <!-- 5.2% of display size -->
+    <dimen name="base_error_dialog_padding">11dp</dimen>
+    <!-- 36.46% of display size -->
+    <dimen name="base_error_dialog_bottom_padding">78dp</dimen>
+</resources>
diff --git a/core/res/res/values-watch/dimens.xml b/core/res/res/values-watch/dimens.xml
index 5472316..c7caa39 100644
--- a/core/res/res/values-watch/dimens.xml
+++ b/core/res/res/values-watch/dimens.xml
@@ -20,4 +20,9 @@
     <dimen name="alert_dialog_button_bar_height">0dp</dimen>
 
     <dimen name="toast_y_offset">0dip</dimen>
+
+    <!-- AppErrorDialog's list item height -->
+    <dimen name="aerr_list_item_height">52dp</dimen>
+    <!-- Padding for contents in a view of BaseErrorDialog such as a title and buttons -->
+    <dimen name="base_error_dialog_contents_padding">14dp</dimen>
 </resources>
diff --git a/core/res/res/values-watch/styles.xml b/core/res/res/values-watch/styles.xml
index 3172f73..6e84f39 100644
--- a/core/res/res/values-watch/styles.xml
+++ b/core/res/res/values-watch/styles.xml
@@ -19,4 +19,43 @@
         <item name="fontFamily">sans-serif-regular</item>
         <item name="textSize">13sp</item>
     </style>
+
+    <!-- @hide -->
+    <style name="TextAppearance.Watch"/>
+
+    <!-- @hide -->
+    <style name="TextAppearance.Watch.BaseErrorDialog">
+        <item name="fontFamily">google-sans-text-medium</item>
+        <item name="textColor">@android:color/white</item>
+        <item name="textAllCaps">false</item>
+    </style>
+
+    <!-- @hide -->
+    <style name="TextAppearance.Watch.BaseErrorDialog.Title">
+        <item name="textSize">16sp</item>
+        <item name="letterSpacing">0.024</item>
+    </style>
+
+    <!-- @hide -->
+    <style name="TextAppearance.Watch.AppErrorDialog"
+           parent="TextAppearance.Watch.BaseErrorDialog"/>
+
+    <!-- @hide -->
+    <style name="TextAppearance.Watch.AppErrorDialog.Item">
+        <item name="textSize">15sp</item>
+        <item name="letterSpacing">0.01</item>
+    </style>
+
+    <!-- @hide -->
+    <style name="aerr_list_item">
+        <item name="minHeight">@dimen/aerr_list_item_height</item>
+        <item name="textAppearance">@style/TextAppearance.Watch.AppErrorDialog.Item</item>
+        <item name="gravity">center_vertical</item>
+        <item name="paddingStart">@dimen/base_error_dialog_contents_padding</item>
+        <item name="paddingEnd">@dimen/base_error_dialog_contents_padding</item>
+        <item name="background">@drawable/global_actions_item_grey_background</item>
+        <item name="drawablePadding">6dp</item>
+        <item name="drawableTint">@android:color/white</item>
+        <item name="drawableTintMode">src_atop</item>
+    </style>
 </resources>
diff --git a/core/res/res/values-watch/styles_device_default.xml b/core/res/res/values-watch/styles_device_default.xml
index e2261af..8a2ce5d 100644
--- a/core/res/res/values-watch/styles_device_default.xml
+++ b/core/res/res/values-watch/styles_device_default.xml
@@ -34,4 +34,7 @@
         <item name="android:textSize">16sp</item>
         <item name="android:fontFamily">google-sans-medium</item>
     </style>
+    <style name="BaseErrorDialog.DeviceDefault" parent="AlertDialog.DeviceDefault">
+        <item name="layout">@layout/watch_base_error_dialog</item>
+    </style>
 </resources>
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index 1db006f..c4c1ed9 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -427,6 +427,8 @@
 
     <!-- Theme for the dialog shown when an app crashes or ANRs. Override to make it dark. -->
     <style name="Theme.DeviceDefault.Dialog.AppError" parent="Theme.DeviceDefault.Dialog.Alert">
+        <item name="alertDialogStyle">@style/BaseErrorDialog.DeviceDefault</item>
+        <item name="dialogPreferredPadding">@dimen/base_error_dialog_padding</item>
         <item name="windowContentTransitions">false</item>
         <item name="windowActivityTransitions">false</item>
         <item name="windowCloseOnTouchOutside">false</item>
diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
index 180a312..bf8c7c6 100644
--- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
+++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
@@ -108,8 +108,10 @@
             Paths.get("/data/misc/wmtrace/ime_trace_managerservice.winscope"),
             Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"),
             Paths.get("/data/misc/wmtrace/wm_trace.winscope"),
+            Paths.get("/data/misc/wmtrace/wm_log.winscope"),
             Paths.get("/data/misc/wmtrace/layers_trace.winscope"),
             Paths.get("/data/misc/wmtrace/transactions_trace.winscope"),
+            Paths.get("/data/misc/wmtrace/transition_trace.winscope"),
     };
     private static final Path[] UI_TRACES_GENERATED_DURING_BUGREPORT = {
             Paths.get("/data/misc/wmtrace/layers_trace_from_transactions.winscope"),
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index bf8ca8b..4cccf8e 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -76,7 +76,8 @@
     <uses-permission android:name="android.permission.USE_CREDENTIALS" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.WRITE_CONTACTS" />
-    <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
+    <uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" />
+    <uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTest.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
index 3b8b8c7..4a12bb3 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTest.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -142,6 +142,31 @@
     }
 
     @Test
+    public void testGetHdrCapabilities_getSupportedHdrTypes_returns_mode_specific_hdr_types() {
+        setDisplayInfoPortrait(mDisplayInfo);
+        float[] alternativeRefreshRates = new float[0];
+        int[] hdrTypesWithDv = new int[] {1, 2, 3, 4};
+        Display.Mode modeWithDv = new Display.Mode(/* modeId= */ 0, 0, 0, 0f,
+                alternativeRefreshRates, hdrTypesWithDv);
+
+        int[] hdrTypesWithoutDv = new int[]{2, 3, 4};
+        Display.Mode modeWithoutDv = new Display.Mode(/* modeId= */ 1, 0, 0, 0f,
+                alternativeRefreshRates, hdrTypesWithoutDv);
+
+        mDisplayInfo.supportedModes = new Display.Mode[] {modeWithoutDv, modeWithDv};
+        mDisplayInfo.hdrCapabilities = new Display.HdrCapabilities(hdrTypesWithDv, 0, 0, 0);
+
+        final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+
+        mDisplayInfo.modeId = 0;
+        assertArrayEquals(hdrTypesWithDv, display.getHdrCapabilities().getSupportedHdrTypes());
+
+        mDisplayInfo.modeId = 1;
+        assertArrayEquals(hdrTypesWithoutDv, display.getHdrCapabilities().getSupportedHdrTypes());
+    }
+
+    @Test
     public void testConstructor_defaultDisplayAdjustments_matchesDisplayInfo() {
         setDisplayInfoPortrait(mDisplayInfo);
         final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
diff --git a/graphics/java/android/graphics/HardwareBufferRenderer.java b/graphics/java/android/graphics/HardwareBufferRenderer.java
index 361dc59..e04f13c 100644
--- a/graphics/java/android/graphics/HardwareBufferRenderer.java
+++ b/graphics/java/android/graphics/HardwareBufferRenderer.java
@@ -275,11 +275,22 @@
             Consumer<RenderResult> wrapped = consumable -> executor.execute(
                     () -> renderCallback.accept(consumable));
             if (!isClosed()) {
+                int renderWidth;
+                int renderHeight;
+                if (mTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_90
+                        || mTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_270) {
+                    renderWidth = mHardwareBuffer.getHeight();
+                    renderHeight = mHardwareBuffer.getWidth();
+                } else {
+                    renderWidth = mHardwareBuffer.getWidth();
+                    renderHeight = mHardwareBuffer.getHeight();
+                }
+
                 nRender(
                         mProxy,
                         mTransform,
-                        mHardwareBuffer.getWidth(),
-                        mHardwareBuffer.getHeight(),
+                        renderWidth,
+                        renderHeight,
                         mColorSpace.getNativeInstance(),
                         wrapped);
             } else {
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 563fb4d..87a7c3e 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -228,9 +228,10 @@
          the screen. This time the double-tap can happen on the top or bottom of the screen.
          To teach the user about this feature, we display an education explaining how the double-tap
          works and how the app can be moved on the screen.
-         This is the text we show to the user below an animated icon visualizing the double-tap
-         action. [CHAR LIMIT=NONE] -->
-    <string name="letterbox_reachability_reposition_text">Double-tap to move this app</string>
+         This is the text we show to the user below an icon visualizing the double-tap
+         action. The description should be split in two lines separated by "\n" like for this
+         locale. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_reachability_reposition_text">Double-tap to\nmove this app</string>
 
     <!-- Freeform window caption strings -->
     <!-- Accessibility text for the maximize window button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 3d1ed87..14daae0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -157,14 +157,21 @@
     @WMSingleton
     @Provides
     static PipTransitionController provideTvPipTransition(
+            Context context,
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             Transitions transitions,
-            PipAnimationController pipAnimationController,
+            TvPipBoundsState tvPipBoundsState,
+            PipDisplayLayoutState pipDisplayLayoutState,
+            PipTransitionState pipTransitionState,
+            TvPipMenuController pipMenuController,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
-            TvPipBoundsState tvPipBoundsState, TvPipMenuController pipMenuController) {
-        return new TvPipTransition(shellInit, shellTaskOrganizer, transitions, tvPipBoundsState,
-                pipMenuController, tvPipBoundsAlgorithm, pipAnimationController);
+            PipAnimationController pipAnimationController,
+            PipSurfaceTransactionHelper pipSurfaceTransactionHelper) {
+        return new TvPipTransition(context, shellInit, shellTaskOrganizer, transitions,
+                tvPipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
+                tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
+                Optional.empty());
     }
 
     @WMSingleton
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 e2cd7a0..d8e2f5c 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
@@ -54,6 +54,7 @@
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
+import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.freeform.FreeformComponents;
 import com.android.wm.shell.freeform.FreeformTaskListener;
@@ -677,13 +678,15 @@
             SyncTransactionQueue syncQueue,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             Transitions transitions,
-            EnterDesktopTaskTransitionHandler transitionHandler,
+            EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
+            ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
             @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
         return new DesktopTasksController(context, shellInit, shellController, displayController,
                 shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions,
-                transitionHandler, desktopModeTaskRepository, mainExecutor);
+                enterDesktopTransitionHandler, exitDesktopTransitionHandler,
+                desktopModeTaskRepository, mainExecutor);
     }
 
     @WMSingleton
@@ -695,6 +698,15 @@
 
     @WMSingleton
     @Provides
+    static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(
+            Transitions transitions,
+            Context context
+    ) {
+        return new ExitDesktopTaskTransitionHandler(transitions, context);
+    }
+
+    @WMSingleton
+    @Provides
     @DynamicOverride
     static DesktopModeTaskRepository provideDesktopModeTaskRepository() {
         return new DesktopModeTaskRepository();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 015d5c1..fb0a91f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -16,10 +16,13 @@
 
 package com.android.wm.shell.desktopmode;
 
+import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Resources;
@@ -32,12 +35,12 @@
 import android.view.WindowManager;
 import android.view.WindowlessWindowManager;
 import android.view.animation.DecelerateInterpolator;
-import android.widget.ImageView;
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
 
 /**
@@ -56,6 +59,9 @@
     private final SyncTransactionQueue mSyncQueue;
     private SurfaceControlViewHost mViewHost;
 
+    private View mView;
+    private boolean mIsFullscreen;
+
     public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
             ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
             Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer,
@@ -67,21 +73,19 @@
         mTaskSurface = taskSurface;
         mTaskOrganizer = taskOrganizer;
         mRootTdaOrganizer = taskDisplayAreaOrganizer;
+        createView();
     }
 
     /**
-     * Create and animate the indicator for the exit desktop mode transition.
+     * Create a fullscreen indicator with no animation
      */
-    public void createFullscreenIndicator() {
+    private void createView() {
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         final Resources resources = mContext.getResources();
         final DisplayMetrics metrics = resources.getDisplayMetrics();
         final int screenWidth = metrics.widthPixels;
         final int screenHeight = metrics.heightPixels;
-        final int padding = mDisplayController
-                .getDisplayLayout(mTaskInfo.displayId).stableInsets().top;
-        final ImageView v = new ImageView(mContext);
-        v.setImageResource(R.drawable.desktop_windowing_transition_background);
+        mView = new View(mContext);
         final SurfaceControl.Builder builder = new SurfaceControl.Builder();
         mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
         mLeash = builder
@@ -101,7 +105,7 @@
         mViewHost = new SurfaceControlViewHost(mContext,
                 mDisplayController.getDisplay(mTaskInfo.displayId), windowManager,
                 "FullscreenVisualIndicator");
-        mViewHost.setView(v, lp);
+        mViewHost.setView(mView, lp);
         // We want this indicator to be behind the dragged task, but in front of all others.
         t.setRelativeLayer(mLeash, mTaskSurface, -1);
 
@@ -109,17 +113,56 @@
             transaction.merge(t);
             t.close();
         });
-        final Rect startBounds = new Rect(padding, padding,
-                screenWidth - padding, screenHeight - padding);
-        final VisualIndicatorAnimator animator = VisualIndicatorAnimator.fullscreenIndicator(v,
-                startBounds);
+    }
+
+    /**
+     * Create fullscreen indicator and fades it in.
+     */
+    public void createFullscreenIndicator() {
+        mIsFullscreen = true;
+        mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
+        final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFullscreenAnimator(
+                mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+        animator.start();
+    }
+
+    /**
+     * Create a fullscreen indicator. Animator fades it in while expanding the bounds outwards.
+     */
+    public void createFullscreenIndicatorWithAnimatedBounds() {
+        mIsFullscreen = true;
+        mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
+        final VisualIndicatorAnimator animator = VisualIndicatorAnimator
+                .toFullscreenAnimatorWithAnimatedBounds(mView,
+                        mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+        animator.start();
+    }
+
+    /**
+     * Takes existing fullscreen indicator and animates it to freeform bounds
+     */
+    public void transitionFullscreenIndicatorToFreeform() {
+        mIsFullscreen = false;
+        final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFreeformAnimator(
+                mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+        animator.start();
+    }
+
+    /**
+     * Takes the existing freeform indicator and animates it to fullscreen
+     */
+    public void transitionFreeformIndicatorToFullscreen() {
+        mIsFullscreen = true;
+        final VisualIndicatorAnimator animator =
+                VisualIndicatorAnimator.toFullscreenAnimatorWithAnimatedBounds(
+                mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
         animator.start();
     }
 
     /**
      * Release the indicator and its components when it is no longer needed.
      */
-    public void releaseFullscreenIndicator() {
+    public void releaseVisualIndicator() {
         if (mViewHost == null) return;
         if (mViewHost != null) {
             mViewHost.release();
@@ -136,20 +179,28 @@
             });
         }
     }
+
+    /**
+     * Returns true if visual indicator is fullscreen
+     */
+    public boolean isFullscreen() {
+        return mIsFullscreen;
+    }
+
     /**
      * Animator for Desktop Mode transitions which supports bounds and alpha animation.
      */
     private static class VisualIndicatorAnimator extends ValueAnimator {
         private static final int FULLSCREEN_INDICATOR_DURATION = 200;
-        private static final float SCALE_ADJUSTMENT_PERCENT = 0.015f;
+        private static final float FULLSCREEN_SCALE_ADJUSTMENT_PERCENT = 0.015f;
         private static final float INDICATOR_FINAL_OPACITY = 0.7f;
 
-        private final ImageView mView;
+        private final View mView;
         private final Rect mStartBounds;
         private final Rect mEndBounds;
         private final RectEvaluator mRectEvaluator;
 
-        private VisualIndicatorAnimator(ImageView view, Rect startBounds,
+        private VisualIndicatorAnimator(View view, Rect startBounds,
                 Rect endBounds) {
             mView = view;
             mStartBounds = new Rect(startBounds);
@@ -161,30 +212,66 @@
         /**
          * Create animator for visual indicator of fullscreen transition
          *
-         * @param view        the view for this indicator
-         * @param startBounds the starting bounds of the fullscreen indicator
+         * @param view the view for this indicator
+         * @param displayLayout information about the display the transitioning task is currently on
          */
-        public static VisualIndicatorAnimator fullscreenIndicator(ImageView view,
-                Rect startBounds) {
-            view.getDrawable().setBounds(startBounds);
-            int width = startBounds.width();
-            int height = startBounds.height();
-            Rect endBounds = new Rect((int) (startBounds.left - (SCALE_ADJUSTMENT_PERCENT * width)),
-                    (int) (startBounds.top - (SCALE_ADJUSTMENT_PERCENT * height)),
-                    (int) (startBounds.right + (SCALE_ADJUSTMENT_PERCENT * width)),
-                    (int) (startBounds.bottom + (SCALE_ADJUSTMENT_PERCENT * height)));
-            VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
-                    view, startBounds, endBounds);
+        public static VisualIndicatorAnimator toFullscreenAnimator(@NonNull View view,
+                @NonNull DisplayLayout displayLayout) {
+            final Rect bounds = getMaxBounds(displayLayout);
+            final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+                    view, bounds, bounds);
             animator.setInterpolator(new DecelerateInterpolator());
-            setupFullscreenIndicatorAnimation(animator);
+            setupIndicatorAnimation(animator);
+            return animator;
+        }
+
+
+        /**
+         * Create animator for visual indicator of fullscreen transition
+         *
+         * @param view the view for this indicator
+         * @param displayLayout information about the display the transitioning task is currently on
+         */
+        public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds(
+                @NonNull View view, @NonNull DisplayLayout displayLayout) {
+            final int padding = displayLayout.stableInsets().top;
+            Rect startBounds = new Rect(padding, padding,
+                    displayLayout.width() - padding, displayLayout.height() - padding);
+            view.getBackground().setBounds(startBounds);
+
+            final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+                    view, startBounds, getMaxBounds(displayLayout));
+            animator.setInterpolator(new DecelerateInterpolator());
+            setupIndicatorAnimation(animator);
             return animator;
         }
 
         /**
-         * Add necessary listener for animation of fullscreen indicator
+         * Create animator for visual indicator of freeform transition
+         *
+         * @param view the view for this indicator
+         * @param displayLayout information about the display the transitioning task is currently on
          */
-        private static void setupFullscreenIndicatorAnimation(
-                VisualIndicatorAnimator animator) {
+        public static VisualIndicatorAnimator toFreeformAnimator(@NonNull View view,
+                @NonNull DisplayLayout displayLayout) {
+            final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE;
+            final int width = displayLayout.width();
+            final int height = displayLayout.height();
+            Rect endBounds = new Rect((int) (adjustmentPercentage * width / 2),
+                    (int) (adjustmentPercentage * height / 2),
+                    (int) (displayLayout.width() - (adjustmentPercentage * width / 2)),
+                    (int) (displayLayout.height() - (adjustmentPercentage * height / 2)));
+            final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+                    view, getMaxBounds(displayLayout), endBounds);
+            animator.setInterpolator(new DecelerateInterpolator());
+            setupIndicatorAnimation(animator);
+            return animator;
+        }
+
+        /**
+         * Add necessary listener for animation of indicator
+         */
+        private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator) {
             animator.addUpdateListener(a -> {
                 if (animator.mView != null) {
                     animator.updateBounds(a.getAnimatedFraction(), animator.mView);
@@ -196,7 +283,7 @@
             animator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    animator.mView.getDrawable().setBounds(animator.mEndBounds);
+                    animator.mView.getBackground().setBounds(animator.mEndBounds);
                 }
             });
             animator.setDuration(FULLSCREEN_INDICATOR_DURATION);
@@ -210,9 +297,12 @@
          * @param fraction fraction to use, compared against previous fraction
          * @param view     the view to update
          */
-        private void updateBounds(float fraction, ImageView view) {
+        private void updateBounds(float fraction, View view) {
+            if (mStartBounds.equals(mEndBounds)) {
+                return;
+            }
             Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
-            view.getDrawable().setBounds(currentBounds);
+            view.getBackground().setBounds(currentBounds);
         }
 
         /**
@@ -223,5 +313,23 @@
         private void updateIndicatorAlpha(float fraction, View view) {
             view.setAlpha(fraction * INDICATOR_FINAL_OPACITY);
         }
+
+        /**
+         * Return the max bounds of a fullscreen indicator
+         */
+        private static Rect getMaxBounds(@NonNull DisplayLayout displayLayout) {
+            final int padding = displayLayout.stableInsets().top;
+            final int width = displayLayout.width() - 2 * padding;
+            final int height = displayLayout.height() - 2 * padding;
+            Rect endBounds = new Rect((int) (padding
+                            - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)),
+                    (int) (padding
+                            - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)),
+                    (int) (displayLayout.width() - padding
+                            + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)),
+                    (int) (displayLayout.height() - padding
+                            + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)));
+            return endBounds;
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index cb04a43..670b24c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -68,7 +68,8 @@
         private val syncQueue: SyncTransactionQueue,
         private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
         private val transitions: Transitions,
-        private val animationTransitionHandler: EnterDesktopTaskTransitionHandler,
+        private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
+        private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
         private val desktopModeTaskRepository: DesktopModeTaskRepository,
         @ShellMainThread private val mainExecutor: ShellExecutor
 ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
@@ -121,7 +122,7 @@
     }
 
     /** Move a task to desktop */
-    fun moveToDesktop(task: ActivityManager.RunningTaskInfo) {
+    fun moveToDesktop(task: RunningTaskInfo) {
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDesktop: %d", task.taskId)
 
         val wct = WindowContainerTransaction()
@@ -149,7 +150,7 @@
         wct.setBounds(taskInfo.token, startBounds)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            animationTransitionHandler.startTransition(
+            enterDesktopTaskTransitionHandler.startTransition(
                     Transitions.TRANSIT_ENTER_FREEFORM, wct)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
@@ -167,7 +168,8 @@
         wct.setBounds(taskInfo.token, freeformBounds)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            animationTransitionHandler.startTransition(Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct)
+            enterDesktopTaskTransitionHandler.startTransition(
+                Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
         }
@@ -179,7 +181,7 @@
     }
 
     /** Move a task to fullscreen */
-    fun moveToFullscreen(task: ActivityManager.RunningTaskInfo) {
+    fun moveToFullscreen(task: RunningTaskInfo) {
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)
 
         val wct = WindowContainerTransaction()
@@ -191,8 +193,20 @@
         }
     }
 
+    fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) {
+        val wct = WindowContainerTransaction()
+        addMoveToFullscreenChanges(wct, task.token)
+
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            exitDesktopTaskTransitionHandler.startTransition(
+            Transitions.TRANSIT_EXIT_DESKTOP_MODE, wct)
+        } else {
+            shellTaskOrganizer.applyTransaction(wct)
+        }
+    }
+
     /** Move a task to the front **/
-    fun moveTaskToFront(taskInfo: ActivityManager.RunningTaskInfo) {
+    fun moveTaskToFront(taskInfo: RunningTaskInfo) {
         val wct = WindowContainerTransaction()
         wct.reorder(taskInfo.token, true)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -259,7 +273,7 @@
         request: TransitionRequestInfo
     ): WindowContainerTransaction? {
         // Check if we should skip handling this transition
-        val task: ActivityManager.RunningTaskInfo? = request.triggerTask
+        val task: RunningTaskInfo? = request.triggerTask
         val shouldHandleRequest =
             when {
                 // Only handle open or to front transitions
@@ -368,16 +382,15 @@
             taskSurface: SurfaceControl,
             y: Float
     ) {
-        val statusBarHeight = displayController
-                .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
         if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+            val statusBarHeight = getStatusBarHeight(taskInfo)
             if (y <= statusBarHeight && visualIndicator == null) {
                 visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
                         displayController, context, taskSurface, shellTaskOrganizer,
                         rootTaskDisplayAreaOrganizer)
-                visualIndicator?.createFullscreenIndicator()
+                visualIndicator?.createFullscreenIndicatorWithAnimatedBounds()
             } else if (y > statusBarHeight && visualIndicator != null) {
-                visualIndicator?.releaseFullscreenIndicator()
+                visualIndicator?.releaseVisualIndicator()
                 visualIndicator = null
             }
         }
@@ -393,16 +406,73 @@
             taskInfo: RunningTaskInfo,
             y: Float
     ) {
-        val statusBarHeight = displayController
-                .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+        val statusBarHeight = getStatusBarHeight(taskInfo)
         if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
-            moveToFullscreen(taskInfo.taskId)
-            visualIndicator?.releaseFullscreenIndicator()
+            visualIndicator?.releaseVisualIndicator()
             visualIndicator = null
+            moveToFullscreenWithAnimation(taskInfo)
         }
     }
 
     /**
+     * Perform checks required on drag move. Create/release fullscreen indicator and transitions
+     * indicator to freeform or fullscreen dimensions as needed.
+     *
+     * @param taskInfo the task being dragged.
+     * @param taskSurface SurfaceControl of dragged task.
+     * @param y coordinate of dragged task. Used for checks against status bar height.
+     */
+    fun onDragPositioningMoveThroughStatusBar(
+            taskInfo: RunningTaskInfo,
+            taskSurface: SurfaceControl,
+            y: Float
+    ) {
+        if (visualIndicator == null) {
+            visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
+                    displayController, context, taskSurface, shellTaskOrganizer,
+                    rootTaskDisplayAreaOrganizer)
+            visualIndicator?.createFullscreenIndicator()
+        }
+        val indicator = visualIndicator ?: return
+        if (y >= getFreeformTransitionStatusBarDragThreshold(taskInfo)) {
+            if (indicator.isFullscreen) {
+                indicator.transitionFullscreenIndicatorToFreeform()
+            }
+        } else if (!indicator.isFullscreen) {
+            indicator.transitionFreeformIndicatorToFullscreen()
+        }
+    }
+
+    /**
+     * Perform checks required when drag ends under status bar area.
+     *
+     * @param taskInfo the task being dragged.
+     * @param y height of drag, to be checked against status bar height.
+     */
+    fun onDragPositioningEndThroughStatusBar(
+            taskInfo: RunningTaskInfo,
+            freeformBounds: Rect
+    ) {
+        moveToDesktopWithAnimation(taskInfo, freeformBounds)
+        visualIndicator?.releaseVisualIndicator()
+        visualIndicator = null
+    }
+
+
+    private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
+        return displayController.getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+    }
+
+    /**
+     * Returns the threshold at which we transition a task into freeform when dragging a
+     * fullscreen task down from the status bar
+     */
+    private fun getFreeformTransitionStatusBarDragThreshold(taskInfo: RunningTaskInfo): Int {
+        return 2 * getStatusBarHeight(taskInfo)
+    }
+
+
+    /**
      * Adds a listener to find out about changes in the visibility of freeform tasks.
      *
      * @param listener the listener to add.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
new file mode 100644
index 0000000..d18e98a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.util.DisplayMetrics;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+
+/**
+ * The {@link Transitions.TransitionHandler} that handles transitions for desktop mode tasks
+ * entering and exiting freeform.
+ */
+public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionHandler {
+    private static final int FULLSCREEN_ANIMATION_DURATION = 336;
+    private final Context mContext;
+    private final Transitions mTransitions;
+    private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
+
+    private Supplier<SurfaceControl.Transaction> mTransactionSupplier;
+
+    public ExitDesktopTaskTransitionHandler(
+            Transitions transitions,
+            Context context) {
+        this(transitions, SurfaceControl.Transaction::new, context);
+    }
+
+    private ExitDesktopTaskTransitionHandler(
+            Transitions transitions,
+            Supplier<SurfaceControl.Transaction> supplier,
+            Context context) {
+        mTransitions = transitions;
+        mTransactionSupplier = supplier;
+        mContext = context;
+    }
+
+    /**
+     * Starts Transition of a given type
+     * @param type Transition type
+     * @param wct WindowContainerTransaction for transition
+     */
+    public void startTransition(@WindowManager.TransitionType int type,
+            @NonNull WindowContainerTransaction wct) {
+        final IBinder token = mTransitions.startTransition(type, wct, this);
+        mPendingTransitionTokens.add(token);
+    }
+
+    @Override
+    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        boolean transitionHandled = false;
+        for (TransitionInfo.Change change : info.getChanges()) {
+            if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
+                continue;
+            }
+
+            final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+            if (taskInfo == null || taskInfo.taskId == -1) {
+                continue;
+            }
+
+            if (change.getMode() == WindowManager.TRANSIT_CHANGE) {
+                transitionHandled |= startChangeTransition(
+                        transition, info.getType(), change, startT, finishCallback);
+            }
+        }
+
+        mPendingTransitionTokens.remove(transition);
+
+        return transitionHandled;
+    }
+
+    @VisibleForTesting
+    boolean startChangeTransition(
+            @NonNull IBinder transition,
+            @WindowManager.TransitionType int type,
+            @NonNull TransitionInfo.Change change,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        if (!mPendingTransitionTokens.contains(transition)) {
+            return false;
+        }
+        final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+        if (type == Transitions.TRANSIT_EXIT_DESKTOP_MODE
+                && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+            // This Transition animates a task to fullscreen after being dragged to status bar
+            final Resources resources = mContext.getResources();
+            final DisplayMetrics metrics = resources.getDisplayMetrics();
+            final int screenWidth = metrics.widthPixels;
+            final int screenHeight = metrics.heightPixels;
+            final SurfaceControl sc = change.getLeash();
+            startT.setCrop(sc, null);
+            startT.apply();
+            final ValueAnimator animator = new ValueAnimator();
+            animator.setFloatValues(0f, 1f);
+            animator.setDuration(FULLSCREEN_ANIMATION_DURATION);
+            final Rect startBounds = change.getStartAbsBounds();
+            final float scaleX = (float) startBounds.width() / screenWidth;
+            final float scaleY = (float) startBounds.height() / screenHeight;
+            final SurfaceControl.Transaction t = mTransactionSupplier.get();
+            Point startPos = new Point(startBounds.left,
+                    startBounds.top);
+            animator.addUpdateListener(animation -> {
+                float fraction = animation.getAnimatedFraction();
+                float currentScaleX = scaleX + ((1 - scaleX) * fraction);
+                float currentScaleY = scaleY + ((1 - scaleY) * fraction);
+                t.setPosition(sc, startPos.x * (1 - fraction), startPos.y * (1 - fraction));
+                t.setScale(sc, currentScaleX, currentScaleY);
+                t.apply();
+            });
+            animator.start();
+            return true;
+        }
+
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+            @NonNull TransitionRequestInfo request) {
+        return null;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 4c53f60..f70df83 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -188,6 +188,11 @@
         return mCurrentAnimator;
     }
 
+    /** Reset animator state to prevent it from being used after its lifetime. */
+    public void resetAnimatorState() {
+        mCurrentAnimator = null;
+    }
+
     private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) {
         animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper);
         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f2f30ea..a939212 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -73,6 +73,7 @@
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransactionCallback;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
@@ -142,6 +143,23 @@
     protected final ShellTaskOrganizer mTaskOrganizer;
     protected final ShellExecutor mMainExecutor;
 
+    // the runnable to execute after WindowContainerTransactions is applied to finish resizing pip
+    private Runnable mPipFinishResizeWCTRunnable;
+
+    private final WindowContainerTransactionCallback mPipFinishResizeWCTCallback =
+            new WindowContainerTransactionCallback() {
+        @Override
+        public void onTransactionReady(int id, SurfaceControl.Transaction t) {
+            t.apply();
+
+            // execute the runnable if non-null after WCT is applied to finish resizing pip
+            if (mPipFinishResizeWCTRunnable != null) {
+                mPipFinishResizeWCTRunnable.run();
+                mPipFinishResizeWCTRunnable = null;
+            }
+        }
+    };
+
     // These callbacks are called on the update thread
     private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
             new PipAnimationController.PipAnimationCallback() {
@@ -416,6 +434,26 @@
     }
 
     /**
+     * Override if the PiP should always use a fade-in animation during PiP entry.
+     *
+     * @return true if the mOneShotAnimationType should always be
+     * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
+     */
+    protected boolean shouldAlwaysFadeIn() {
+        return false;
+    }
+
+    /**
+     * Whether the menu should get attached as early as possible when entering PiP.
+     *
+     * @return whether the menu should be attached before
+     * {@link PipBoundsAlgorithm#getEntryDestinationBounds()} is called.
+     */
+    protected boolean shouldAttachMenuEarly() {
+        return false;
+    }
+
+    /**
      * Callback when Launcher starts swipe-pip-to-home operation.
      * @return {@link Rect} for destination bounds.
      */
@@ -709,17 +747,26 @@
             return;
         }
 
+        if (shouldAlwaysFadeIn()) {
+            mOneShotAnimationType = ANIM_TYPE_ALPHA;
+        }
+
         if (mWaitForFixedRotation) {
             onTaskAppearedWithFixedRotation();
             return;
         }
 
+        if (shouldAttachMenuEarly()) {
+            mPipMenuController.attach(mLeash);
+        }
         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
         final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
 
         if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
-            mPipMenuController.attach(mLeash);
+            if (!shouldAttachMenuEarly()) {
+                mPipMenuController.attach(mLeash);
+            }
             final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
                     info.pictureInPictureParams, currentBounds);
             scheduleAnimateResizePip(currentBounds, destinationBounds, 0 /* startingAngle */,
@@ -834,7 +881,9 @@
             @Nullable SurfaceControl.Transaction boundsChangeTransaction) {
         // PiP menu is attached late in the process here to avoid any artifacts on the leash
         // caused by addShellRoot when in gesture navigation mode.
-        mPipMenuController.attach(mLeash);
+        if (!shouldAttachMenuEarly()) {
+            mPipMenuController.attach(mLeash);
+        }
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
         wct.setBounds(mToken, destinationBounds);
@@ -1249,8 +1298,23 @@
     /**
      * Animates resizing of the pinned stack given the duration and start bounds.
      * This is used when the starting bounds is not the current PiP bounds.
+     *
+     * @param pipFinishResizeWCTRunnable callback to run after window updates are complete
      */
     public void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration,
+            float startingAngle, Consumer<Rect> updateBoundsCallback,
+            Runnable pipFinishResizeWCTRunnable) {
+        mPipFinishResizeWCTRunnable = pipFinishResizeWCTRunnable;
+        if (mPipFinishResizeWCTRunnable != null) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "mPipFinishResizeWCTRunnable is set to be called once window updates");
+        }
+
+        scheduleAnimateResizePip(fromBounds, toBounds, duration, startingAngle,
+                updateBoundsCallback);
+    }
+
+    private void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration,
             float startingAngle, Consumer<Rect> updateBoundsCallback) {
         if (mWaitForFixedRotation) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -1555,7 +1619,7 @@
             mSplitScreenOptional.ifPresent(splitScreenController ->
                     splitScreenController.enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct));
         } else {
-            mTaskOrganizer.applyTransaction(wct);
+            mTaskOrganizer.applySyncTransaction(wct, mPipFinishResizeWCTCallback);
         }
     }
 
@@ -1790,6 +1854,7 @@
                         animator::clearContentOverlay);
             }
             PipAnimationController.quietCancel(animator);
+            mPipAnimationController.resetAnimatorState();
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 49a27c5..4a76a50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -272,6 +272,8 @@
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
             @NonNull TransitionRequestInfo request) {
         if (requestHasPipEnter(request)) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: handle PiP enter request", TAG);
             WindowContainerTransaction wct = new WindowContainerTransaction();
             augmentRequest(transition, request, wct);
             return wct;
@@ -731,6 +733,11 @@
 
         setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
                 taskInfo.topActivityInfo);
+
+        if (mPipOrganizer.shouldAttachMenuEarly()) {
+            mPipMenuController.attach(leash);
+        }
+
         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds();
         int rotationDelta = deltaRotation(startRotation, endRotation);
@@ -745,7 +752,10 @@
         mSurfaceTransactionHelper
                 .crop(finishTransaction, leash, destinationBounds)
                 .round(finishTransaction, leash, true /* applyCornerRadius */);
-        mTransitions.getMainExecutor().executeDelayed(() -> mPipMenuController.attach(leash), 0);
+        if (!mPipOrganizer.shouldAttachMenuEarly()) {
+            mTransitions.getMainExecutor().executeDelayed(
+                    () -> mPipMenuController.attach(leash), 0);
+        }
 
         if (taskInfo.pictureInPictureParams != null
                 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()
@@ -785,6 +795,11 @@
             tmpTransform.postRotate(rotationDelta);
             startTransaction.setMatrix(leash, tmpTransform, new float[9]);
         }
+
+        if (mPipOrganizer.shouldAlwaysFadeIn()) {
+            mOneShotAnimationType = ANIM_TYPE_ALPHA;
+        }
+
         if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
             startTransaction.setAlpha(leash, 0f);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 1d12824..68952c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -588,8 +588,16 @@
                 final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
                         mLastResizeBounds, movementBounds);
                 mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
+
+                // disable the pinch resizing until the final bounds are updated
+                final boolean prevEnablePinchResize = mEnablePinchResize;
+                mEnablePinchResize = false;
+
                 mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
-                        PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback);
+                        PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> {
+                            // reset the pinch resizing to its default state
+                            mEnablePinchResize = prevEnablePinchResize;
+                        });
             } else {
                 mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
                         PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index d73723c..2f74fb6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -446,7 +446,7 @@
                     "%s: PiP has already been closed by custom close action", TAG);
             return;
         }
-        removeTask(mPinnedTaskId);
+        mPipTaskOrganizer.removePip();
         onPipDisappeared();
     }
 
@@ -673,17 +673,6 @@
         }
     }
 
-    private static void removeTask(int taskId) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: removeTask(), taskId=%d", TAG, taskId);
-        try {
-            ActivityTaskManager.getService().removeTask(taskId);
-        } catch (Exception e) {
-            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: Atm.removeTask() failed, %s", TAG, e);
-        }
-    }
-
     private static String stateToName(@State int state) {
         switch (state) {
             case STATE_NO_PIP:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index f6856f1..0940490 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -85,4 +85,17 @@
             mPipParamsChangedForwarder.notifySubtitleChanged(params.getSubtitle());
         }
     }
+
+    /**
+     * Override for TV since the menu bounds affect the PiP location. Additionally, we want to
+     * ensure that menu is shown immediately since it should always be visible on TV as it creates
+     * a border with rounded corners around the PiP.
+     */
+    protected boolean shouldAttachMenuEarly() {
+        return true;
+    }
+
+    protected boolean shouldAlwaysFadeIn() {
+        return true;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index 8ebcf63..d3253a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -16,60 +16,43 @@
 
 package com.android.wm.shell.pip.tv;
 
-import android.app.TaskInfo;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerTransaction;
+import android.content.Context;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipMenuController;
-import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipTransition;
+import com.android.wm.shell.pip.PipTransitionState;
+import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
+import java.util.Optional;
+
 /**
  * PiP Transition for TV.
- * TODO: Implement animation once TV is using Transitions.
  */
-public class TvPipTransition extends PipTransitionController {
-    public TvPipTransition(
+public class TvPipTransition extends PipTransition {
+
+    public TvPipTransition(Context context,
             @NonNull ShellInit shellInit,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
             @NonNull Transitions transitions,
-            PipBoundsState pipBoundsState,
-            PipMenuController pipMenuController,
+            TvPipBoundsState tvPipBoundsState,
+            PipDisplayLayoutState pipDisplayLayoutState,
+            PipTransitionState pipTransitionState,
+            TvPipMenuController tvPipMenuController,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
-            PipAnimationController pipAnimationController) {
-        super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
-                tvPipBoundsAlgorithm, pipAnimationController);
+            PipAnimationController pipAnimationController,
+            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+            Optional<SplitScreenController> splitScreenOptional) {
+        super(context, shellInit, shellTaskOrganizer, transitions, tvPipBoundsState,
+                pipDisplayLayoutState, pipTransitionState, tvPipMenuController,
+                tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
+                splitScreenOptional);
     }
 
-    @Override
-    public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds, int direction,
-            SurfaceControl.Transaction tx) {
-
-    }
-
-    @Override
-    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @android.annotation.NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        return false;
-    }
-
-    @Nullable
-    @Override
-    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
-            @NonNull TransitionRequestInfo request) {
-        return null;
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 5c64177..c8d6a5e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -456,7 +456,10 @@
                         cancel(mWillFinishToHome);
                         return;
                     }
-                    hasChangingApp = true;
+                    // Don't consider order-only changes as changing apps.
+                    if (!TransitionUtil.isOrderOnly(change)) {
+                        hasChangingApp = true;
+                    }
                 }
             }
             if (hasChangingApp && foundRecentsClosing) {
@@ -484,13 +487,14 @@
             }
             boolean didMergeThings = false;
             if (closingTasks != null) {
-                // Cancelling a task-switch. Move the tasks back to mPausing from mOpening
+                // Potentially cancelling a task-switch. Move the tasks back to mPausing if they
+                // are in mOpening.
                 for (int i = 0; i < closingTasks.size(); ++i) {
                     final TransitionInfo.Change change = closingTasks.get(i);
                     int openingIdx = TaskState.indexOf(mOpeningTasks, change);
                     if (openingIdx < 0) {
-                        Slog.e(TAG, "Back to existing recents animation from an unrecognized "
-                                + "task: " + change.getTaskInfo().taskId);
+                        Slog.w(TAG, "Closing a task that wasn't opening, this may be split or"
+                                + " something unexpected: " + change.getTaskInfo().taskId);
                         continue;
                     }
                     mPausingTasks.add(mOpeningTasks.remove(openingIdx));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index d52abf7..5a92f78 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -235,6 +235,7 @@
     private TransitionInfo subCopy(@NonNull TransitionInfo info,
             @WindowManager.TransitionType int newType, boolean withChanges) {
         final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0);
+        out.setDebugId(info.getDebugId());
         if (withChanges) {
             for (int i = 0; i < info.getChanges().size(); ++i) {
                 out.getChanges().add(info.getChanges().get(i));
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 63c7969..3dd10a0 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
@@ -301,8 +301,8 @@
             return true;
         }
 
-        // check if no-animation and skip animation if so.
-        if (Transitions.isAllNoAnimation(info)) {
+        // Early check if the transition doesn't warrant an animation.
+        if (Transitions.isAllNoAnimation(info) || Transitions.isAllOrderOnly(info)) {
             startTransaction.apply();
             finishTransaction.apply();
             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 485b400..4e3d220 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -63,7 +63,7 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         if (mTransition != transition) return false;
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote"
-                + " transition %s for %s.", mRemote, transition);
+                + " transition %s for #%d.", mRemote, info.getDebugId());
 
         final IBinder.DeathRecipient remoteDied = () -> {
             Log.e(Transitions.TAG, "Remote transition died, finishing");
@@ -113,9 +113,6 @@
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote"
-                + " transition %s for %s.", mRemote, transition);
-
         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
             @Override
             public void onTransitionFinished(WindowContainerTransaction wct,
@@ -154,4 +151,10 @@
                 + " for %s: %s", transition, remote);
         return new WindowContainerTransaction();
     }
+
+    @Override
+    public String toString() {
+        return "OneShotRemoteHandler:" + mRemote.getDebugName() + ":"
+                + mRemote.getRemoteTransition();
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 3c4e889..5b7231c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -101,8 +101,8 @@
         }
         RemoteTransition pendingRemote = mRequestedRemotes.get(transition);
         if (pendingRemote == null) {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s doesn't have "
-                    + "explicit remote, search filters for match for %s", transition, info);
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition doesn't have "
+                    + "explicit remote, search filters for match for %s", info);
             // If no explicit remote, search filters until one matches
             for (int i = mFilters.size() - 1; i >= 0; --i) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Checking filter %s",
@@ -116,8 +116,8 @@
                 }
             }
         }
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for %s to %s",
-                transition, pendingRemote);
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for #%d to %s",
+                info.getDebugId(), pendingRemote);
 
         if (pendingRemote == null) return false;
 
@@ -184,9 +184,10 @@
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        final IRemoteTransition remote = mRequestedRemotes.get(mergeTarget).getRemoteTransition();
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt merge %s into %s",
-                transition, remote);
+        final RemoteTransition remoteTransition = mRequestedRemotes.get(mergeTarget);
+        final IRemoteTransition remote = remoteTransition.getRemoteTransition();
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "   Merge into remote: %s",
+                remoteTransition);
         if (remote == null) return;
 
         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
index 0386ec3..1879bf7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.IBinder;
-import android.util.Log;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
@@ -59,7 +58,6 @@
     @Override
     public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
             @Nullable SurfaceControl.Transaction finishTransaction) {
-        Log.e(Transitions.TAG, "Sleep transition was consumed. This doesn't make sense");
         mSleepTransitions.remove(transition);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 4284993..681fa51 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -140,6 +140,9 @@
     /** Transition type to freeform in desktop mode. */
     public static final int TRANSIT_ENTER_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 11;
 
+    /** Transition type to fullscreen from desktop mode. */
+    public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
+
     private final WindowOrganizer mOrganizer;
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
@@ -182,6 +185,14 @@
 
         /** Ordered list of transitions which have been merged into this one. */
         private ArrayList<ActiveTransition> mMerged;
+
+        @Override
+        public String toString() {
+            if (mInfo != null && mInfo.getDebugId() >= 0) {
+                return "(#" + mInfo.getDebugId() + ")" + mToken;
+            }
+            return mToken.toString();
+        }
     }
 
     /** Keeps track of transitions which have been started, but aren't ready yet. */
@@ -516,6 +527,16 @@
         return hasNoAnimation;
     }
 
+    /**
+     * Check if all changes in this transition are only ordering changes. If so, we won't animate.
+     */
+    static boolean isAllOrderOnly(TransitionInfo info) {
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            if (!TransitionUtil.isOrderOnly(info.getChanges().get(i))) return false;
+        }
+        return true;
+    }
+
     @VisibleForTesting
     void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
@@ -529,8 +550,8 @@
                             activeTransition -> activeTransition.mToken).toArray()));
         }
         if (activeIdx > 0) {
-            Log.e(TAG, "Transition became ready out-of-order " + transitionToken + ". Expected"
-                    + " order: " + Arrays.toString(mPendingTransitions.stream().map(
+            Log.e(TAG, "Transition became ready out-of-order " + mPendingTransitions.get(activeIdx)
+                    + ". Expected order: " + Arrays.toString(mPendingTransitions.stream().map(
                             activeTransition -> activeTransition.mToken).toArray()));
         }
         // Move from pending to ready
@@ -547,6 +568,7 @@
         if (info.getType() == TRANSIT_SLEEP) {
             if (activeIdx > 0 || !mActiveTransitions.isEmpty() || mReadyTransitions.size() > 1) {
                 // Sleep starts a process of forcing all prior transitions to finish immediately
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Start finish-for-sleep");
                 finishForSleep(null /* forceFinish */);
                 return;
             }
@@ -555,8 +577,8 @@
         if (info.getRootCount() == 0 && !alwaysReportToKeyguard(info)) {
             // No root-leashes implies that the transition is empty/no-op, so just do
             // housekeeping and return.
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots (%s): %s",
-                    transitionToken, info);
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots in %s so"
+                    + " abort", active);
             onAbort(active);
             return;
         }
@@ -585,6 +607,8 @@
                 && allOccluded)) {
             // Treat this as an abort since we are bypassing any merge logic and effectively
             // finishing immediately.
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                    "Non-visible anim so abort: %s", active);
             onAbort(active);
             return;
         }
@@ -652,21 +676,21 @@
             return;
         }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
-                + " another transition %s is still animating. Notify the animating transition"
-                + " in case they can be merged", ready.mToken, playing.mToken);
+                + " %s is still animating. Notify the animating transition"
+                + " in case they can be merged", ready, playing);
         playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
                 playing.mToken, (wct, cb) -> onMerged(playing, ready));
     }
 
     private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged %s",
-                merged.mToken);
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s",
+                merged, playing);
         int readyIdx = 0;
         if (mReadyTransitions.isEmpty() || mReadyTransitions.get(0) != merged) {
-            Log.e(TAG, "Merged transition out-of-order?");
+            Log.e(TAG, "Merged transition out-of-order? " + merged);
             readyIdx = mReadyTransitions.indexOf(merged);
             if (readyIdx < 0) {
-                Log.e(TAG, "Merged a transition that is no-longer queued?");
+                Log.e(TAG, "Merged a transition that is no-longer queued? " + merged);
                 return;
             }
         }
@@ -687,6 +711,7 @@
     }
 
     private void playTransition(@NonNull ActiveTransition active) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Playing animation for %s", active);
         for (int i = 0; i < mObservers.size(); ++i) {
             mObservers.get(i).onTransitionStarting(active.mToken);
         }
@@ -788,12 +813,12 @@
         int activeIdx = mActiveTransitions.indexOf(active);
         if (activeIdx < 0) {
             Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
-                    + " a handler didn't properly deal with a merge. " + active.mToken,
+                    + " a handler didn't properly deal with a merge. " + active,
                     new RuntimeException());
             return;
         } else if (activeIdx != 0) {
             // Relevant right now since we only allow 1 active transition at a time.
-            Log.e(TAG, "Finishing a transition out of order. " + active.mToken);
+            Log.e(TAG, "Finishing a transition out of order. " + active);
         }
         mActiveTransitions.remove(activeIdx);
 
@@ -801,7 +826,7 @@
             mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
         }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished "
-                + "(aborted=%b), notifying core %s", active.mAborted, active.mToken);
+                + "(aborted=%b), notifying core %s", active.mAborted, active);
         if (active.mStartT != null) {
             // Applied by now, so clear immediately to remove any references. Do not set to null
             // yet, though, since nullness is used later to disambiguate malformed transitions.
@@ -917,6 +942,8 @@
     /** Start a new transition directly. */
     public IBinder startTransition(@WindowManager.TransitionType int type,
             @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition "
+                + "type=%d wct=%s handler=%s", type, wct, handler);
         final ActiveTransition active = new ActiveTransition();
         active.mHandler = handler;
         active.mToken = mOrganizer.startNewTransition(type, wct);
@@ -948,8 +975,7 @@
             return;
         }
         if (forceFinish != null && mActiveTransitions.contains(forceFinish)) {
-            Log.e(TAG, "Forcing transition to finish due to sleep timeout: "
-                    + forceFinish.mToken);
+            Log.e(TAG, "Forcing transition to finish due to sleep timeout: " + forceFinish);
             forceFinish.mAborted = true;
             // Last notify of it being consumed. Note: mHandler should never be null,
             // but check just to be safe.
@@ -967,6 +993,8 @@
                 // Try to signal that we are sleeping by attempting to merge the sleep transition
                 // into the playing one.
                 final ActiveTransition nextSleep = mReadyTransitions.get(sleepIdx);
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge SLEEP %s"
+                        + " into %s", nextSleep, playing);
                 playing.mHandler.mergeAnimation(nextSleep.mToken, nextSleep.mInfo, dummyT,
                         playing.mToken, (wct, cb) -> {});
             } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
index 7595c96..ce10291 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
@@ -31,6 +31,7 @@
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
@@ -90,6 +91,15 @@
                 && !change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
     }
 
+    /** Returns `true` if `change` is only re-ordering. */
+    public static boolean isOrderOnly(TransitionInfo.Change change) {
+        return change.getMode() == TRANSIT_CHANGE
+                && (change.getFlags() & FLAG_MOVED_TO_TOP) != 0
+                && change.getStartAbsBounds().equals(change.getEndAbsBounds())
+                && (change.getLastParent() == null
+                        || change.getLastParent().equals(change.getParent()));
+    }
+
     /**
      * Filter that selects leaf-tasks only. THIS IS ORDER-DEPENDENT! For it to work properly, you
      * MUST call `test` in the same order that the changes appear in the TransitionInfo.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index c0dcd0b..f998217 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -79,6 +79,7 @@
 
 public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
     private static final String TAG = "DesktopModeWindowDecorViewModel";
+
     private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
     private final ActivityTaskManager mActivityTaskManager;
     private final ShellTaskOrganizer mTaskOrganizer;
@@ -542,7 +543,7 @@
                     mTransitionDragActive = false;
                     final int statusBarHeight = getStatusBarHeight(
                             relevantDecor.mTaskInfo.displayId);
-                    if (ev.getY() > statusBarHeight) {
+                    if (ev.getY() > 2 * statusBarHeight) {
                         if (DesktopModeStatus.isProto2Enabled()) {
                             mPauseRelayoutForTask = relevantDecor.mTaskInfo.taskId;
                             centerAndMoveToDesktopWithAnimation(relevantDecor, ev);
@@ -567,9 +568,11 @@
                     return;
                 }
                 if (mTransitionDragActive) {
-                    final int statusBarHeight = mDisplayController
-                            .getDisplayLayout(
-                                    relevantDecor.mTaskInfo.displayId).stableInsets().top;
+                    mDesktopTasksController.ifPresent(
+                            c -> c.onDragPositioningMoveThroughStatusBar(relevantDecor.mTaskInfo,
+                            relevantDecor.mTaskSurface, ev.getY()));
+                    final int statusBarHeight = getStatusBarHeight(
+                            relevantDecor.mTaskInfo.displayId);
                     if (ev.getY() > statusBarHeight) {
                         if (!mDragToDesktopAnimationStarted) {
                             mDragToDesktopAnimationStarted = true;
@@ -644,7 +647,8 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 mDesktopTasksController.ifPresent(
-                        c -> c.moveToDesktopWithAnimation(relevantDecor.mTaskInfo,
+                        c -> c.onDragPositioningEndThroughStatusBar(
+                                relevantDecor.mTaskInfo,
                                 calculateFreeformBounds(FINAL_FREEFORM_SCALE)));
             }
         });
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 8cb575c..2cd1e1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -42,6 +42,9 @@
 
 import com.android.internal.view.BaseIWindow;
 
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * An input event listener registered to InputDispatcher to receive input events on task edges and
  * and corners. Converts them to drag resize requests.
@@ -211,6 +214,10 @@
                     PRIVATE_FLAG_TRUSTED_OVERLAY,
                     0 /* inputFeatures */,
                     touchRegion);
+            List<Rect> cornersList = Arrays.asList(
+                    mLeftTopCornerBounds, mLeftBottomCornerBounds,
+                    mRightTopCornerBounds, mRightBottomCornerBounds);
+            mWindowSession.reportSystemGestureExclusionChanged(mFakeWindow, cornersList);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 91846fa..e986ee1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -218,36 +218,36 @@
     assertLayers {
         if (landscapePosLeft) {
             splitAppLayerBoundsSnapToDivider(
-                component,
-                landscapePosLeft,
-                portraitPosTop,
-                scenario.endRotation
-            )
-            .then()
-            .isInvisible(component)
-            .then()
-            .splitAppLayerBoundsSnapToDivider(
-                component,
-                landscapePosLeft,
-                portraitPosTop,
-                scenario.endRotation
-            )
+                    component,
+                    landscapePosLeft,
+                    portraitPosTop,
+                    scenario.endRotation
+                )
+                .then()
+                .isInvisible(component)
+                .then()
+                .splitAppLayerBoundsSnapToDivider(
+                    component,
+                    landscapePosLeft,
+                    portraitPosTop,
+                    scenario.endRotation
+                )
         } else {
             splitAppLayerBoundsSnapToDivider(
-                component,
-                landscapePosLeft,
-                portraitPosTop,
-                scenario.endRotation
-            )
-            .then()
-            .isInvisible(component)
-            .then()
-            .splitAppLayerBoundsSnapToDivider(
-                component,
-                landscapePosLeft,
-                portraitPosTop,
-                scenario.endRotation
-            )
+                    component,
+                    landscapePosLeft,
+                    portraitPosTop,
+                    scenario.endRotation
+                )
+                .then()
+                .isInvisible(component)
+                .then()
+                .splitAppLayerBoundsSnapToDivider(
+                    component,
+                    landscapePosLeft,
+                    portraitPosTop,
+                    scenario.endRotation
+                )
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
index d0bca13..2474ecf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
@@ -17,8 +17,8 @@
 package com.android.wm.shell.flicker.bubble
 
 import android.os.SystemClock
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -26,8 +26,6 @@
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiObject2
 import androidx.test.uiautomator.Until
-import org.junit.Assume
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -45,13 +43,8 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FlakyTest(bugId = 217777115)
 open class ChangeActiveActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
-
-    @Before
-    open fun before() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-    }
-
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt
deleted file mode 100644
index 5e85eb8..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt
+++ /dev/null
@@ -1,39 +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.flicker.bubble
-
-import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.isShellTransitionsEnabled
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
-import org.junit.Assume
-import org.junit.Before
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FlakyTest(bugId = 217777115)
-class ChangeActiveActivityFromBubbleTestShellTransit(flicker: FlickerTest) :
-    ChangeActiveActivityFromBubbleTest(flicker) {
-    @Before
-    override fun before() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 1045a5a..93ee699 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -40,6 +40,7 @@
  *     Select "Auto-enter PiP" radio button
  *     Press Home button or swipe up to go Home and put [pipApp] in pip mode
  * ```
+ *
  * Notes:
  * ```
  *     1. All assertions are inherited from [EnterPipTest]
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index 2d2588e..59918fb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -38,6 +38,7 @@
  *     Launch an app in pip mode [pipApp],
  *     Swipe the pip window to the bottom-center of the screen and wait it disappear
  * ```
+ *
  * Notes:
  * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
index 6c5a344..36c6f7c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
@@ -19,7 +19,6 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
 import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.LAUNCHER
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -43,25 +42,17 @@
     @Presubmit
     @Test
     open fun pipWindowBecomesInvisible() {
-        if (isShellTransitionsEnabled) {
-            // When Shell transition is enabled, we change the windowing mode at start, but
-            // update the visibility after the transition is finished, so we can't check isNotPinned
-            // and isAppWindowInvisible in the same assertion block.
-            flicker.assertWm {
-                this.invoke("hasPipWindow") {
-                        it.isPinned(pipApp).isAppWindowVisible(pipApp).isAppWindowOnTop(pipApp)
-                    }
-                    .then()
-                    .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowNotOnTop(pipApp) }
-            }
-            flicker.assertWmEnd { isAppWindowInvisible(pipApp) }
-        } else {
-            flicker.assertWm {
-                this.invoke("hasPipWindow") { it.isPinned(pipApp).isAppWindowVisible(pipApp) }
-                    .then()
-                    .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowInvisible(pipApp) }
-            }
+        // When Shell transition is enabled, we change the windowing mode at start, but
+        // update the visibility after the transition is finished, so we can't check isNotPinned
+        // and isAppWindowInvisible in the same assertion block.
+        flicker.assertWm {
+            this.invoke("hasPipWindow") {
+                    it.isPinned(pipApp).isAppWindowVisible(pipApp).isAppWindowOnTop(pipApp)
+                }
+                .then()
+                .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowNotOnTop(pipApp) }
         }
+        flicker.assertWmEnd { isAppWindowInvisible(pipApp) }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index e540ad5..d165832 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -38,6 +38,7 @@
  *     Click on the pip window
  *     Click on dismiss button and wait window disappear
  * ```
+ *
  * Notes:
  * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index e079d547..db18edb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -53,6 +53,7 @@
  *     Launch [pipApp] on a fixed landscape orientation
  *     Broadcast action [ACTION_ENTER_PIP] to enter pip mode
  * ```
+ *
  * Notes:
  * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
index e40e5ea..51f0136 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
@@ -44,9 +44,7 @@
     @Presubmit
     @Test
     open fun pipAppLayerAlwaysVisible() {
-        flicker.assertLayers {
-            this.isVisible(pipApp)
-        }
+        flicker.assertLayers { this.isVisible(pipApp) }
     }
 
     /** Checks the content overlay appears then disappears during the animation */
@@ -55,11 +53,7 @@
     open fun pipOverlayLayerAppearThenDisappear() {
         val overlay = ComponentNameMatcher.PIP_CONTENT_OVERLAY
         flicker.assertLayers {
-            this.notContains(overlay)
-                .then()
-                .contains(overlay)
-                .then()
-                .notContains(overlay)
+            this.notContains(overlay).then().contains(overlay).then().notContains(overlay)
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index 1f060e9..f1925d8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -35,6 +35,7 @@
  *     Launch an app in full screen
  *     Press an "enter pip" button to put [pipApp] in pip mode
  * ```
+ *
  * Notes:
  * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 313631c..3e0e37d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -16,16 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import androidx.test.filters.RequiresDevice
-import org.junit.Assume
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -42,6 +37,7 @@
  *     Expand [pipApp] app to full screen by clicking on the pip window and
  *     then on the expand button
  * ```
+ *
  * Notes:
  * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -72,19 +68,4 @@
                 wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
             }
         }
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 197726610)
-    @Test
-    override fun pipLayerExpands() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        super.pipLayerExpands()
-    }
-
-    @Presubmit
-    @Test
-    fun pipLayerExpands_ShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-        super.pipLayerExpands()
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index 93ffdd8d..603f995 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -16,16 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import androidx.test.filters.RequiresDevice
-import org.junit.Assume
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -41,6 +36,7 @@
  *     Launch another full screen mode [testApp]
  *     Expand [pipApp] app to full screen via an intent
  * ```
+ *
  * Notes:
  * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -71,37 +67,4 @@
                 wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
             }
         }
-
-    /** {@inheritDoc} */
-    @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun statusBarLayerPositionAtStartAndEnd() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerPositionAtStartAndEnd()
-    }
-
-    @Presubmit
-    @Test
-    fun statusBarLayerRotatesScales_ShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-        super.statusBarLayerPositionAtStartAndEnd()
-    }
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 197726610)
-    @Test
-    override fun pipLayerExpands() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        super.pipLayerExpands()
-    }
-
-    @Presubmit
-    @Test
-    fun pipLayerExpands_ShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-        super.pipLayerExpands()
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index 7d5f740..6deba1b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -40,6 +40,7 @@
  *     Launch an app in pip mode [pipApp],
  *     Expand [pipApp] app to its maximum pip size by double clicking on it
  * ```
+ *
  * Notes:
  * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 9c00744..d8d57d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -40,6 +40,7 @@
  *     Launch [testApp]
  *     Check if pip window moves down (visually)
  * ```
+ *
  * Notes:
  * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index c23838a..a626713 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -19,7 +19,6 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
 import android.tools.common.datatypes.component.ComponentNameMatcher
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -28,8 +27,6 @@
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
-import org.junit.Assume.assumeFalse
-import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -44,11 +41,6 @@
 open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransition(flicker) {
     private val imeApp = ImeAppHelper(instrumentation)
 
-    @Before
-    open fun before() {
-        assumeFalse(isShellTransitionsEnabled)
-    }
-
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestShellTransit.kt
deleted file mode 100644
index 6f81116..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestShellTransit.kt
+++ /dev/null
@@ -1,47 +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.flicker.pip
-
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.isShellTransitionsEnabled
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
-import org.junit.Assume
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MovePipOnImeVisibilityChangeTestShellTransit(flicker: FlickerTest) :
-    MovePipOnImeVisibilityChangeTest(flicker) {
-
-    @Before
-    override fun before() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-    }
-
-    @Presubmit
-    @Test
-    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index c8d5624..ae3f879 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -40,6 +40,7 @@
  *     Press home
  *     Check if pip window moves up (visually)
  * ```
+ *
  * Notes:
  * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
index 083cfd2..4e2a4e7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -30,9 +30,7 @@
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
 
-/**
- * Test the dragging of a PIP window.
- */
+/** Test the dragging of a PIP window. */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@@ -59,9 +57,7 @@
                 pipApp.exit(wmHelper)
                 tapl.setEnableRotation(false)
             }
-            transitions {
-                pipApp.dragPipWindowAwayFromEdgeWithoutRelease(wmHelper, 50)
-            }
+            transitions { pipApp.dragPipWindowAwayFromEdgeWithoutRelease(wmHelper, 50) }
         }
 
     @Postsubmit
@@ -92,4 +88,4 @@
             return FlickerTestFactory.nonRotationTests()
         }
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index 53ce393..9fe9f52 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -16,10 +16,10 @@
 
 package com.android.wm.shell.flicker.pip
 
+import android.graphics.Rect
 import android.platform.test.annotations.Postsubmit
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.graphics.Rect
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -33,14 +33,12 @@
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
 
-/**
- * Test the snapping of a PIP window via dragging, releasing, and checking its final location.
- */
+/** Test the snapping of a PIP window via dragging, releasing, and checking its final location. */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker){
+class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker) {
     // represents the direction in which the pip window should be snapping
     private var willSnapRight: Boolean = true
 
@@ -60,8 +58,12 @@
 
                 // get the initial region bounds and cache them
                 val initRegion = pipApp.getWindowRect(wmHelper)
-                startBounds
-                        .set(initRegion.left, initRegion.top, initRegion.right, initRegion.bottom)
+                startBounds.set(
+                    initRegion.left,
+                    initRegion.top,
+                    initRegion.right,
+                    initRegion.bottom
+                )
 
                 // drag the pip window away from the edge
                 pipApp.dragPipWindowAwayFromEdge(wmHelper, 50)
@@ -108,4 +110,4 @@
             )
         }
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index 2cf8f61..703784d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -43,6 +43,7 @@
  *     Rotate the screen from [flicker.scenario.startRotation] to [flicker.scenario.endRotation]
  *     (usually, 0->90 and 90->0)
  * ```
+ *
  * Notes:
  * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 17f174b..5180791 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -16,9 +16,9 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -107,18 +107,15 @@
         }
     }
 
-    @Presubmit
-    @Test
-    fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp)
+    @Presubmit @Test fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp)
 
     @Presubmit
     @Test
     fun secondaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(secondaryApp)
 
-    @Presubmit
+    @FlakyTest(bugId = 245472831)
     @Test
     fun primaryAppBoundsChanges() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
         flicker.splitAppLayerBoundsChanges(
             primaryApp,
             landscapePosLeft = true,
@@ -135,11 +132,6 @@
             portraitPosTop = true
         )
 
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun entireScreenCovered() = super.entireScreenCovered()
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 5b06c9c..69da1e2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -16,11 +16,11 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -79,21 +79,24 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false,
-            appExistAtStart = false)
+    fun cujCompleted() =
+        flicker.splitScreenEntered(
+            primaryApp,
+            secondaryApp,
+            fromOtherApp = false,
+            appExistAtStart = false
+        )
 
-    @Presubmit
+    @FlakyTest(bugId = 245472831)
     @Test
     fun splitScreenDividerBecomesVisible() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
         flicker.splitScreenDividerBecomesVisible()
     }
 
     // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
     @Presubmit
     @Test
-    fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
+    fun splitScreenDividerIsVisibleAtEnd() {
         flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index c840183..1773846 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -16,11 +16,11 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -82,21 +82,19 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, sendNotificationApp,
-            fromOtherApp = false)
+    fun cujCompleted() =
+        flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false)
 
-    @Presubmit
+    @FlakyTest(bugId = 245472831)
     @Test
     fun splitScreenDividerBecomesVisible() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
         flicker.splitScreenDividerBecomesVisible()
     }
 
     // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
     @Presubmit
     @Test
-    fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
+    fun splitScreenDividerIsVisibleAtEnd() {
         flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
     }
 
@@ -105,23 +103,6 @@
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        flicker.assertLayers {
-            this.isInvisible(sendNotificationApp)
-                .then()
-                .isVisible(sendNotificationApp)
-                .then()
-                .isInvisible(sendNotificationApp)
-                .then()
-                .isVisible(sendNotificationApp)
-        }
-    }
-
-    // TODO(b/245472831): Align to legacy transition after shell transition ready.
-    @Presubmit
-    @Test
-    fun secondaryAppLayerBecomesVisible_ShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
         flicker.layerBecomesVisible(sendNotificationApp)
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 5c99209..3bea66e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -16,11 +16,11 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -80,21 +80,24 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false,
-            appExistAtStart = false)
+    fun cujCompleted() =
+        flicker.splitScreenEntered(
+            primaryApp,
+            secondaryApp,
+            fromOtherApp = false,
+            appExistAtStart = false
+        )
 
-    @Presubmit
+    @FlakyTest(bugId = 245472831)
     @Test
     fun splitScreenDividerBecomesVisible() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
         flicker.splitScreenDividerBecomesVisible()
     }
 
     // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
     @Presubmit
     @Test
-    fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
+    fun splitScreenDividerIsVisibleAtEnd() {
         flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
     }
 
@@ -103,23 +106,6 @@
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        flicker.assertLayers {
-            this.isInvisible(secondaryApp)
-                .then()
-                .isVisible(secondaryApp)
-                .then()
-                .isInvisible(secondaryApp)
-                .then()
-                .isVisible(secondaryApp)
-        }
-    }
-
-    // TODO(b/245472831): Align to legacy transition after shell transition ready.
-    @Presubmit
-    @Test
-    fun secondaryAppLayerBecomesVisible_ShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
         flicker.layerBecomesVisible(secondaryApp)
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 2855c71..9f4cb8c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -20,7 +20,6 @@
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -31,7 +30,6 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -73,19 +71,7 @@
     @Test
     fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
 
-    @FlakyTest
-    @Test
-    fun primaryAppLayerBecomesVisible() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        flicker.layerBecomesVisible(primaryApp)
-    }
-
-    @Presubmit
-    @Test
-    fun primaryAppLayerBecomesVisibleShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-        flicker.layerBecomesVisible(primaryApp)
-    }
+    @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index c29a917..a33d8ca 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -20,7 +20,6 @@
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -31,7 +30,6 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -73,19 +71,7 @@
     @Test
     fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
 
-    @FlakyTest
-    @Test
-    fun primaryAppLayerBecomesVisible() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        flicker.layerBecomesVisible(primaryApp)
-    }
-
-    @Presubmit
-    @Test
-    fun primaryAppLayerBecomesVisibleShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-        flicker.layerBecomesVisible(primaryApp)
-    }
+    @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 4ccc467..c9bd695 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -81,7 +81,8 @@
     @Mock lateinit var syncQueue: SyncTransactionQueue
     @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
     @Mock lateinit var transitions: Transitions
-    @Mock lateinit var transitionHandler: EnterDesktopTaskTransitionHandler
+    @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
+    @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
 
     lateinit var mockitoSession: StaticMockitoSession
     lateinit var controller: DesktopTasksController
@@ -117,7 +118,8 @@
             syncQueue,
             rootTaskDisplayAreaOrganizer,
             transitions,
-            transitionHandler,
+            enterDesktopTransitionHandler,
+            exitDesktopTransitionHandler,
             desktopModeTaskRepository,
             TestShellExecutor()
         )
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
new file mode 100644
index 0000000..2c5a5cd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import static androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.IBinder;
+import android.util.DisplayMetrics;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.IWindowContainerToken;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.transition.Transitions;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.function.Supplier;
+
+/** Tests of {@link com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler} */
+@SmallTest
+public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
+
+    @Mock
+    private Transitions mTransitions;
+    @Mock
+    IBinder mToken;
+    @Mock
+    Supplier<SurfaceControl.Transaction> mTransactionFactory;
+    @Mock
+    Context mContext;
+    @Mock
+    DisplayMetrics mDisplayMetrics;
+    @Mock
+    Resources mResources;
+    @Mock
+    SurfaceControl.Transaction mStartT;
+    @Mock
+    SurfaceControl.Transaction mFinishT;
+    @Mock
+    SurfaceControl.Transaction mAnimationT;
+    @Mock
+    Transitions.TransitionFinishCallback mTransitionFinishCallback;
+    @Mock
+    ShellExecutor mExecutor;
+
+    private ExitDesktopTaskTransitionHandler mExitDesktopTaskTransitionHandler;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(mExecutor).when(mTransitions).getMainExecutor();
+        doReturn(mAnimationT).when(mTransactionFactory).get();
+        doReturn(mResources).when(mContext).getResources();
+        doReturn(mDisplayMetrics).when(mResources).getDisplayMetrics();
+        when(mResources.getDisplayMetrics())
+                .thenReturn(getContext().getResources().getDisplayMetrics());
+
+        mExitDesktopTaskTransitionHandler = new ExitDesktopTaskTransitionHandler(mTransitions,
+                mContext);
+    }
+
+    @Test
+    public void testTransitExitDesktopModeAnimation() throws Throwable {
+        final int transitionType = Transitions.TRANSIT_EXIT_DESKTOP_MODE;
+        final int taskId = 1;
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        doReturn(mToken).when(mTransitions)
+                .startTransition(transitionType, wct, mExitDesktopTaskTransitionHandler);
+
+        mExitDesktopTaskTransitionHandler.startTransition(transitionType, wct);
+
+        TransitionInfo.Change change =
+                createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FULLSCREEN);
+        TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_EXIT_DESKTOP_MODE, change);
+        ArrayList<Exception> exceptions = new ArrayList<>();
+        runOnUiThread(() -> {
+            try {
+                assertTrue(mExitDesktopTaskTransitionHandler
+                        .startAnimation(mToken, info, mStartT, mFinishT,
+                                mTransitionFinishCallback));
+            } catch (Exception e) {
+                exceptions.add(e);
+            }
+        });
+        if (!exceptions.isEmpty()) {
+            throw exceptions.get(0);
+        }
+    }
+
+    private TransitionInfo.Change createChange(@WindowManager.TransitionType int type, int taskId,
+            @WindowConfiguration.WindowingMode int windowingMode) {
+        final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+        taskInfo.taskId = taskId;
+        taskInfo.token = new WindowContainerToken(mock(IWindowContainerToken.class));
+        taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+        SurfaceControl.Builder b = new SurfaceControl.Builder()
+                .setName("test task");
+        final TransitionInfo.Change change = new TransitionInfo.Change(
+                taskInfo.token, b.build());
+        change.setMode(type);
+        change.setTaskInfo(taskInfo);
+        return change;
+    }
+
+    private static TransitionInfo createTransitionInfo(
+            @WindowManager.TransitionType int type, @NonNull TransitionInfo.Change change) {
+        TransitionInfo info = new TransitionInfo(type, 0);
+        info.addChange(change);
+        return info;
+    }
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 5b62a94..ada3455 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -159,7 +159,7 @@
         mPipResizeGestureHandler.onPinchResize(upEvent);
 
         verify(mPipTaskOrganizer, times(1))
-                .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any());
+                .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any(), any());
 
         assertTrue("The new size should be bigger than the original PiP size.",
                 mPipResizeGestureHandler.getLastResizeBounds().width()
@@ -198,7 +198,7 @@
         mPipResizeGestureHandler.onPinchResize(upEvent);
 
         verify(mPipTaskOrganizer, times(1))
-                .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any());
+                .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any(), any());
 
         assertTrue("The new size should be smaller than the original PiP size.",
                 mPipResizeGestureHandler.getLastResizeBounds().width()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 1b29146..a9f311f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -181,7 +181,7 @@
 
         IBinder transition = mSplitScreenTransitions.startEnterTransition(
                 TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
-                new RemoteTransition(testRemote), mStageCoordinator, null, null);
+                new RemoteTransition(testRemote, "Test"), mStageCoordinator, null, null);
         mMainStage.onTaskAppeared(mMainChild, createMockSurface());
         mSideStage.onTaskAppeared(mSideChild, createMockSurface());
         boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -407,7 +407,8 @@
         TransitionInfo enterInfo = createEnterPairInfo();
         IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
                 TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
-                new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null, null);
+                new RemoteTransition(new TestRemoteTransition(), "Test"),
+                mStageCoordinator, null, null);
         mMainStage.onTaskAppeared(mMainChild, createMockSurface());
         mSideStage.onTaskAppeared(mSideChild, createMockSurface());
         mStageCoordinator.startAnimation(enterTransit, enterInfo,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 60d6978..5cd548b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -277,7 +277,7 @@
         IBinder transitToken = new Binder();
         transitions.requestStartTransition(transitToken,
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */,
-                        new RemoteTransition(testRemote)));
+                        new RemoteTransition(testRemote, "Test")));
         verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
         TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
@@ -422,7 +422,7 @@
                 new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
         filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
 
-        transitions.registerRemote(filter, new RemoteTransition(testRemote));
+        transitions.registerRemote(filter, new RemoteTransition(testRemote, "Test"));
         mMainExecutor.flushAll();
 
         IBinder transitToken = new Binder();
@@ -466,11 +466,12 @@
         final int transitType = TRANSIT_FIRST_CUSTOM + 1;
 
         OneShotRemoteHandler oneShot = new OneShotRemoteHandler(mMainExecutor,
-                new RemoteTransition(testRemote));
+                new RemoteTransition(testRemote, "Test"));
         // Verify that it responds to the remote but not other things.
         IBinder transitToken = new Binder();
         assertNotNull(oneShot.handleRequest(transitToken,
-                new TransitionRequestInfo(transitType, null, new RemoteTransition(testRemote))));
+                new TransitionRequestInfo(transitType, null,
+                        new RemoteTransition(testRemote, "Test"))));
         assertNull(oneShot.handleRequest(transitToken,
                 new TransitionRequestInfo(transitType, null, null)));
 
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 3b12972..5d79104 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -738,6 +738,9 @@
         "tests/unit/VectorDrawableTests.cpp",
         "tests/unit/WebViewFunctorManagerTests.cpp",
     ],
+    data: [
+        ":hwuimicro",
+    ],
 }
 
 // ------------------------
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
index 768dfcd..706f18c 100644
--- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -85,28 +85,20 @@
 }
 
 static SkMatrix createMatrixFromBufferTransform(SkScalar width, SkScalar height, int transform) {
-    auto matrix = SkMatrix();
     switch (transform) {
         case ANATIVEWINDOW_TRANSFORM_ROTATE_90:
-            matrix.setRotate(90);
-            matrix.postTranslate(width, 0);
-            break;
+            return SkMatrix::MakeAll(0, -1, height, 1, 0, 0, 0, 0, 1);
         case ANATIVEWINDOW_TRANSFORM_ROTATE_180:
-            matrix.setRotate(180);
-            matrix.postTranslate(width, height);
-            break;
+            return SkMatrix::MakeAll(-1, 0, width, 0, -1, height, 0, 0, 1);
         case ANATIVEWINDOW_TRANSFORM_ROTATE_270:
-            matrix.setRotate(270);
-            matrix.postTranslate(0, width);
-            break;
+            return SkMatrix::MakeAll(0, 1, 0, -1, 0, width, 0, 0, 1);
         default:
             ALOGE("Invalid transform provided. Transform should be validated from"
                   "the java side. Leveraging identity transform as a fallback");
             [[fallthrough]];
         case ANATIVEWINDOW_TRANSFORM_IDENTITY:
-            break;
+            return SkMatrix::I();
     }
-    return matrix;
 }
 
 static int android_graphics_HardwareBufferRenderer_render(JNIEnv* env, jobject, jlong renderProxy,
@@ -117,8 +109,8 @@
     auto skHeight = static_cast<SkScalar>(height);
     auto matrix = createMatrixFromBufferTransform(skWidth, skHeight, transform);
     auto colorSpace = GraphicsJNI::getNativeColorSpace(colorspacePtr);
-    proxy->setHardwareBufferRenderParams(
-            HardwareBufferRenderParams(matrix, colorSpace, createRenderCallback(env, consumer)));
+    proxy->setHardwareBufferRenderParams(HardwareBufferRenderParams(
+            width, height, matrix, colorSpace, createRenderCallback(env, consumer)));
     nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC);
     UiFrameInfoBuilder(proxy->frameInfo())
                 .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID,
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 202a62c..cc987bc 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -69,15 +69,9 @@
 }
 
 Frame SkiaOpenGLPipeline::getFrame() {
-    if (mHardwareBuffer) {
-        AHardwareBuffer_Desc description;
-        AHardwareBuffer_describe(mHardwareBuffer, &description);
-        return Frame(description.width, description.height, 0);
-    } else {
-        LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
-                            "drawRenderNode called on a context with no surface!");
-        return mEglManager.beginFrame(mEglSurface);
-    }
+    LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
+                        "drawRenderNode called on a context with no surface!");
+    return mEglManager.beginFrame(mEglSurface);
 }
 
 IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 99298bc..c8f2e69 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -66,15 +66,8 @@
 }
 
 Frame SkiaVulkanPipeline::getFrame() {
-    if (mHardwareBuffer) {
-        AHardwareBuffer_Desc description;
-        AHardwareBuffer_describe(mHardwareBuffer, &description);
-        return Frame(description.width, description.height, 0);
-    } else {
-        LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr,
-                            "getFrame() called on a context with no surface!");
-        return vulkanManager().dequeueNextBuffer(mVkSurface);
-    }
+    LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr, "getFrame() called on a context with no surface!");
+    return vulkanManager().dequeueNextBuffer(mVkSurface);
 }
 
 IRenderPipeline::DrawResult SkiaVulkanPipeline::draw(
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index dd781bb..6b2c995 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -528,6 +528,14 @@
     sendLoadResetHint();
 }
 
+Frame CanvasContext::getFrame() {
+    if (mHardwareBuffer != nullptr) {
+        return {mBufferParams.getLogicalWidth(), mBufferParams.getLogicalHeight(), 0};
+    } else {
+        return mRenderPipeline->getFrame();
+    }
+}
+
 void CanvasContext::draw() {
     if (auto grContext = getGrContext()) {
         if (grContext->abandoned()) {
@@ -569,7 +577,8 @@
 
     mCurrentFrameInfo->markIssueDrawCommandsStart();
 
-    Frame frame = mRenderPipeline->getFrame();
+    Frame frame = getFrame();
+
     SkRect windowDirty = computeDirtyRect(frame, &dirty);
 
     ATRACE_FORMAT("Drawing " RECT_STRING, SK_RECT_ARGS(dirty));
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index b26c018..3f25339 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -264,6 +264,8 @@
 
     FrameInfo* getFrameInfoFromLast4(uint64_t frameNumber, uint32_t surfaceControlId);
 
+    Frame getFrame();
+
     // The same type as Frame.mWidth and Frame.mHeight
     int32_t mLastFrameWidth = 0;
     int32_t mLastFrameHeight = 0;
diff --git a/libs/hwui/renderthread/HardwareBufferRenderParams.h b/libs/hwui/renderthread/HardwareBufferRenderParams.h
index 91fe3f6..8c942d0 100644
--- a/libs/hwui/renderthread/HardwareBufferRenderParams.h
+++ b/libs/hwui/renderthread/HardwareBufferRenderParams.h
@@ -36,9 +36,12 @@
 class HardwareBufferRenderParams {
 public:
     HardwareBufferRenderParams() = default;
-    HardwareBufferRenderParams(const SkMatrix& transform, const sk_sp<SkColorSpace>& colorSpace,
+    HardwareBufferRenderParams(int32_t logicalWidth, int32_t logicalHeight,
+                               const SkMatrix& transform, const sk_sp<SkColorSpace>& colorSpace,
                                RenderCallback&& callback)
-            : mTransform(transform)
+            : mLogicalWidth(logicalWidth)
+            , mLogicalHeight(logicalHeight)
+            , mTransform(transform)
             , mColorSpace(colorSpace)
             , mRenderCallback(std::move(callback)) {}
     const SkMatrix& getTransform() const { return mTransform; }
@@ -50,7 +53,12 @@
         }
     }
 
+    int32_t getLogicalWidth() { return mLogicalWidth; }
+    int32_t getLogicalHeight() { return mLogicalHeight; }
+
 private:
+    int32_t mLogicalWidth;
+    int32_t mLogicalHeight;
     SkMatrix mTransform = SkMatrix::I();
     sk_sp<SkColorSpace> mColorSpace = SkColorSpace::MakeSRGB();
     RenderCallback mRenderCallback = nullptr;
diff --git a/location/java/android/location/GnssMeasurementRequest.java b/location/java/android/location/GnssMeasurementRequest.java
index 3813e97..3f3ad75 100644
--- a/location/java/android/location/GnssMeasurementRequest.java
+++ b/location/java/android/location/GnssMeasurementRequest.java
@@ -135,8 +135,12 @@
     public String toString() {
         StringBuilder s = new StringBuilder();
         s.append("GnssMeasurementRequest[");
-        s.append("@");
-        TimeUtils.formatDuration(mIntervalMillis, s);
+        if (mIntervalMillis == PASSIVE_INTERVAL) {
+            s.append("passive");
+        } else {
+            s.append("@");
+            TimeUtils.formatDuration(mIntervalMillis, s);
+        }
         if (mFullTracking) {
             s.append(", FullTracking");
         }
diff --git a/media/aidl/android/media/soundtrigger_middleware/IInjectGlobalEvent.aidl b/media/aidl/android/media/soundtrigger_middleware/IInjectGlobalEvent.aidl
index dcf3945..47d8426 100644
--- a/media/aidl/android/media/soundtrigger_middleware/IInjectGlobalEvent.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/IInjectGlobalEvent.aidl
@@ -25,14 +25,14 @@
 oneway interface IInjectGlobalEvent {
 
     /**
-     * Request a fake STHAL restart.
+     * Trigger a fake STHAL restart.
      * This invalidates the {@link IInjectGlobalEvent}.
      */
     void triggerRestart();
 
     /**
-     * Triggers global resource contention into the fake STHAL. Loads/startRecognition
-     * will fail with RESOURCE_CONTENTION.
+     * Set global resource contention within the fake STHAL. Loads/startRecognition
+     * will fail with {@code RESOURCE_CONTENTION} until unset.
      * @param isContended - true to enable resource contention. false to disable resource contention
      *                      and resume normal functionality.
      * @param callback - Call {@link IAcknowledgeEvent#eventReceived()} on this interface once
@@ -40,4 +40,11 @@
      */
     void setResourceContention(boolean isContended, IAcknowledgeEvent callback);
 
+    /**
+     * Trigger an
+     * {@link android.hardware.soundtrigger3.ISoundTriggerHwGlobalCallback#onResourcesAvailable}
+     * callback from the fake STHAL. This callback is used to signal to the framework that
+     * previous operations which failed may now succeed.
+     */
+    void triggerOnResourcesAvailable();
 }
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 37050df..62c4a51 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -29,6 +29,7 @@
 import android.media.AudioManager;
 import android.media.MediaMetadata;
 import android.media.Rating;
+import android.media.RoutingSessionInfo;
 import android.media.VolumeProvider;
 import android.media.VolumeProvider.ControlType;
 import android.media.session.MediaSession.QueueItem;
@@ -1001,8 +1002,11 @@
          * @param maxVolume The max volume. Should be equal or greater than zero.
          * @param currentVolume The current volume. Should be in the interval [0, maxVolume].
          * @param audioAttrs The audio attributes for this playback. Should not be null.
-         * @param volumeControlId The volume control ID. This is used for matching
-         *                        {@link RoutingSessionInfo} and {@link MediaSession}.
+         * @param volumeControlId The {@link RoutingSessionInfo#getId() routing session id} of the
+         *     {@link RoutingSessionInfo} associated with this controller, or null if not
+         *     applicable. This id allows mapping this controller to a routing session which, when
+         *     applicable, provides information about the remote device, and support for volume
+         *     adjustment.
          * @hide
          */
         @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
diff --git a/native/android/tests/activitymanager/nativeTests/Android.bp b/native/android/tests/activitymanager/nativeTests/Android.bp
index 528ac12..ebd7533 100644
--- a/native/android/tests/activitymanager/nativeTests/Android.bp
+++ b/native/android/tests/activitymanager/nativeTests/Android.bp
@@ -45,4 +45,7 @@
     required: [
         "UidImportanceHelperApp",
     ],
+    data: [
+        ":UidImportanceHelperApp",
+    ],
 }
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index c898fe5..d87abb9 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -20,7 +20,7 @@
     <string name="app_label">Companion Device Manager</string>
 
     <!-- Title of the device association confirmation dialog. -->
-    <string name="confirmation_title">Allow &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g>&lt;/strong&gt;</string>
+    <string name="confirmation_title">Allow &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g>&lt;/strong&gt;?</string>
 
     <!-- ================= DEVICE_PROFILE_WATCH and null profile ================= -->
 
@@ -34,7 +34,7 @@
     <string name="summary_watch">This app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
 
     <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] -->
-    <string name="summary_watch_single_device">This app will be allowed to sync info, like the name of someone calling, and access these permissions</string>
+    <string name="summary_watch_single_device">This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string>
 
     <!-- ================= DEVICE_PROFILE_GLASSES ================= -->
 
@@ -48,7 +48,7 @@
     <string name="summary_glasses_multi_device">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
 
     <!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] -->
-    <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your phone</string>
+    <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string>
 
     <!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
 
@@ -194,4 +194,10 @@
     <!-- Description of nearby_device_streaming permission of corresponding profile [CHAR LIMIT=NONE] -->
     <string name="permission_nearby_device_streaming_summary">Stream apps and other system features from your phone</string>
 
+    <!-- The type of the device for phone [CHAR LIMIT=30] -->
+    <string name="device_type" product="default">phone</string>
+
+    <!-- The type of the device for tablet [CHAR LIMIT=30] -->
+    <string name="device_type" product="tablet">tablet</string>
+
 </resources>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index ae08823..4154029 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -551,7 +551,8 @@
             summary = getHtmlFromResources(this, SUMMARIES.get(null), deviceName);
             mConstraintList.setVisibility(View.GONE);
         } else {
-            summary = getHtmlFromResources(this, SUMMARIES.get(deviceProfile));
+            summary = getHtmlFromResources(
+                    this, SUMMARIES.get(deviceProfile), getString(R.string.device_type));
             mPermissionTypes.addAll(PERMISSION_TYPES.get(deviceProfile));
             setupPermissionList();
         }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 28f9453..452455c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -110,6 +110,11 @@
             ResultReceiver::class.java
         )
 
+        val cancellationRequest = getCancelUiRequest(intent)
+        val cancelUiRequestState = cancellationRequest?.let {
+            CancelUiRequestState(getAppLabel(context.getPackageManager(), it.appPackageName))
+        }
+
         initialUiState = when (requestInfo.type) {
             RequestInfo.TYPE_CREATE -> {
                 val defaultProviderId = userConfigRepo.getDefaultProviderId()
@@ -128,6 +133,7 @@
                         isPasskeyFirstUse
                     )!!,
                     getCredentialUiState = null,
+                    cancelRequestState = cancelUiRequestState
                 )
             }
             RequestInfo.TYPE_GET -> {
@@ -142,6 +148,7 @@
                     if (autoSelectEntry == null) ProviderActivityState.NOT_APPLICABLE
                     else ProviderActivityState.READY_TO_LAUNCH,
                     isAutoSelectFlow = autoSelectEntry != null,
+                    cancelRequestState = cancelUiRequestState
                 )
             }
             else -> throw IllegalStateException("Unrecognized request type: ${requestInfo.type}")
@@ -238,12 +245,12 @@
             }
         }
 
-        /** Return the request token whose UI should be cancelled, or null otherwise. */
-        fun getCancelUiRequestToken(intent: Intent): IBinder? {
+        /** Return the cancellation request if present. */
+        fun getCancelUiRequest(intent: Intent): CancelUiRequest? {
             return intent.extras?.getParcelable(
                 CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
                 CancelUiRequest::class.java
-            )?.token
+            )
         }
     }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 5d72424..2efe1be 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -30,11 +30,13 @@
 import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.res.stringResource
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.android.credentialmanager.common.Constants
 import com.android.credentialmanager.common.DialogState
 import com.android.credentialmanager.common.ProviderActivityResult
 import com.android.credentialmanager.common.StartBalIntentSenderForResultContract
+import com.android.credentialmanager.common.ui.Snackbar
 import com.android.credentialmanager.createflow.CreateCredentialScreen
 import com.android.credentialmanager.createflow.hasContentToDisplay
 import com.android.credentialmanager.getflow.GetCredentialScreen
@@ -49,10 +51,9 @@
         super.onCreate(savedInstanceState)
         Log.d(Constants.LOG_TAG, "Creating new CredentialSelectorActivity")
         try {
-            if (CredentialManagerRepo.getCancelUiRequestToken(intent) != null) {
-                Log.d(
-                    Constants.LOG_TAG, "Received UI cancellation intent; cancelling the activity.")
-                this.finish()
+            val (isCancellationRequest, shouldShowCancellationUi, _) =
+                maybeCancelUIUponRequest(intent)
+            if (isCancellationRequest && !shouldShowCancellationUi) {
                 return
             }
             val userConfigRepo = UserConfigRepo(this)
@@ -75,14 +76,15 @@
         setIntent(intent)
         Log.d(Constants.LOG_TAG, "Existing activity received new intent")
         try {
-            val cancelUiRequestToken = CredentialManagerRepo.getCancelUiRequestToken(intent)
             val viewModel: CredentialSelectorViewModel by viewModels()
-            if (cancelUiRequestToken != null &&
-                viewModel.shouldCancelCurrentUi(cancelUiRequestToken)) {
-                Log.d(
-                    Constants.LOG_TAG, "Received UI cancellation intent; cancelling the activity.")
-                this.finish()
-                return
+            val (isCancellationRequest, shouldShowCancellationUi, appDisplayName) =
+                maybeCancelUIUponRequest(intent, viewModel)
+            if (isCancellationRequest) {
+                if (shouldShowCancellationUi) {
+                    viewModel.onCancellationUiRequested(appDisplayName)
+                } else {
+                    return
+                }
             } else {
                 val userConfigRepo = UserConfigRepo(this)
                 val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
@@ -93,11 +95,41 @@
         }
     }
 
+    /**
+     * Cancels the UI activity if requested by the backend. Different from the other finishing
+     * helpers, this does not report anything back to the Credential Manager service backend.
+     *
+     * Can potentially show a transient snackbar before finishing, if the request specifies so.
+     *
+     * Returns <isCancellationRequest, shouldShowCancellationUi, appDisplayName>.
+     */
+    private fun maybeCancelUIUponRequest(
+        intent: Intent,
+        viewModel: CredentialSelectorViewModel? = null
+    ): Triple<Boolean, Boolean, String?> {
+        val cancelUiRequest = CredentialManagerRepo.getCancelUiRequest(intent)
+            ?: return Triple(false, false, null)
+        if (viewModel != null && !viewModel.shouldCancelCurrentUi(cancelUiRequest.token)) {
+            // Cancellation was for a different request, don't cancel the current UI.
+            return Triple(false, false, null)
+        }
+        val shouldShowCancellationUi = cancelUiRequest.shouldShowCancellationUi()
+        Log.d(
+            Constants.LOG_TAG, "Received UI cancellation intent. Should show cancellation" +
+            " ui = $shouldShowCancellationUi")
+        val appDisplayName = getAppLabel(packageManager, cancelUiRequest.appPackageName)
+        if (!shouldShowCancellationUi) {
+            this.finish()
+        }
+        return Triple(true, shouldShowCancellationUi, appDisplayName)
+    }
+
+
     @ExperimentalMaterialApi
     @Composable
-    fun CredentialManagerBottomSheet(
+    private fun CredentialManagerBottomSheet(
         credManRepo: CredentialManagerRepo,
-        userConfigRepo: UserConfigRepo
+        userConfigRepo: UserConfigRepo,
     ) {
         val viewModel: CredentialSelectorViewModel = viewModel {
             CredentialSelectorViewModel(credManRepo, userConfigRepo)
@@ -113,7 +145,17 @@
 
         val createCredentialUiState = viewModel.uiState.createCredentialUiState
         val getCredentialUiState = viewModel.uiState.getCredentialUiState
-        if (createCredentialUiState != null && hasContentToDisplay(createCredentialUiState)) {
+        val cancelRequestState = viewModel.uiState.cancelRequestState
+        if (cancelRequestState != null) {
+            if (cancelRequestState.appDisplayName == null) {
+                Log.d(Constants.LOG_TAG, "Received UI cancel request with an invalid package name.")
+                this.finish()
+                return
+            } else {
+                UiCancellationScreen(cancelRequestState.appDisplayName)
+            }
+        } else if (
+            createCredentialUiState != null && hasContentToDisplay(createCredentialUiState)) {
             CreateCredentialScreen(
                 viewModel = viewModel,
                 createCredentialUiState = createCredentialUiState,
@@ -122,15 +164,15 @@
         } else if (getCredentialUiState != null && hasContentToDisplay(getCredentialUiState)) {
             if (isFallbackScreen(getCredentialUiState)) {
                 GetGenericCredentialScreen(
-                        viewModel = viewModel,
-                        getCredentialUiState = getCredentialUiState,
-                        providerActivityLauncher = launcher
+                    viewModel = viewModel,
+                    getCredentialUiState = getCredentialUiState,
+                    providerActivityLauncher = launcher
                 )
             } else {
                 GetCredentialScreen(
-                        viewModel = viewModel,
-                        getCredentialUiState = getCredentialUiState,
-                        providerActivityLauncher = launcher
+                    viewModel = viewModel,
+                    getCredentialUiState = getCredentialUiState,
+                    providerActivityLauncher = launcher
                 )
             }
         } else {
@@ -172,4 +214,13 @@
         )
         this.finish()
     }
+
+    @Composable
+    private fun UiCancellationScreen(appDisplayName: String) {
+        Snackbar(
+            contentText = stringResource(R.string.request_cancelled_by, appDisplayName),
+            onDismiss = { this@CredentialSelectorActivity.finish() },
+            dismissOnTimeout = true,
+        )
+    }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index e49e3f1..7eb3bf4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -51,6 +51,11 @@
     // True if the UI has one and only one auto selectable entry. Its provider activity will be
     // launched immediately, and canceling it will cancel the whole UI flow.
     val isAutoSelectFlow: Boolean = false,
+    val cancelRequestState: CancelUiRequestState?,
+)
+
+data class CancelUiRequestState(
+    val appDisplayName: String?,
 )
 
 class CredentialSelectorViewModel(
@@ -76,6 +81,10 @@
         uiState = uiState.copy(dialogState = DialogState.COMPLETE)
     }
 
+    fun onCancellationUiRequested(appDisplayName: String?) {
+        uiState = uiState.copy(cancelRequestState = CancelUiRequestState(appDisplayName))
+    }
+
     /** Close the activity and don't report anything to the backend.
      *  Example use case is the no-auth-info snackbar where the activity should simply display the
      *  UI and then be dismissed. */
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 783cf3b..43da980 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -64,7 +64,7 @@
 import org.json.JSONObject
 
 // TODO: remove all !! checks
-private fun getAppLabel(
+fun getAppLabel(
     pm: PackageManager,
     appPackageName: String
 ): String? {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
index 514ff90..dfff3d6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
@@ -30,20 +30,24 @@
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalAccessibilityManager
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import com.android.credentialmanager.R
 import com.android.credentialmanager.common.material.Scrim
 import com.android.credentialmanager.ui.theme.Shapes
+import kotlinx.coroutines.delay
 
 @Composable
 fun Snackbar(
     contentText: String,
     action: (@Composable () -> Unit)? = null,
     onDismiss: () -> Unit,
+    dismissOnTimeout: Boolean = false,
 ) {
     BoxWithConstraints {
         Box(Modifier.fillMaxSize()) {
@@ -89,4 +93,20 @@
             }
         }
     }
+    val accessibilityManager = LocalAccessibilityManager.current
+    LaunchedEffect(true) {
+        if (dismissOnTimeout) {
+            // Same as SnackbarDuration.Short
+            val originalDuration = 4000L
+            val duration = if (accessibilityManager == null) originalDuration else
+                accessibilityManager.calculateRecommendedTimeoutMillis(
+                    originalDuration,
+                    containsIcons = true,
+                    containsText = true,
+                    containsControls = action != null,
+                )
+            delay(duration)
+            onDismiss()
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index a3d632c..e884cf8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -305,10 +305,11 @@
         synchronized (mProfileLock) {
             if (getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
                 for (CachedBluetoothDevice member : getMemberDevice()) {
-                    Log.d(TAG, "Disconnect the member(" + member.getAddress() + ")");
+                    Log.d(TAG, "Disconnect the member:" + member);
                     member.disconnect();
                 }
             }
+            Log.d(TAG, "Disconnect " + this);
             mDevice.disconnect();
         }
         // Disconnect  PBAP server in case its connected
@@ -440,11 +441,11 @@
                 Log.d(TAG, "No profiles. Maybe we will connect later for device " + mDevice);
                 return;
             }
-
+            Log.d(TAG, "connect " + this);
             mDevice.connect();
             if (getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
                 for (CachedBluetoothDevice member : getMemberDevice()) {
-                    Log.d(TAG, "connect the member(" + member.getAddress() + ")");
+                    Log.d(TAG, "connect the member:" + member);
                     member.connect();
                 }
             }
@@ -530,7 +531,7 @@
     }
 
     // TODO: do any of these need to run async on a background thread?
-    private void fillData() {
+    void fillData() {
         updateProfiles();
         fetchActiveDevices();
         migratePhonebookPermissionChoice();
@@ -933,14 +934,15 @@
 
     @Override
     public String toString() {
-        return "CachedBluetoothDevice ("
+        return "CachedBluetoothDevice{"
                 + "anonymizedAddress="
                 + mDevice.getAnonymizedAddress()
                 + ", name="
                 + getName()
                 + ", groupId="
                 + mGroupId
-                + ")";
+                + ", member=" + mMemberDevices
+                + "}";
     }
 
     @Override
@@ -1482,6 +1484,7 @@
      * Store the member devices that are in the same coordinated set.
      */
     public void addMemberDevice(CachedBluetoothDevice memberDevice) {
+        Log.d(TAG, this + " addMemberDevice = " + memberDevice);
         mMemberDevices.add(memberDevice);
     }
 
@@ -1511,13 +1514,14 @@
         mDevice = newMainDevice.mDevice;
         mRssi = newMainDevice.mRssi;
         mJustDiscovered = newMainDevice.mJustDiscovered;
+        fillData();
 
         // Set sub device from backup
         newMainDevice.release();
         newMainDevice.mDevice = tmpDevice;
         newMainDevice.mRssi = tmpRssi;
         newMainDevice.mJustDiscovered = tmpJustDiscovered;
-        fetchActiveDevices();
+        newMainDevice.fillData();
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index 20a6cd8..356bb82 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -71,7 +71,7 @@
                 return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
             }
 
-            for (Map.Entry<Integer, ParcelUuid> entry: groupIdMap.entrySet()) {
+            for (Map.Entry<Integer, ParcelUuid> entry : groupIdMap.entrySet()) {
                 if (entry.getValue().equals(BluetoothUuid.CAP)) {
                     return entry.getKey();
                 }
@@ -153,72 +153,13 @@
             return;
         }
         log("onGroupIdChanged: mCachedDevices list =" + mCachedDevices.toString());
-        final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
-        final CachedBluetoothDeviceManager deviceManager = mBtManager.getCachedDeviceManager();
-        final LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile();
-        final BluetoothDevice mainBluetoothDevice = (leAudioProfile != null && isAtLeastT()) ?
-                leAudioProfile.getConnectedGroupLeadDevice(groupId) : null;
+        List<CachedBluetoothDevice> memberDevicesList = getMemberDevicesList(groupId);
         CachedBluetoothDevice newMainDevice =
-                mainBluetoothDevice != null ? deviceManager.findDevice(mainBluetoothDevice) : null;
-        if (newMainDevice != null) {
-            final CachedBluetoothDevice finalNewMainDevice = newMainDevice;
-            final List<CachedBluetoothDevice> memberDevices = mCachedDevices.stream()
-                    .filter(cachedDevice -> !cachedDevice.equals(finalNewMainDevice)
-                            && cachedDevice.getGroupId() == groupId)
-                    .collect(Collectors.toList());
-            if (memberDevices == null || memberDevices.isEmpty()) {
-                log("onGroupIdChanged: There is no member device in list.");
-                return;
-            }
-            log("onGroupIdChanged: removed from UI device =" + memberDevices
-                    + ", with groupId=" + groupId + " mainDevice= " + newMainDevice);
-            for (CachedBluetoothDevice memberDeviceItem : memberDevices) {
-                Set<CachedBluetoothDevice> memberSet = memberDeviceItem.getMemberDevice();
-                if (!memberSet.isEmpty()) {
-                    log("onGroupIdChanged: Transfer the member list into new main device.");
-                    for (CachedBluetoothDevice memberListItem : memberSet) {
-                        if (!memberListItem.equals(newMainDevice)) {
-                            newMainDevice.addMemberDevice(memberListItem);
-                        }
-                    }
-                    memberSet.clear();
-                }
+                getPreferredMainDeviceWithoutConectionState(groupId, memberDevicesList);
 
-                newMainDevice.addMemberDevice(memberDeviceItem);
-                mCachedDevices.remove(memberDeviceItem);
-                mBtManager.getEventManager().dispatchDeviceRemoved(memberDeviceItem);
-            }
-
-            if (!mCachedDevices.contains(newMainDevice)) {
-                mCachedDevices.add(newMainDevice);
-                mBtManager.getEventManager().dispatchDeviceAdded(newMainDevice);
-            }
-        } else {
-            log("onGroupIdChanged: There is no main device from the LE profile.");
-            int firstMatchedIndex = -1;
-
-            for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
-                final CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
-                if (cachedDevice.getGroupId() != groupId) {
-                    continue;
-                }
-
-                if (firstMatchedIndex == -1) {
-                    // Found the first one
-                    firstMatchedIndex = i;
-                    newMainDevice = cachedDevice;
-                    continue;
-                }
-
-                log("onGroupIdChanged: removed from UI device =" + cachedDevice
-                        + ", with groupId=" + groupId + " firstMatchedIndex=" + firstMatchedIndex);
-
-                newMainDevice.addMemberDevice(cachedDevice);
-                mCachedDevices.remove(i);
-                mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
-                break;
-            }
-        }
+        log("onGroupIdChanged: The mainDevice= " + newMainDevice
+                + " and the memberDevicesList of groupId= " + groupId + " =" + memberDevicesList);
+        addMemberDevicesIntoMainDevice(memberDevicesList, newMainDevice);
     }
 
     // @return {@code true}, the event is processed inside the method. It is for updating
@@ -263,7 +204,7 @@
                     break;
                 }
 
-                for (CachedBluetoothDevice device: memberSet) {
+                for (CachedBluetoothDevice device : memberSet) {
                     if (device.isConnected()) {
                         log("set device: " + device + " as the main device");
                         // Main device is disconnected and sub device is connected
@@ -296,7 +237,7 @@
                     continue;
                 }
 
-                for (CachedBluetoothDevice memberDevice: memberSet) {
+                for (CachedBluetoothDevice memberDevice : memberSet) {
                     if (memberDevice != null && memberDevice.equals(device)) {
                         return cachedDevice;
                     }
@@ -310,7 +251,6 @@
      * Check if the {@code groupId} is existed.
      *
      * @param groupId The group id
-     *
      * @return {@code true}, if we could find a device with this {@code groupId}; Otherwise,
      * return {@code false}.
      */
@@ -322,6 +262,116 @@
         return false;
     }
 
+    private List<CachedBluetoothDevice> getMemberDevicesList(int groupId) {
+        return mCachedDevices.stream()
+                .filter(cacheDevice -> cacheDevice.getGroupId() == groupId)
+                .collect(Collectors.toList());
+    }
+
+    private CachedBluetoothDevice getPreferredMainDeviceWithoutConectionState(int groupId,
+            List<CachedBluetoothDevice> memberDevicesList) {
+        // First, priority connected lead device from LE profile
+        // Second, the DUAL mode device which has A2DP/HFP and LE audio
+        // Last, any one of LE device in the list.
+        if (memberDevicesList == null || memberDevicesList.isEmpty()) {
+            return null;
+        }
+
+        final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
+        final CachedBluetoothDeviceManager deviceManager = mBtManager.getCachedDeviceManager();
+        final LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile();
+        final BluetoothDevice mainBluetoothDevice = (leAudioProfile != null && isAtLeastT())
+                ? leAudioProfile.getConnectedGroupLeadDevice(groupId) : null;
+
+        if (mainBluetoothDevice != null) {
+            log("getPreferredMainDevice: The LeadDevice from LE profile is "
+                    + mainBluetoothDevice.getAnonymizedAddress());
+        }
+
+        // 1st
+        CachedBluetoothDevice newMainDevice =
+                mainBluetoothDevice != null ? deviceManager.findDevice(mainBluetoothDevice) : null;
+        if (newMainDevice != null) {
+            if (newMainDevice.isConnected()) {
+                log("getPreferredMainDevice: The connected LeadDevice from LE profile");
+                return newMainDevice;
+            } else {
+                log("getPreferredMainDevice: The LeadDevice is not connect.");
+            }
+        } else {
+            log("getPreferredMainDevice: The LeadDevice is not in the all of devices list");
+        }
+
+        // 2nd
+        newMainDevice = memberDevicesList.stream()
+                .filter(cachedDevice -> cachedDevice.getConnectableProfiles().stream()
+                        .anyMatch(profile -> profile instanceof A2dpProfile
+                                || profile instanceof HeadsetProfile))
+                .findFirst().orElse(null);
+        if (newMainDevice != null) {
+            log("getPreferredMainDevice: The DUAL mode device");
+            return newMainDevice;
+        }
+
+        // last
+        if (!memberDevicesList.isEmpty()) {
+            newMainDevice = memberDevicesList.get(0);
+        }
+        return newMainDevice;
+    }
+
+    private void addMemberDevicesIntoMainDevice(List<CachedBluetoothDevice> memberDevicesList,
+            CachedBluetoothDevice newMainDevice) {
+        if (newMainDevice == null) {
+            log("addMemberDevicesIntoMainDevice: No main device. Do nothing.");
+            return;
+        }
+        if (memberDevicesList.isEmpty()) {
+            log("addMemberDevicesIntoMainDevice: No member device in list. Do nothing.");
+            return;
+        }
+        CachedBluetoothDevice mainDeviceOfNewMainDevice = findMainDevice(newMainDevice);
+        boolean isMemberInOtherMainDevice = mainDeviceOfNewMainDevice != null;
+        if (!memberDevicesList.contains(newMainDevice) && isMemberInOtherMainDevice) {
+            log("addMemberDevicesIntoMainDevice: The 'new main device' is not in list, and it is "
+                    + "the member at other device. Do switch main and member.");
+            // To switch content and dispatch to notify UI change
+            mBtManager.getEventManager().dispatchDeviceRemoved(mainDeviceOfNewMainDevice);
+            mainDeviceOfNewMainDevice.switchMemberDeviceContent(newMainDevice);
+            mainDeviceOfNewMainDevice.refresh();
+            // It is necessary to do remove and add for updating the mapping on
+            // preference and device
+            mBtManager.getEventManager().dispatchDeviceAdded(mainDeviceOfNewMainDevice);
+        } else {
+            log("addMemberDevicesIntoMainDevice: Set new main device");
+            for (CachedBluetoothDevice memberDeviceItem : memberDevicesList) {
+                if (memberDeviceItem.equals(newMainDevice)) {
+                    continue;
+                }
+                Set<CachedBluetoothDevice> memberSet = memberDeviceItem.getMemberDevice();
+                if (!memberSet.isEmpty()) {
+                    for (CachedBluetoothDevice memberSetItem : memberSet) {
+                        if (!memberSetItem.equals(newMainDevice)) {
+                            newMainDevice.addMemberDevice(memberSetItem);
+                        }
+                    }
+                    memberSet.clear();
+                }
+
+                newMainDevice.addMemberDevice(memberDeviceItem);
+                mCachedDevices.remove(memberDeviceItem);
+                mBtManager.getEventManager().dispatchDeviceRemoved(memberDeviceItem);
+            }
+
+            if (!mCachedDevices.contains(newMainDevice)) {
+                mCachedDevices.add(newMainDevice);
+                mBtManager.getEventManager().dispatchDeviceAdded(newMainDevice);
+            }
+        }
+        log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: "
+                + mCachedDevices);
+    }
+
     private void log(String msg) {
         if (DEBUG) {
             Log.d(TAG, msg);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 1c179f8..6444f3b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -1150,9 +1150,11 @@
         assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_2);
         assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2);
         assertThat(mCachedDevice.mDevice).isEqualTo(mSubDevice);
+        verify(mCachedDevice).fillData();
         assertThat(mSubCachedDevice.mRssi).isEqualTo(RSSI_1);
         assertThat(mSubCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1);
         assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
+        verify(mSubCachedDevice).fillData();
         assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
     }
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index 80030f7..02ec486 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -20,14 +20,19 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.providers.settings.BackingStoreProto;
+import android.providers.settings.CacheEntryProto;
+import android.providers.settings.GenerationRegistryProto;
 import android.util.ArrayMap;
 import android.util.MemoryIntArray;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.IOException;
+import java.io.PrintWriter;
 
 /**
  * This class tracks changes for config/global/secure/system tables
@@ -292,4 +297,94 @@
     int getMaxNumBackingStores() {
         return mMaxNumBackingStore;
     }
+
+    public void dumpProto(ProtoOutputStream proto) {
+        synchronized (mLock) {
+            final int numBackingStores = mKeyToBackingStoreMap.size();
+            proto.write(GenerationRegistryProto.NUM_BACKING_STORES, numBackingStores);
+            proto.write(GenerationRegistryProto.NUM_MAX_BACKING_STORES, getMaxNumBackingStores());
+
+            for (int i = 0; i < numBackingStores; i++) {
+                final long token = proto.start(GenerationRegistryProto.BACKING_STORES);
+                final int key = mKeyToBackingStoreMap.keyAt(i);
+                proto.write(BackingStoreProto.KEY, key);
+                try {
+                    proto.write(BackingStoreProto.BACKING_STORE_SIZE,
+                            mKeyToBackingStoreMap.valueAt(i).size());
+                } catch (IOException ignore) {
+                }
+                proto.write(BackingStoreProto.NUM_CACHED_ENTRIES,
+                        mKeyToIndexMapMap.get(key).size());
+                final ArrayMap<String, Integer> indexMap = mKeyToIndexMapMap.get(key);
+                final MemoryIntArray backingStore = getBackingStoreLocked(key,
+                        /* createIfNotExist= */ false);
+                if (indexMap == null || backingStore == null) {
+                    continue;
+                }
+                for (String setting : indexMap.keySet()) {
+                    try {
+                        final int index = getKeyIndexLocked(key, setting, mKeyToIndexMapMap,
+                                backingStore, /* createIfNotExist= */ false);
+                        if (index < 0) {
+                            continue;
+                        }
+                        final long cacheEntryToken = proto.start(
+                                BackingStoreProto.CACHE_ENTRIES);
+                        final int generation = backingStore.get(index);
+                        proto.write(CacheEntryProto.NAME,
+                                setting.equals(DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS)
+                                        ? "UNSET" : setting);
+                        proto.write(CacheEntryProto.GENERATION, generation);
+                        proto.end(cacheEntryToken);
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+                proto.end(token);
+            }
+
+        }
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.println("GENERATION REGISTRY");
+        pw.println("Maximum number of backing stores:" + getMaxNumBackingStores());
+        synchronized (mLock) {
+            final int numBackingStores = mKeyToBackingStoreMap.size();
+            pw.println("Number of backing stores:" + numBackingStores);
+            for (int i = 0; i < numBackingStores; i++) {
+                final int key = mKeyToBackingStoreMap.keyAt(i);
+                pw.print("_Backing store for type:"); pw.print(SettingsState.settingTypeToString(
+                        SettingsState.getTypeFromKey(key)));
+                pw.print(" user:"); pw.print(SettingsState.getUserIdFromKey(key));
+                try {
+                    pw.print(" size:" + mKeyToBackingStoreMap.valueAt(i).size());
+                } catch (IOException ignore) {
+                }
+                pw.println(" cachedEntries:" + mKeyToIndexMapMap.get(key).size());
+                final ArrayMap<String, Integer> indexMap = mKeyToIndexMapMap.get(key);
+                final MemoryIntArray backingStore = getBackingStoreLocked(key,
+                        /* createIfNotExist= */ false);
+                if (indexMap == null || backingStore == null) {
+                    continue;
+                }
+                for (String setting : indexMap.keySet()) {
+                    try {
+                        final int index = getKeyIndexLocked(key, setting, mKeyToIndexMapMap,
+                                backingStore, /* createIfNotExist= */ false);
+                        if (index < 0) {
+                            continue;
+                        }
+                        final int generation = backingStore.get(index);
+                        pw.print("  setting: "); pw.print(
+                                setting.equals(DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS)
+                                        ? "UNSET" : setting);
+                        pw.println(" generation:" + generation);
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/OWNERS b/packages/SettingsProvider/src/com/android/providers/settings/OWNERS
new file mode 100644
index 0000000..0b71816
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/OWNERS
@@ -0,0 +1 @@
+per-file WritableNamespacePrefixes.java = cbrubaker@google.com,tedbauer@google.com
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index d49627e..d3a9e91 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -120,6 +120,17 @@
             dumpProtoUserSettingsLocked(proto, SettingsServiceDumpProto.USER_SETTINGS,
                     settingsRegistry, UserHandle.of(users.keyAt(i)));
         }
+
+        // Generation registry
+        dumpProtoGenerationRegistryLocked(proto, SettingsServiceDumpProto.GENERATION_REGISTRY,
+                settingsRegistry);
+    }
+
+    private static void dumpProtoGenerationRegistryLocked(@NonNull ProtoOutputStream proto,
+            long fieldId, SettingsProvider.SettingsRegistry settingsRegistry) {
+        final long token = proto.start(fieldId);
+        settingsRegistry.getGenerationRegistry().dumpProto(proto);
+        proto.end(token);
     }
 
     /**
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 5a8c594..7a97b78 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -900,6 +900,7 @@
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
+            mSettingsRegistry.mGenerationRegistry.dump(pw);
         }
     }
 
@@ -2323,7 +2324,15 @@
             return;
         } else if (hasAllowlistPermission) {
             for (String flag : flags) {
-                if (!DeviceConfig.getAdbWritableFlags().contains(flag)) {
+                boolean namespaceAllowed = false;
+                for (String allowlistedPrefix : WritableNamespacePrefixes.ALLOWLIST) {
+                    if (flag.startsWith(allowlistedPrefix)) {
+                        namespaceAllowed = true;
+                        break;
+                    }
+                }
+
+                if (!namespaceAllowed && !DeviceConfig.getAdbWritableFlags().contains(flag)) {
                     throw new SecurityException("Permission denial for flag '"
                         + flag
                         + "'; allowlist permission granted, but must add flag to the allowlist.");
@@ -6016,5 +6025,10 @@
             return !a11yButtonTargetsSettings.isNull()
                     && !TextUtils.isEmpty(a11yButtonTargetsSettings.getValue());
         }
+
+        @NonNull
+        public GenerationRegistry getGenerationRegistry() {
+            return mGenerationRegistry;
+        }
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
new file mode 100644
index 0000000..28f25e0
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2007 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.providers.settings;
+
+import android.util.ArraySet;
+
+import java.util.Arrays;
+import java.util.Set;
+
+/**
+ * Contains the list of prefixes for namespaces in which any flag can be written with adb.
+ * <p>
+ * A security review is required for any prefix that's added to this list. To add to
+ * the list, create a change and tag the OWNER. In the change description, include a
+ * description of the flag's functionality, and a justification for why it needs to be
+ * allowlisted.
+ */
+final class WritableNamespacePrefixes {
+    public static final Set<String> ALLOWLIST =
+            new ArraySet<String>(Arrays.asList(
+                "app_compat_overrides",
+                "game_overlay",
+                "namespace1"
+            ));
+}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index ac75cc8..3007d4a 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -182,7 +182,6 @@
         "androidx.dynamicanimation_dynamicanimation",
         "androidx-constraintlayout_constraintlayout",
         "androidx.exifinterface_exifinterface",
-        "androidx.test.ext.junit",
         "com.google.android.material_material",
         "kotlinx_coroutines_android",
         "kotlinx_coroutines",
@@ -191,6 +190,7 @@
         "SystemUI-proto",
         "monet",
         "dagger2",
+        "jsr305",
         "jsr330",
         "lottie",
         "LowLightDreamLib",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/footerlayout_switch_page.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/footerlayout_switch_page.xml
index 91cb4ba..462c90b 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/footerlayout_switch_page.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/footerlayout_switch_page.xml
@@ -56,6 +56,7 @@
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:paddingStart="16dp"
+      android:paddingEnd="16dp"
       android:gravity="center_vertical"
       android:textColor="@color/colorControlNormal"
       android:textSize="@dimen/label_text_size"
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index 4b6f9a4..02d279f 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -24,7 +24,9 @@
 import android.os.Bundle;
 import android.provider.Browser;
 import android.provider.Settings;
+import android.view.View;
 
+import androidx.annotation.Nullable;
 import androidx.fragment.app.FragmentActivity;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceFragmentCompat;
@@ -56,6 +58,13 @@
             initializeHelpAndFeedbackPreference();
         }
 
+        @Override
+        public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+            super.onViewCreated(view, savedInstanceState);
+            view.setLayoutDirection(
+                    view.getResources().getConfiguration().getLayoutDirection());
+        }
+
         /**
          * Returns large buttons settings state.
          *
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
index a25790a..5b7bbe8 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
@@ -194,13 +194,15 @@
     /** Updates a11y menu layout position by configuring layout params. */
     private void updateLayoutPosition() {
         final Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
-        final int orientation = mService.getResources().getConfiguration().orientation;
+        final Configuration configuration = mService.getResources().getConfiguration();
+        final int orientation = configuration.orientation;
         if (display != null && orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            final boolean ltr = configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
             switch (display.getRotation()) {
-                case Surface.ROTATION_90:
+                case Surface.ROTATION_0:
                 case Surface.ROTATION_180:
                     mLayoutParameter.gravity =
-                            Gravity.END | Gravity.BOTTOM
+                            (ltr ? Gravity.END : Gravity.START) | Gravity.BOTTOM
                                     | Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL;
                     mLayoutParameter.width = WindowManager.LayoutParams.WRAP_CONTENT;
                     mLayoutParameter.height = WindowManager.LayoutParams.MATCH_PARENT;
@@ -208,10 +210,10 @@
                     mLayoutParameter.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
                     mLayout.setBackgroundResource(R.drawable.shadow_90deg);
                     break;
-                case Surface.ROTATION_0:
+                case Surface.ROTATION_90:
                 case Surface.ROTATION_270:
                     mLayoutParameter.gravity =
-                            Gravity.START | Gravity.BOTTOM
+                            (ltr ? Gravity.START : Gravity.END) | Gravity.BOTTOM
                                     | Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL;
                     mLayoutParameter.width = WindowManager.LayoutParams.WRAP_CONTENT;
                     mLayoutParameter.height = WindowManager.LayoutParams.MATCH_PARENT;
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index 03e1e66..197b217 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -384,8 +384,15 @@
         }
 
         @JvmStatic
-        fun adaptRemoteAnimation(adapter: RemoteAnimationAdapter): RemoteTransition {
-            return RemoteTransition(adaptRemoteRunner(adapter.runner), adapter.callingApplication)
+        fun adaptRemoteAnimation(
+            adapter: RemoteAnimationAdapter,
+            debugName: String
+        ): RemoteTransition {
+            return RemoteTransition(
+                adaptRemoteRunner(adapter.runner),
+                adapter.callingApplication,
+                debugName
+            )
         }
     }
 
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index e73afe7..a7e95b5 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.plugins.PluginListener
 import com.android.systemui.plugins.PluginManager
 import com.android.systemui.util.Assert
+import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.atomic.AtomicBoolean
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -41,6 +42,18 @@
 private const val DEBUG = true
 private val KEY_TIMESTAMP = "appliedTimestamp"
 
+private fun <TKey, TVal> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut(
+    key: TKey,
+    value: TVal,
+    onNew: () -> Unit
+): TVal {
+    val result = this.putIfAbsent(key, value)
+    if (result == null) {
+        onNew()
+    }
+    return result ?: value
+}
+
 /** ClockRegistry aggregates providers and plugins */
 open class ClockRegistry(
     val context: Context,
@@ -64,7 +77,7 @@
         fun onAvailableClocksChanged() {}
     }
 
-    private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
+    private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>()
     private val clockChangeListeners = mutableListOf<ClockChangeListener>()
     private val settingObserver =
         object : ContentObserver(null) {
@@ -92,18 +105,12 @@
                 var isClockListChanged = false
                 for (clock in plugin.getClocks()) {
                     val id = clock.clockId
-                    var isNew = false
                     val info =
-                        availableClocks.getOrPut(id) {
-                            isNew = true
-                            ClockInfo(clock, plugin, manager)
+                        availableClocks.concurrentGetOrPut(id, ClockInfo(clock, plugin, manager)) {
+                            isClockListChanged = true
+                            onConnected(id)
                         }
 
-                    if (isNew) {
-                        isClockListChanged = true
-                        onConnected(id)
-                    }
-
                     if (manager != info.manager) {
                         Log.e(
                             TAG,
@@ -254,10 +261,8 @@
             return
         }
 
-        android.util.Log.e("HAWK", "triggerOnCurrentClockChanged")
         scope.launch(mainDispatcher) {
             assertMainThread()
-            android.util.Log.e("HAWK", "isClockChanged")
             isClockChanged.set(false)
             clockChangeListeners.forEach { it.onCurrentClockChanged() }
         }
@@ -270,10 +275,8 @@
             return
         }
 
-        android.util.Log.e("HAWK", "triggerOnAvailableClocksChanged")
         scope.launch(mainDispatcher) {
             assertMainThread()
-            android.util.Log.e("HAWK", "isClockListChanged")
             isClockListChanged.set(false)
             clockChangeListeners.forEach { it.onAvailableClocksChanged() }
         }
@@ -356,7 +359,7 @@
     }
 
     private var isVerifying = AtomicBoolean(false)
-    private fun verifyLoadedProviders() {
+    fun verifyLoadedProviders() {
         val shouldSchedule = isVerifying.compareAndSet(false, true)
         if (!shouldSchedule) {
             return
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index b49afee..4b94707 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -28,9 +28,10 @@
     <FrameLayout
         android:id="@+id/lockscreen_clock_view"
         android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_height="@dimen/small_clock_height"
         android:layout_alignParentStart="true"
         android:layout_alignParentTop="true"
+        android:clipChildren="false"
         android:paddingStart="@dimen/clock_padding_start"
         android:visibility="invisible" />
     <FrameLayout
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 1f44f05..cad2c16 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -95,6 +95,7 @@
     <dimen name="num_pad_key_margin_end">12dp</dimen>
 
     <!-- additional offset for clock switch area items -->
+    <dimen name="small_clock_height">114dp</dimen>
     <dimen name="clock_padding_start">28dp</dimen>
     <dimen name="below_clock_padding_start">32dp</dimen>
     <dimen name="below_clock_padding_end">16dp</dimen>
diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml
index b9e711e..d8967d4 100644
--- a/packages/SystemUI/res/layout/controls_management.xml
+++ b/packages/SystemUI/res/layout/controls_management.xml
@@ -77,6 +77,29 @@
                 app:layout_constraintStart_toStartOf="parent"/>
 
             <Button
+                android:id="@+id/rearrange"
+                android:visibility="gone"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:gravity="center_vertical"
+                style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"/>
+
+            <Button
+                android:id="@+id/addControls"
+                android:visibility="gone"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:gravity="center_vertical"
+                android:text="@string/controls_favorite_add_controls"
+                style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"/>
+
+            <Button
                 android:id="@+id/done"
                 android:layout_width="wrap_content"
                 android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index 79948da..4f6e88c 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -170,11 +170,11 @@
             android:layout_width="@dimen/notification_importance_toggle_size"
             android:layout_height="@dimen/notification_importance_toggle_size"
             android:layout_centerVertical="true"
-            android:background="@drawable/ripple_drawable"
             android:contentDescription="@string/notification_more_settings"
+            android:background="@drawable/ripple_drawable_20dp"
             android:src="@drawable/ic_settings"
-            android:layout_alignParentEnd="true"
-            android:tint="@color/notification_guts_link_icon_tint"/>
+            android:tint="?android:attr/colorAccent"
+            android:layout_alignParentEnd="true" />
 
     </LinearLayout>
 
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 4d6c2022..852db1b 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -108,11 +108,11 @@
             android:layout_width="@dimen/notification_importance_toggle_size"
             android:layout_height="@dimen/notification_importance_toggle_size"
             android:layout_centerVertical="true"
-            android:background="@android:color/transparent"
             android:contentDescription="@string/notification_more_settings"
-            android:src="@drawable/notif_settings_button"
-            android:layout_alignParentEnd="true"
-            android:tint="@color/notification_guts_link_icon_tint"/>
+            android:background="@drawable/ripple_drawable_20dp"
+            android:src="@drawable/ic_settings"
+            android:tint="?android:attr/colorAccent"
+            android:layout_alignParentEnd="true" />
 
     </LinearLayout>
 
diff --git a/packages/SystemUI/res/layout/partial_conversation_info.xml b/packages/SystemUI/res/layout/partial_conversation_info.xml
index 9ed3f92..4850b35 100644
--- a/packages/SystemUI/res/layout/partial_conversation_info.xml
+++ b/packages/SystemUI/res/layout/partial_conversation_info.xml
@@ -81,11 +81,11 @@
             android:layout_width="@dimen/notification_importance_toggle_size"
             android:layout_height="@dimen/notification_importance_toggle_size"
             android:layout_centerVertical="true"
-            android:background="@drawable/ripple_drawable"
             android:contentDescription="@string/notification_more_settings"
+            android:background="@drawable/ripple_drawable_20dp"
             android:src="@drawable/ic_settings"
-            android:layout_alignParentEnd="true"
-            android:tint="@color/notification_guts_link_icon_tint"/>
+            android:tint="?android:attr/colorAccent"
+            android:layout_alignParentEnd="true"/>
 
     </LinearLayout>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 324ba02..1dd12ee 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2471,6 +2471,15 @@
     <!-- Controls management favorites screen. See other apps button [CHAR LIMIT=30] -->
     <string name="controls_favorite_see_other_apps">See other apps</string>
 
+    <!-- Controls management favorites screen. Rearrange controls button [CHAR LIMIT=30]-->
+    <string name="controls_favorite_rearrange_button">Rearrange</string>
+
+    <!-- Controls management edit screen. Add controls button [CHAR LIMIT=30]-->
+    <string name="controls_favorite_add_controls">Add controls</string>
+
+    <!-- Controls management edit screen. Return to editing button [CHAR LIMIT=30]-->
+    <string name="controls_favorite_back_to_editing">Back to editing</string>
+
     <!-- Controls management controls screen error on load message [CHAR LIMIT=NONE] -->
     <string name="controls_favorite_load_error">Controls could not be loaded. Check the <xliff:g id="app" example="System UI">%s</xliff:g> app to make sure that the app settings haven\u2019t changed.</string>
     <!-- Controls management controls screen no controls found on load message [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 0890465..fac2f91 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -133,6 +133,10 @@
             return this.baseIntent.getPackage();
         }
 
+        public int getId() {
+            return id;
+        }
+
         @Override
         public boolean equals(Object o) {
             if (!(o instanceof TaskKey)) {
@@ -307,6 +311,10 @@
         lastSnapshotData.set(rawTask.lastSnapshotData);
     }
 
+    public TaskKey getKey() {
+        return key;
+    }
+
     /**
      * Returns the visible width to height ratio. Returns 0f if snapshot data is not available.
      */
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 53fab69..cab54d0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -66,7 +66,6 @@
 
 import java.io.PrintWriter;
 import java.util.Optional;
-import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 /**
@@ -244,7 +243,12 @@
 
         mListenersRegistered = false;
 
-        mContext.unregisterReceiver(mDockedReceiver);
+        try {
+            mContext.unregisterReceiver(mDockedReceiver);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Docked receiver already unregistered", e);
+        }
+
         if (mRotationWatcherRegistered) {
             try {
                 WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index e08a604..4269530 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -59,6 +59,8 @@
             InteractionJankMonitor.CUJ_RECENTS_SCROLLING;
     public static final int CUJ_APP_SWIPE_TO_RECENTS =
             InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
+    public static final int CUJ_OPEN_SEARCH_RESULT =
+            InteractionJankMonitor.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
 
     @IntDef({
             CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -72,7 +74,8 @@
             CUJ_APP_SWIPE_TO_RECENTS,
             CUJ_OPEN_ALL_APPS,
             CUJ_CLOSE_ALL_APPS_SWIPE,
-            CUJ_CLOSE_ALL_APPS_TO_HOME
+            CUJ_CLOSE_ALL_APPS_TO_HOME,
+            CUJ_OPEN_SEARCH_RESULT,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 6c59a94..4d7d0ea 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -116,28 +116,25 @@
     public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26;
     // Device dreaming state
     public static final int SYSUI_STATE_DEVICE_DREAMING = 1 << 27;
-    // Whether the screen is currently on. Note that the screen is considered on while turning on,
-    // but not while turning off.
-    public static final int SYSUI_STATE_SCREEN_ON = 1 << 28;
-    // Whether the screen is currently transitioning into the state indicated by
-    // SYSUI_STATE_SCREEN_ON.
-    public static final int SYSUI_STATE_SCREEN_TRANSITION = 1 << 29;
+    // Whether the device is currently awake (as opposed to asleep, see WakefulnessLifecycle).
+    // Note that the device is awake on while waking up on, but not while going to sleep.
+    public static final int SYSUI_STATE_AWAKE = 1 << 28;
+    // Whether the device is currently transitioning between awake/asleep indicated by
+    // SYSUI_STATE_AWAKE.
+    public static final int SYSUI_STATE_WAKEFULNESS_TRANSITION = 1 << 29;
     // The notification panel expansion fraction is > 0
     public static final int SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE = 1 << 30;
 
-    // Mask for SystemUiStateFlags to isolate SYSUI_STATE_SCREEN_ON and
-    // SYSUI_STATE_SCREEN_TRANSITION, to match SCREEN_STATE_*
-    public static final int SYSUI_STATE_SCREEN_STATE_MASK =
-            SYSUI_STATE_SCREEN_ON | SYSUI_STATE_SCREEN_TRANSITION;
-    // Screen is off.
-    public static final int SCREEN_STATE_OFF = 0;
-    // Screen is on.
-    public static final int SCREEN_STATE_ON = SYSUI_STATE_SCREEN_ON;
-    // Screen is still on, but transitioning to turn off.
-    public static final int SCREEN_STATE_TURNING_OFF = SYSUI_STATE_SCREEN_TRANSITION;
-    // Screen was off and is now turning on.
-    public static final int SCREEN_STATE_TURNING_ON =
-            SYSUI_STATE_SCREEN_TRANSITION | SYSUI_STATE_SCREEN_ON;
+    // Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and
+    // SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants
+    public static final int SYSUI_STATE_WAKEFULNESS_MASK =
+            SYSUI_STATE_AWAKE | SYSUI_STATE_WAKEFULNESS_TRANSITION;
+    // Mirroring the WakefulnessLifecycle#Wakefulness states
+    public static final int WAKEFULNESS_ASLEEP = 0;
+    public static final int WAKEFULNESS_AWAKE = SYSUI_STATE_AWAKE;
+    public static final int WAKEFULNESS_GOING_TO_SLEEP = SYSUI_STATE_WAKEFULNESS_TRANSITION;
+    public static final int WAKEFULNESS_WAKING =
+            SYSUI_STATE_WAKEFULNESS_TRANSITION | SYSUI_STATE_AWAKE;
 
     // Whether the back gesture is allowed (or ignored) by the Shade
     public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = SystemProperties.getBoolean(
@@ -172,8 +169,9 @@
             SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING,
             SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE,
             SYSUI_STATE_DEVICE_DREAMING,
-            SYSUI_STATE_SCREEN_ON,
-            SYSUI_STATE_SCREEN_TRANSITION,
+            SYSUI_STATE_AWAKE,
+            SYSUI_STATE_WAKEFULNESS_TRANSITION,
+            SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
     })
     public @interface SystemUiStateFlags {}
 
@@ -195,7 +193,7 @@
             str.add("navbar_hidden");
         }
         if ((flags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) != 0) {
-            str.add("notif_visible");
+            str.add("notif_expanded");
         }
         if ((flags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) != 0) {
             str.add("qs_visible");
@@ -263,11 +261,14 @@
         if ((flags & SYSUI_STATE_DEVICE_DREAMING) != 0) {
             str.add("device_dreaming");
         }
-        if ((flags & SYSUI_STATE_SCREEN_TRANSITION) != 0) {
-            str.add("screen_transition");
+        if ((flags & SYSUI_STATE_WAKEFULNESS_TRANSITION) != 0) {
+            str.add("wakefulness_transition");
         }
-        if ((flags & SYSUI_STATE_SCREEN_ON) != 0) {
-            str.add("screen_on");
+        if ((flags & SYSUI_STATE_AWAKE) != 0) {
+            str.add("awake");
+        }
+        if ((flags & SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE) != 0) {
+            str.add("notif_visible");
         }
 
         return str.toString();
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 44f9d43..f094102 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -63,6 +63,7 @@
         final ArrayList<RemoteAnimationTarget> out = new ArrayList<>();
         for (int i = 0; i < info.getChanges().size(); i++) {
             TransitionInfo.Change change = info.getChanges().get(i);
+            if (TransitionUtil.isOrderOnly(change)) continue;
             if (filter.test(change)) {
                 out.add(TransitionUtil.newTarget(
                         change, info.getChanges().size() - i, info, t, leashMap));
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 58e7747..1fbf743 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -89,7 +89,7 @@
                 }
             }
         };
-        return new RemoteTransition(remote, appThread);
+        return new RemoteTransition(remote, appThread, "Recents");
     }
 
     /**
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index 31234cf..c22d689 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -43,9 +43,8 @@
         id: Int,
         name: String,
         namespace: String = "systemui",
-        teamfood: Boolean = false
     ): ReleasedFlag {
-        val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
+        val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = false)
         checkForDupesAndAdd(flag)
         return flag
     }
@@ -55,7 +54,6 @@
         @BoolRes resourceId: Int,
         name: String,
         namespace: String = "systemui",
-        teamfood: Boolean = false
     ): ResourceBooleanFlag {
         val flag =
             ResourceBooleanFlag(
@@ -63,7 +61,7 @@
                 name = name,
                 namespace = namespace,
                 resourceId = resourceId,
-                teamfood = teamfood
+                teamfood = false,
             )
         checkForDupesAndAdd(flag)
         return flag
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
index 27c5699..5502da1 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
@@ -43,9 +43,8 @@
         id: Int,
         name: String,
         namespace: String = "systemui",
-        teamfood: Boolean = false
     ): ReleasedFlag {
-        val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
+        val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = false)
         flagMap[name] = flag
         return flag
     }
@@ -55,7 +54,6 @@
         @BoolRes resourceId: Int,
         name: String,
         namespace: String = "systemui",
-        teamfood: Boolean = false
     ): ResourceBooleanFlag {
         val flag =
             ResourceBooleanFlag(
@@ -63,7 +61,7 @@
                 name = name,
                 namespace = namespace,
                 resourceId = resourceId,
-                teamfood = teamfood
+                teamfood = false,
             )
         flagMap[name] = flag
         return flag
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 9f2333d8..1980f70 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -107,14 +107,7 @@
         // start fresh
         mDismissing = false;
         mView.resetPasswordText(false /* animate */, false /* announce */);
-        // if the user is currently locked out, enforce it.
-        long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
-                KeyguardUpdateMonitor.getCurrentUser());
-        if (shouldLockout(deadline)) {
-            handleAttemptLockout(deadline);
-        } else {
-            resetState();
-        }
+        resetState();
     }
 
     @Override
@@ -277,7 +270,12 @@
     @Override
     public void onResume(int reason) {
         mResumed = true;
-        reset();
+        // if the user is currently locked out, enforce it.
+        long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+                KeyguardUpdateMonitor.getCurrentUser());
+        if (shouldLockout(deadline)) {
+            handleAttemptLockout(deadline);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index cdaed87..07333f7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -390,6 +390,13 @@
             PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X,
                     x, props, animate);
         }
+
+    }
+
+    void updateKeyguardStatusViewOffset() {
+        // updateClockTargetRegions will call onTargetRegionChanged
+        // which will require the correct translationY property of keyguardStatusView after updating
+        mView.updateClockTargetRegions();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 68b40ab..5c56aab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -260,20 +260,18 @@
         mLockPatternView.setEnabled(true);
         mLockPatternView.clearPattern();
 
-        // if the user is currently locked out, enforce it.
-        long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
-                KeyguardUpdateMonitor.getCurrentUser());
-        if (deadline != 0) {
-            handleAttemptLockout(deadline);
-        } else {
-            displayDefaultSecurityMessage();
-        }
+        displayDefaultSecurityMessage();
     }
 
     @Override
     public void onResume(int reason) {
         super.onResume(reason);
-        reset();
+        // if the user is currently locked out, enforce it.
+        long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+                KeyguardUpdateMonitor.getCurrentUser());
+        if (deadline != 0) {
+            handleAttemptLockout(deadline);
+        }
     }
 
     @Override
@@ -300,34 +298,38 @@
     @Override
     public void showPromptReason(int reason) {
         /// TODO: move all this logic into the MessageAreaController?
+        int resId =  0;
         switch (reason) {
             case PROMPT_REASON_RESTART:
-                mMessageAreaController.setMessage(R.string.kg_prompt_reason_restart_pattern);
+                resId = R.string.kg_prompt_reason_restart_pattern;
                 break;
             case PROMPT_REASON_TIMEOUT:
-                mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+                resId = R.string.kg_prompt_reason_timeout_pattern;
                 break;
             case PROMPT_REASON_DEVICE_ADMIN:
-                mMessageAreaController.setMessage(R.string.kg_prompt_reason_device_admin);
+                resId = R.string.kg_prompt_reason_device_admin;
                 break;
             case PROMPT_REASON_USER_REQUEST:
-                mMessageAreaController.setMessage(R.string.kg_prompt_reason_user_request);
+                resId = R.string.kg_prompt_reason_user_request;
                 break;
             case PROMPT_REASON_PREPARE_FOR_UPDATE:
-                mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+                resId = R.string.kg_prompt_reason_timeout_pattern;
                 break;
             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
-                mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+                resId = R.string.kg_prompt_reason_timeout_pattern;
                 break;
             case PROMPT_REASON_TRUSTAGENT_EXPIRED:
-                mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+                resId = R.string.kg_prompt_reason_timeout_pattern;
                 break;
             case PROMPT_REASON_NONE:
                 break;
             default:
-                mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+                resId = R.string.kg_prompt_reason_timeout_pattern;
                 break;
         }
+        if (resId != 0) {
+            mMessageAreaController.setMessage(getResources().getText(resId), /* animate= */ false);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 559db76..ded1238 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -127,6 +127,11 @@
     @Override
     public void onResume(int reason) {
         super.onResume(reason);
+        // It's possible to reach a state here where mPasswordEntry believes it is focused
+        // but it is not actually focused. This state will prevent the view from gaining focus,
+        // as requestFocus will no-op since the focus flag is already set. By clearing focus first,
+        // it's guaranteed that the view has focus.
+        mPasswordEntry.clearFocus();
         mPasswordEntry.requestFocus();
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 67874e1..87a7758 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -40,6 +40,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Slog;
@@ -64,6 +65,7 @@
 import com.android.keyguard.KeyguardSecurityContainer.SwipeListener;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.keyguard.dagger.KeyguardBouncerScope;
+import com.android.settingslib.Utils;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
@@ -634,6 +636,16 @@
                 mKeyguardStateController.isFaceAuthEnabled());
     }
 
+    /** Sets an initial message that would override the default message */
+    public void setInitialMessage() {
+        CharSequence customMessage = mViewMediatorCallback.consumeCustomMessage();
+        if (!TextUtils.isEmpty(customMessage)) {
+            showMessage(customMessage, Utils.getColorError(getContext()));
+            return;
+        }
+        showPromptReason(mViewMediatorCallback.getBouncerPromptReason());
+    }
+
     /**
      * Show the bouncer and start appear animations.
      *
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index f4c5815..fd55d69 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -172,11 +172,15 @@
      * Update position of the view with an optional animation
      */
     public void updatePosition(int x, int y, float scale, boolean animate) {
+        float oldY = mView.getY();
         PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES,
                 animate);
 
         mKeyguardClockSwitchController.updatePosition(x, scale, CLOCK_ANIMATION_PROPERTIES,
                 animate);
+        if (oldY != y) {
+            mKeyguardClockSwitchController.updateKeyguardStatusViewOffset();
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index a678edc..ac0a3fd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -28,6 +28,7 @@
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.phone.AnimatorHandle;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -47,6 +48,7 @@
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private boolean mAnimateYPos;
     private boolean mKeyguardViewVisibilityAnimating;
+    private AnimatorHandle mKeyguardAnimatorHandle;
     private boolean mLastOccludedState = false;
     private final AnimationProperties mAnimationProperties = new AnimationProperties();
     private final LogBuffer mLogBuffer;
@@ -83,6 +85,10 @@
             boolean keyguardFadingAway,
             boolean goingToFullShade,
             int oldStatusBarState) {
+        if (mKeyguardAnimatorHandle != null) {
+            mKeyguardAnimatorHandle.cancel();
+            mKeyguardAnimatorHandle = null;
+        }
         mView.animate().cancel();
         boolean isOccluded = mKeyguardStateController.isOccluded();
         mKeyguardViewVisibilityAnimating = false;
@@ -116,7 +122,7 @@
                     .setDuration(320)
                     .setInterpolator(Interpolators.ALPHA_IN)
                     .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
-            log("keyguardFadingAway transition w/ Y Aniamtion");
+            log("keyguardFadingAway transition w/ Y Animation");
         } else if (statusBarState == KEYGUARD) {
             if (keyguardFadingAway) {
                 mKeyguardViewVisibilityAnimating = true;
@@ -148,7 +154,7 @@
 
                 // Ask the screen off animation controller to animate the keyguard visibility for us
                 // since it may need to be cancelled due to keyguard lifecycle events.
-                mScreenOffAnimationController.animateInKeyguard(
+                mKeyguardAnimatorHandle = mScreenOffAnimationController.animateInKeyguard(
                         mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
             } else {
                 log("Direct set Visibility to VISIBLE");
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 1ae380e..235a8bc 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -22,7 +22,6 @@
 import static com.android.keyguard.LockIconView.ICON_FINGERPRINT;
 import static com.android.keyguard.LockIconView.ICON_LOCK;
 import static com.android.keyguard.LockIconView.ICON_UNLOCK;
-import static com.android.systemui.classifier.Classifier.LOCK_ICON;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
 import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
@@ -127,8 +126,6 @@
     private boolean mCanDismissLockScreen;
     private int mStatusBarState;
     private boolean mIsKeyguardShowing;
-    private boolean mUserUnlockedWithBiometric;
-    private Runnable mCancelDelayedUpdateVisibilityRunnable;
     private Runnable mOnGestureDetectedRunnable;
     private Runnable mLongPressCancelRunnable;
 
@@ -229,7 +226,6 @@
         updateIsUdfpsEnrolled();
         updateConfiguration();
         updateKeyguardShowing();
-        mUserUnlockedWithBiometric = false;
 
         mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
         mIsDozing = mStatusBarStateController.isDozing();
@@ -270,11 +266,6 @@
         mStatusBarStateController.removeCallback(mStatusBarStateListener);
         mKeyguardStateController.removeCallback(mKeyguardStateCallback);
 
-        if (mCancelDelayedUpdateVisibilityRunnable != null) {
-            mCancelDelayedUpdateVisibilityRunnable.run();
-            mCancelDelayedUpdateVisibilityRunnable = null;
-        }
-
         mAccessibilityManager.removeAccessibilityStateChangeListener(
                 mAccessibilityStateChangeListener);
     }
@@ -288,11 +279,6 @@
     }
 
     private void updateVisibility() {
-        if (mCancelDelayedUpdateVisibilityRunnable != null) {
-            mCancelDelayedUpdateVisibilityRunnable.run();
-            mCancelDelayedUpdateVisibilityRunnable = null;
-        }
-
         if (!mIsKeyguardShowing && !mIsDozing) {
             mView.setVisibility(View.INVISIBLE);
             return;
@@ -300,9 +286,9 @@
 
         boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon
                 && !mShowAodUnlockedIcon && !mShowAodLockIcon;
-        mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen()
+        mShowLockIcon = !mCanDismissLockScreen && isLockScreen()
                 && (!mUdfpsEnrolled || !mRunningFPS);
-        mShowUnlockIcon = (mCanDismissLockScreen || mUserUnlockedWithBiometric) && isLockScreen();
+        mShowUnlockIcon = mCanDismissLockScreen && isLockScreen();
         mShowAodUnlockedIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && mCanDismissLockScreen;
         mShowAodLockIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && !mCanDismissLockScreen;
 
@@ -426,7 +412,6 @@
         pw.println(" isFlagEnabled(DOZING_MIGRATION_1): "
                 + mFeatureFlags.isEnabled(DOZING_MIGRATION_1));
         pw.println(" mIsBouncerShowing: " + mIsBouncerShowing);
-        pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric);
         pw.println(" mRunningFPS: " + mRunningFPS);
         pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
         pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState));
@@ -469,17 +454,6 @@
         }
     }
 
-    /**
-     * @return whether the userUnlockedWithBiometric state changed
-     */
-    private boolean updateUserUnlockedWithBiometric() {
-        final boolean wasUserUnlockedWithBiometric = mUserUnlockedWithBiometric;
-        mUserUnlockedWithBiometric =
-                mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(
-                        KeyguardUpdateMonitor.getCurrentUser());
-        return wasUserUnlockedWithBiometric != mUserUnlockedWithBiometric;
-    }
-
     private StatusBarStateController.StateListener mStatusBarStateListener =
             new StatusBarStateController.StateListener() {
                 @Override
@@ -516,36 +490,15 @@
                 }
 
                 @Override
-                public void onBiometricsCleared() {
-                    if (updateUserUnlockedWithBiometric()) {
-                        updateVisibility();
-                    }
-                }
-
-                @Override
                 public void onBiometricRunningStateChanged(boolean running,
                         BiometricSourceType biometricSourceType) {
                     final boolean wasRunningFps = mRunningFPS;
-                    final boolean userUnlockedWithBiometricChanged =
-                            updateUserUnlockedWithBiometric();
 
                     if (biometricSourceType == FINGERPRINT) {
                         mRunningFPS = running;
-                        if (wasRunningFps && !mRunningFPS) {
-                            if (mCancelDelayedUpdateVisibilityRunnable != null) {
-                                mCancelDelayedUpdateVisibilityRunnable.run();
-                            }
-
-                            // For some devices, auth is cancelled immediately on screen off but
-                            // before dozing state is set. We want to avoid briefly showing the
-                            // button in this case, so we delay updating the visibility by 50ms.
-                            mCancelDelayedUpdateVisibilityRunnable =
-                                    mExecutor.executeDelayed(() -> updateVisibility(), 50);
-                            return;
-                        }
                     }
 
-                    if (userUnlockedWithBiometricChanged || wasRunningFps != mRunningFPS) {
+                    if (wasRunningFps != mRunningFPS) {
                         updateVisibility();
                     }
                 }
@@ -556,7 +509,6 @@
         @Override
         public void onUnlockedChanged() {
             mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
-            updateUserUnlockedWithBiometric();
             updateKeyguardShowing();
             updateVisibility();
         }
@@ -573,9 +525,6 @@
             mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
 
             updateKeyguardShowing();
-            if (mIsKeyguardShowing) {
-                updateUserUnlockedWithBiometric();
-            }
             updateVisibility();
         }
 
@@ -694,7 +643,7 @@
 
     private void onLongPress() {
         cancelTouches();
-        if (mFalsingManager.isFalseTouch(LOCK_ICON)) {
+        if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
             Log.v(TAG, "lock icon long-press rejected by the falsing manager.");
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index cbc0a1b..ac30311 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -25,7 +25,6 @@
 import static android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR;
 
 import static com.android.internal.util.Preconditions.checkNotNull;
-import static com.android.systemui.classifier.Classifier.LOCK_ICON;
 import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
 
 import android.content.BroadcastReceiver;
@@ -619,9 +618,9 @@
         }
         logBiometricTouch(processedTouch.getEvent(), data);
 
-        // Always pilfer pointers that are within sensor area
-        if (isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true)) {
-            Log.d("Austin", "pilferTouch invalid overlap");
+        // Always pilfer pointers that are within sensor area or when alternate bouncer is showing
+        if (isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true)
+                || mAlternateBouncerInteractor.isVisibleState()) {
             mInputManager.pilferPointers(
                     mOverlay.getOverlayView().getViewRootImpl().getInputToken());
         }
@@ -983,7 +982,7 @@
         }
 
         if (!mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
-            if (mFalsingManager.isFalseTouch(LOCK_ICON)) {
+            if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
                 Log.v(TAG, "aod lock icon long-press rejected by the falsing manager.");
                 return;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 414c2ec..f876aff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -353,10 +353,19 @@
             flags = flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
         }
 
-        // Original sensorBounds assume portrait mode.
+        val isEnrollment = when (requestReason) {
+            REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true
+            else -> false
+        }
+
+        // Use expanded overlay unless touchExploration enabled
         var rotatedBounds =
             if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
-                Rect(overlayParams.overlayBounds)
+                if (accessibilityManager.isTouchExplorationEnabled && isEnrollment) {
+                    Rect(overlayParams.sensorBounds)
+                } else {
+                    Rect(overlayParams.overlayBounds)
+                }
             } else {
                 Rect(overlayParams.sensorBounds)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index 063b41e..5101ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -167,6 +167,9 @@
 
     private val keyguardStateControllerCallback: KeyguardStateController.Callback =
         object : KeyguardStateController.Callback {
+            override fun onUnlockedChanged() {
+                updatePauseAuth()
+            }
             override fun onLaunchTransitionFadingAwayChanged() {
                 launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
                 updatePauseAuth()
@@ -403,6 +406,15 @@
         if (isBouncerExpansionGreaterThan(.5f)) {
             return true
         }
+        if (
+            keyguardUpdateMonitor.getUserUnlockedWithBiometric(
+                KeyguardUpdateMonitor.getCurrentUser()
+            )
+        ) {
+            // If the device was unlocked by a biometric, immediately hide the UDFPS icon to avoid
+            // overlap with the LockIconView. Shortly afterwards, UDFPS will stop running.
+            return true
+        }
         return view.unpausedAlpha < 255 * .1
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
index 701df89..334cf93 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
@@ -41,7 +41,6 @@
     public static final int SHADE_DRAG = 11;
     public static final int QS_COLLAPSE = 12;
     public static final int UDFPS_AUTHENTICATION = 13;
-    public static final int LOCK_ICON = 14;
     public static final int QS_SWIPE_SIDE = 15;
     public static final int BACK_GESTURE = 16;
     public static final int QS_SWIPE_NESTED = 17;
@@ -58,12 +57,10 @@
             GENERIC,
             BOUNCER_UNLOCK,
             PULSE_EXPAND,
-            BRIGHTNESS_SLIDER,
             SHADE_DRAG,
             QS_COLLAPSE,
             BRIGHTNESS_SLIDER,
             UDFPS_AUTHENTICATION,
-            LOCK_ICON,
             QS_SWIPE_SIDE,
             QS_SWIPE_NESTED,
             BACK_GESTURE,
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java
index d17eadd..8ec48b9 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java
@@ -19,7 +19,6 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DIAGONAL_HORIZONTAL_ANGLE_RANGE;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DIAGONAL_VERTICAL_ANGLE_RANGE;
 import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
-import static com.android.systemui.classifier.Classifier.LOCK_ICON;
 import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE;
 
 import android.provider.DeviceConfig;
@@ -73,8 +72,7 @@
         }
 
         if (interactionType == LEFT_AFFORDANCE
-                || interactionType == RIGHT_AFFORDANCE
-                || interactionType == LOCK_ICON) {
+                || interactionType == RIGHT_AFFORDANCE) {
             return Result.passed(0);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
index f8ee49a..15e2e9a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
@@ -158,7 +158,6 @@
                 || interactionType == SHADE_DRAG
                 || interactionType == QS_COLLAPSE
                 || interactionType == Classifier.UDFPS_AUTHENTICATION
-                || interactionType == Classifier.LOCK_ICON
                 || interactionType == Classifier.QS_SWIPE_SIDE
                 || interactionType == QS_SWIPE_NESTED) {
             return Result.passed(0);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
index d8d2c98..2fb6aaf 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
@@ -47,8 +47,7 @@
     Result calculateFalsingResult(
             @Classifier.InteractionType int interactionType,
             double historyBelief, double historyConfidence) {
-        if (interactionType == Classifier.UDFPS_AUTHENTICATION
-                || interactionType == Classifier.LOCK_ICON) {
+        if (interactionType == Classifier.UDFPS_AUTHENTICATION) {
             return Result.passed(0);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
index 840982c..4a3710b 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
@@ -21,7 +21,6 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_PRIMARY_DEVIANCE;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE;
 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
-import static com.android.systemui.classifier.Classifier.LOCK_ICON;
 import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
 import static com.android.systemui.classifier.Classifier.SHADE_DRAG;
 
@@ -93,8 +92,7 @@
             double historyBelief, double historyConfidence) {
         if (interactionType == BRIGHTNESS_SLIDER
                 || interactionType == MEDIA_SEEKBAR
-                || interactionType == SHADE_DRAG
-                || interactionType == LOCK_ICON) {
+                || interactionType == SHADE_DRAG) {
             return Result.passed(0);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index e049ae0..c312f69 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -308,7 +308,7 @@
                 if (model.isSensitive()) {
                     mView.showTextPreview(mContext.getString(R.string.clipboard_asterisks), true);
                 } else {
-                    mView.showTextPreview(model.getText(), false);
+                    mView.showTextPreview(model.getText().toString(), false);
                 }
                 mView.setEditAccessibilityAction(true);
                 mOnPreviewTapped = this::editText;
@@ -527,7 +527,7 @@
     }
 
     private void showEditableText(CharSequence text, boolean hidden) {
-        mView.showTextPreview(text, hidden);
+        mView.showTextPreview(text.toString(), hidden);
         mView.setEditAccessibilityAction(true);
         mOnPreviewTapped = this::editText;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index ac1150e..e8c97bf 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -31,7 +31,6 @@
 import android.util.ArrayMap
 import android.util.Log
 import com.android.internal.annotations.VisibleForTesting
-import com.android.internal.notification.NotificationAccessConfirmationActivityContract.EXTRA_USER_ID
 import com.android.systemui.Dumpable
 import com.android.systemui.backup.BackupHelper
 import com.android.systemui.controls.ControlStatus
@@ -44,7 +43,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.people.widget.PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_FILE
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
index 00a406e..be428a8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
@@ -75,9 +75,12 @@
         } else {
             favoriteIds.remove(controlId)
         }
-        if (changed && !modified) {
-            modified = true
-            controlsModelCallback.onFirstChange()
+        if (changed) {
+            if (!modified) {
+                modified = true
+                controlsModelCallback.onFirstChange()
+            }
+            controlsModelCallback.onChange()
         }
         toChange?.let {
             it.controlStatus.favorite = favorite
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index 7df0865..d629e3e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -27,6 +27,7 @@
 import android.view.ViewStub
 import android.widget.Button
 import android.widget.TextView
+import android.widget.Toast
 import android.window.OnBackInvokedCallback
 import android.window.OnBackInvokedDispatcher
 import androidx.activity.ComponentActivity
@@ -38,8 +39,9 @@
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.ui.ControlsActivity
-import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -48,17 +50,19 @@
  * Activity for rearranging and removing controls for a given structure
  */
 open class ControlsEditingActivity @Inject constructor(
+    featureFlags: FeatureFlags,
     @Main private val mainExecutor: Executor,
     private val controller: ControlsControllerImpl,
     private val userTracker: UserTracker,
     private val customIconCache: CustomIconCache,
-    private val uiController: ControlsUiController
 ) : ComponentActivity() {
 
     companion object {
         private const val DEBUG = false
         private const val TAG = "ControlsEditingActivity"
         const val EXTRA_STRUCTURE = ControlsFavoritingActivity.EXTRA_STRUCTURE
+        const val EXTRA_APP = ControlsFavoritingActivity.EXTRA_APP
+        const val EXTRA_FROM_FAVORITING = "extra_from_favoriting"
         private val SUBTITLE_ID = R.string.controls_favorite_rearrange
         private val EMPTY_TEXT_ID = R.string.controls_favorite_removed
     }
@@ -68,7 +72,12 @@
     private lateinit var model: FavoritesModel
     private lateinit var subtitle: TextView
     private lateinit var saveButton: View
+    private lateinit var addControls: View
 
+    private var isFromFavoriting: Boolean = false
+
+    private val isNewFlowEnabled: Boolean =
+        featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
     private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
         private val startingUser = controller.currentUserId
 
@@ -93,7 +102,7 @@
         intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)?.let {
             component = it
         } ?: run(this::finish)
-
+        isFromFavoriting = intent.getBooleanExtra(EXTRA_FROM_FAVORITING, false)
         intent.getCharSequenceExtra(EXTRA_STRUCTURE)?.let {
             structure = it
         } ?: run(this::finish)
@@ -165,8 +174,42 @@
     }
 
     private fun bindButtons() {
+        addControls = requireViewById<Button>(R.id.addControls).apply {
+            isEnabled = true
+            visibility = if (isNewFlowEnabled) View.VISIBLE else View.GONE
+            setOnClickListener {
+                if (saveButton.isEnabled) {
+                    // The user has made changes
+                    Toast.makeText(
+                        applicationContext,
+                        R.string.controls_favorite_toast_no_changes,
+                        Toast.LENGTH_SHORT
+                    ).show()
+                }
+                if (isFromFavoriting) {
+                    animateExitAndFinish()
+                } else {
+                    startActivity(Intent(context, ControlsFavoritingActivity::class.java).also {
+                        it.putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, structure)
+                        it.putExtra(Intent.EXTRA_COMPONENT_NAME, component)
+                        it.putExtra(
+                            ControlsFavoritingActivity.EXTRA_APP,
+                            intent.getCharSequenceExtra(EXTRA_APP),
+                        )
+                        it.putExtra(
+                            ControlsFavoritingActivity.EXTRA_SOURCE,
+                            ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_EDITING,
+                        )
+                    },
+                                  ActivityOptions.makeSceneTransitionAnimation(
+                                      this@ControlsEditingActivity
+                                  ).toBundle(),
+                    )
+                }
+            }
+        }
         saveButton = requireViewById<Button>(R.id.done).apply {
-            isEnabled = false
+            isEnabled = isFromFavoriting
             setText(R.string.save)
             setOnClickListener {
                 saveFavorites()
@@ -194,6 +237,8 @@
             }
         }
 
+        override fun onChange() = Unit
+
         override fun onFirstChange() {
             saveButton.isEnabled = true
         }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 3e97d31..d3ffc95 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -37,6 +37,7 @@
 import android.window.OnBackInvokedCallback
 import android.window.OnBackInvokedDispatcher
 import androidx.activity.ComponentActivity
+import androidx.annotation.VisibleForTesting
 import androidx.viewpager2.widget.ViewPager2
 import com.android.systemui.Prefs
 import com.android.systemui.R
@@ -45,20 +46,20 @@
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.ui.ControlsActivity
-import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
 import java.text.Collator
 import java.util.concurrent.Executor
-import java.util.function.Consumer
 import javax.inject.Inject
 
 open class ControlsFavoritingActivity @Inject constructor(
+    featureFlags: FeatureFlags,
     @Main private val executor: Executor,
     private val controller: ControlsControllerImpl,
     private val listingController: ControlsListingController,
     private val userTracker: UserTracker,
-    private val uiController: ControlsUiController
 ) : ComponentActivity() {
 
     companion object {
@@ -71,7 +72,10 @@
         // If provided, show this structure page first
         const val EXTRA_STRUCTURE = "extra_structure"
         const val EXTRA_SINGLE_STRUCTURE = "extra_single_structure"
-        const val EXTRA_FROM_PROVIDER_SELECTOR = "extra_from_provider_selector"
+        const val EXTRA_SOURCE = "extra_source"
+        const val EXTRA_SOURCE_UNDEFINED: Byte = 0
+        const val EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR: Byte = 1
+        const val EXTRA_SOURCE_VALUE_FROM_EDITING: Byte = 2
         private const val TOOLTIP_PREFS_KEY = Prefs.Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT
         private const val TOOLTIP_MAX_SHOWN = 2
     }
@@ -79,7 +83,7 @@
     private var component: ComponentName? = null
     private var appName: CharSequence? = null
     private var structureExtra: CharSequence? = null
-    private var fromProviderSelector = false
+    private var openSource = EXTRA_SOURCE_UNDEFINED
 
     private lateinit var structurePager: ViewPager2
     private lateinit var statusText: TextView
@@ -89,12 +93,19 @@
     private var mTooltipManager: TooltipManager? = null
     private lateinit var doneButton: View
     private lateinit var otherAppsButton: View
+    private lateinit var rearrangeButton: Button
     private var listOfStructures = emptyList<StructureContainer>()
 
     private lateinit var comparator: Comparator<StructureContainer>
     private var cancelLoadRunnable: Runnable? = null
     private var isPagerLoaded = false
 
+    private val fromProviderSelector: Boolean
+        get() = openSource == EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR
+    private val fromEditing: Boolean
+        get() = openSource == EXTRA_SOURCE_VALUE_FROM_EDITING
+    private val isNewFlowEnabled: Boolean =
+        featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
     private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
         private val startingUser = controller.currentUserId
 
@@ -117,14 +128,20 @@
 
         override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
             if (serviceInfos.size > 1) {
-                otherAppsButton.post {
-                    otherAppsButton.visibility = View.VISIBLE
+                val newVisibility = if (isNewFlowEnabled) View.GONE else View.VISIBLE
+                if (otherAppsButton.visibility != newVisibility) {
+                    otherAppsButton.post {
+                        otherAppsButton.visibility = newVisibility
+                    }
                 }
             }
         }
     }
 
     override fun onBackPressed() {
+        if (fromEditing) {
+            animateExitAndFinish()
+        }
         if (!fromProviderSelector) {
             openControlsOrigin()
         }
@@ -139,7 +156,7 @@
         appName = intent.getCharSequenceExtra(EXTRA_APP)
         structureExtra = intent.getCharSequenceExtra(EXTRA_STRUCTURE)
         component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
-        fromProviderSelector = intent.getBooleanExtra(EXTRA_FROM_PROVIDER_SELECTOR, false)
+        openSource = intent.getByteExtra(EXTRA_SOURCE, EXTRA_SOURCE_UNDEFINED)
 
         bindViews()
     }
@@ -148,14 +165,19 @@
         override fun onFirstChange() {
             doneButton.isEnabled = true
         }
+
+        override fun onChange() {
+            val structure: StructureContainer = listOfStructures[structurePager.currentItem]
+            rearrangeButton.isEnabled = structure.model.favorites.isNotEmpty()
+        }
     }
 
     private fun loadControls() {
-        component?.let {
+        component?.let { componentName ->
             statusText.text = resources.getText(com.android.internal.R.string.loading)
             val emptyZoneString = resources.getText(
                     R.string.controls_favorite_other_zone_header)
-            controller.loadForComponent(it, Consumer { data ->
+            controller.loadForComponent(componentName, { data ->
                 val allControls = data.allControls
                 val favoriteKeys = data.favoritesIds
                 val error = data.errorOnLoad
@@ -213,7 +235,7 @@
                         ControlsAnimations.enterAnimation(structurePager).start()
                     }
                 }
-            }, Consumer { runnable -> cancelLoadRunnable = runnable })
+            }, { runnable -> cancelLoadRunnable = runnable })
         }
     }
 
@@ -299,7 +321,8 @@
         bindButtons()
     }
 
-    private fun animateExitAndFinish() {
+    @VisibleForTesting
+    internal open fun animateExitAndFinish() {
         val rootView = requireViewById<ViewGroup>(R.id.controls_management_root)
         ControlsAnimations.exitAnimation(
                 rootView,
@@ -312,6 +335,32 @@
     }
 
     private fun bindButtons() {
+        rearrangeButton = requireViewById<Button>(R.id.rearrange).apply {
+            text = if (fromEditing) {
+                getString(R.string.controls_favorite_back_to_editing)
+            } else {
+                getString(R.string.controls_favorite_rearrange_button)
+            }
+            isEnabled = false
+            visibility = if (isNewFlowEnabled) View.VISIBLE else View.GONE
+            setOnClickListener {
+                if (component == null) return@setOnClickListener
+                saveFavorites()
+                startActivity(
+                    Intent(context, ControlsEditingActivity::class.java).also {
+                        it.putExtra(Intent.EXTRA_COMPONENT_NAME, component)
+                        it.putExtra(ControlsEditingActivity.EXTRA_APP, appName)
+                        it.putExtra(ControlsEditingActivity.EXTRA_FROM_FAVORITING, true)
+                        it.putExtra(
+                            ControlsEditingActivity.EXTRA_STRUCTURE,
+                            listOfStructures[structurePager.currentItem].structureName,
+                        )
+                    },
+                    ActivityOptions
+                        .makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle()
+                )
+            }
+        }
         otherAppsButton = requireViewById<Button>(R.id.other_apps).apply {
             setOnClickListener {
                 if (doneButton.isEnabled) {
@@ -335,18 +384,22 @@
             isEnabled = false
             setOnClickListener {
                 if (component == null) return@setOnClickListener
-                listOfStructures.forEach {
-                    val favoritesForStorage = it.model.favorites
-                    controller.replaceFavoritesForStructure(
-                        StructureInfo(component!!, it.structureName, favoritesForStorage)
-                    )
-                }
+                saveFavorites()
                 animateExitAndFinish()
                 openControlsOrigin()
             }
         }
     }
 
+    private fun saveFavorites() {
+        listOfStructures.forEach {
+            val favoritesForStorage = it.model.favorites
+            controller.replaceFavoritesForStructure(
+                StructureInfo(component!!, it.structureName, favoritesForStorage)
+            )
+        }
+    }
+
     private fun openControlsOrigin() {
         startActivity(
             Intent(applicationContext, ControlsActivity::class.java),
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
index d65481a..3455e6d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
@@ -71,6 +71,11 @@
          * Use to notify that the model has changed for the first time
          */
         fun onFirstChange()
+
+        /**
+         * Use to notify that the model has changed
+         */
+        fun onChange()
     }
 
     /**
@@ -132,7 +137,7 @@
         controlInfo: ControlInfo,
         favorite: Boolean,
         customIconGetter: (ComponentName, String) -> Icon?
-    ): this(component, controlInfo, favorite) {
+    ) : this(component, controlInfo, favorite) {
         this.customIconGetter = customIconGetter
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 3808e73..92aff06 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -211,7 +211,10 @@
                     putExtra(ControlsFavoritingActivity.EXTRA_APP,
                             listingController.getAppLabel(it))
                     putExtra(Intent.EXTRA_COMPONENT_NAME, it)
-                    putExtra(ControlsFavoritingActivity.EXTRA_FROM_PROVIDER_SELECTOR, true)
+                    putExtra(
+                        ControlsFavoritingActivity.EXTRA_SOURCE,
+                        ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR,
+                    )
                 }
                 startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
                 animateExitAndFinish()
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java b/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java
index 0aeb128..cf0dcad 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.doze;
 
+import java.util.concurrent.Executor;
+
 /**
  * Forwards the currently used brightness to {@link DozeHost}.
  */
@@ -23,8 +25,9 @@
 
     private final DozeHost mHost;
 
-    public DozeBrightnessHostForwarder(DozeMachine.Service wrappedService, DozeHost host) {
-        super(wrappedService);
+    public DozeBrightnessHostForwarder(DozeMachine.Service wrappedService, DozeHost host,
+            Executor bgExecutor) {
+        super(wrappedService, bgExecutor);
         mHost = host;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index f0aefb5..7f0b16b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -39,6 +39,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -150,7 +151,6 @@
     private final DockManager mDockManager;
     private final Part[] mParts;
     private final UserTracker mUserTracker;
-
     private final ArrayList<State> mQueuedRequests = new ArrayList<>();
     private State mState = State.UNINITIALIZED;
     private int mPulseReason;
@@ -512,9 +512,11 @@
 
         class Delegate implements Service {
             private final Service mDelegate;
+            private final Executor mBgExecutor;
 
-            public Delegate(Service delegate) {
+            public Delegate(Service delegate, Executor bgExecutor) {
                 mDelegate = delegate;
+                mBgExecutor = bgExecutor;
             }
 
             @Override
@@ -534,7 +536,9 @@
 
             @Override
             public void setDozeScreenBrightness(int brightness) {
-                mDelegate.setDozeScreenBrightness(brightness);
+                mBgExecutor.execute(() -> {
+                    mDelegate.setDozeScreenBrightness(brightness);
+                });
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
index 25c2c39..8d44472 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
@@ -22,14 +22,16 @@
 
 import com.android.systemui.statusbar.phone.DozeParameters;
 
+import java.util.concurrent.Executor;
+
 /**
  * Prevents usage of doze screen states on devices that don't support them.
  */
 public class DozeScreenStatePreventingAdapter extends DozeMachine.Service.Delegate {
 
     @VisibleForTesting
-    DozeScreenStatePreventingAdapter(DozeMachine.Service inner) {
-        super(inner);
+    DozeScreenStatePreventingAdapter(DozeMachine.Service inner, Executor bgExecutor) {
+        super(inner, bgExecutor);
     }
 
     @Override
@@ -47,8 +49,8 @@
      * return a new instance of {@link DozeScreenStatePreventingAdapter} wrapping {@code inner}.
      */
     public static DozeMachine.Service wrapIfNeeded(DozeMachine.Service inner,
-            DozeParameters params) {
-        return isNeeded(params) ? new DozeScreenStatePreventingAdapter(inner) : inner;
+            DozeParameters params, Executor bgExecutor) {
+        return isNeeded(params) ? new DozeScreenStatePreventingAdapter(inner, bgExecutor) : inner;
     }
 
     private static boolean isNeeded(DozeParameters params) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
index a0c490951..f7773f1 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
@@ -22,14 +22,16 @@
 
 import com.android.systemui.statusbar.phone.DozeParameters;
 
+import java.util.concurrent.Executor;
+
 /**
  * Prevents usage of doze screen states on devices that don't support them.
  */
 public class DozeSuspendScreenStatePreventingAdapter extends DozeMachine.Service.Delegate {
 
     @VisibleForTesting
-    DozeSuspendScreenStatePreventingAdapter(DozeMachine.Service inner) {
-        super(inner);
+    DozeSuspendScreenStatePreventingAdapter(DozeMachine.Service inner, Executor bgExecutor) {
+        super(inner, bgExecutor);
     }
 
     @Override
@@ -45,8 +47,9 @@
      * return a new instance of {@link DozeSuspendScreenStatePreventingAdapter} wrapping {@code inner}.
      */
     public static DozeMachine.Service wrapIfNeeded(DozeMachine.Service inner,
-            DozeParameters params) {
-        return isNeeded(params) ? new DozeSuspendScreenStatePreventingAdapter(inner) : inner;
+            DozeParameters params, Executor bgExecutor) {
+        return isNeeded(params) ? new DozeSuspendScreenStatePreventingAdapter(inner, bgExecutor)
+                : inner;
     }
 
     private static boolean isNeeded(DozeParameters params) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index 069344f..d408472 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -22,6 +22,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.doze.DozeAuthRemover;
 import com.android.systemui.doze.DozeBrightnessHostForwarder;
 import com.android.systemui.doze.DozeDockHandler;
@@ -45,13 +46,14 @@
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
+import dagger.Module;
+import dagger.Provides;
+
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
-
-import dagger.Module;
-import dagger.Provides;
+import java.util.concurrent.Executor;
 
 /** Dagger module for use with {@link com.android.systemui.doze.dagger.DozeComponent}. */
 @Module
@@ -60,13 +62,13 @@
     @DozeScope
     @WrappedService
     static DozeMachine.Service providesWrappedService(DozeMachine.Service dozeMachineService,
-            DozeHost dozeHost, DozeParameters dozeParameters) {
+            DozeHost dozeHost, DozeParameters dozeParameters, @UiBackground Executor bgExecutor) {
         DozeMachine.Service wrappedService = dozeMachineService;
-        wrappedService = new DozeBrightnessHostForwarder(wrappedService, dozeHost);
+        wrappedService = new DozeBrightnessHostForwarder(wrappedService, dozeHost, bgExecutor);
         wrappedService = DozeScreenStatePreventingAdapter.wrapIfNeeded(
-                wrappedService, dozeParameters);
+                wrappedService, dozeParameters, bgExecutor);
         wrappedService = DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(
-                wrappedService, dozeParameters);
+                wrappedService, dozeParameters, bgExecutor);
 
         return wrappedService;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index 74a49a8..c954f98 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -201,8 +201,6 @@
         mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback);
 
         mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
-
-        mTouchInsetSession.addViewToTracking(mView);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
index 43e4c62..7f44463 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
@@ -101,6 +101,10 @@
 
                     completer.set(predecessor);
                 }
+
+                if (mActiveTouchSessions.isEmpty() && mStopMonitoringPending) {
+                    stopMonitoring(false);
+                }
             });
 
             return "DreamOverlayTouchMonitor::pop";
@@ -214,7 +218,12 @@
 
         @Override
         public void onPause(@NonNull LifecycleOwner owner) {
-            stopMonitoring();
+            stopMonitoring(false);
+        }
+
+        @Override
+        public void onDestroy(LifecycleOwner owner) {
+            stopMonitoring(true);
         }
     };
 
@@ -222,7 +231,7 @@
      * When invoked, instantiates a new {@link InputSession} to monitor touch events.
      */
     private void startMonitoring() {
-        stopMonitoring();
+        stopMonitoring(true);
         mCurrentInputSession = mInputSessionFactory.create(
                 "dreamOverlay",
                 mInputEventListener,
@@ -234,11 +243,16 @@
     /**
      * Destroys any active {@link InputSession}.
      */
-    private void stopMonitoring() {
+    private void stopMonitoring(boolean force) {
         if (mCurrentInputSession == null) {
             return;
         }
 
+        if (!mActiveTouchSessions.isEmpty() && !force) {
+            mStopMonitoringPending = true;
+            return;
+        }
+
         // When we stop monitoring touches, we must ensure that all active touch sessions and
         // descendants informed of the removal so any cleanup for active tracking can proceed.
         mExecutor.execute(() -> mActiveTouchSessions.forEach(touchSession -> {
@@ -250,6 +264,7 @@
 
         mCurrentInputSession.dispose();
         mCurrentInputSession = null;
+        mStopMonitoringPending = false;
     }
 
 
@@ -257,6 +272,8 @@
     private final Collection<DreamTouchHandler> mHandlers;
     private final DisplayHelper mDisplayHelper;
 
+    private boolean mStopMonitoringPending;
+
     private InputChannelCompat.InputEventListener mInputEventListener =
             new InputChannelCompat.InputEventListener() {
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
new file mode 100644
index 0000000..58b70b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.touch;
+
+import static com.android.systemui.dreams.touch.dagger.ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link ShadeTouchHandler} is responsible for handling swipe down gestures over dream
+ * to bring down the shade.
+ */
+public class ShadeTouchHandler implements DreamTouchHandler {
+    private final Optional<CentralSurfaces> mSurfaces;
+    private final int mInitiationHeight;
+
+    @Inject
+    ShadeTouchHandler(Optional<CentralSurfaces> centralSurfaces,
+            @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) int initiationHeight) {
+        mSurfaces = centralSurfaces;
+        mInitiationHeight = initiationHeight;
+    }
+
+    @Override
+    public void onSessionStart(TouchSession session) {
+        if (mSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false)) {
+            session.pop();
+            return;
+        }
+
+        session.registerInputListener(ev -> {
+            final NotificationPanelViewController viewController =
+                    mSurfaces.map(CentralSurfaces::getNotificationPanelViewController).orElse(null);
+
+            if (viewController != null) {
+                viewController.handleExternalTouch((MotionEvent) ev);
+            }
+
+            if (ev instanceof MotionEvent) {
+                if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
+                    session.pop();
+                }
+            }
+        });
+
+        session.registerGestureListener(new GestureDetector.SimpleOnGestureListener() {
+            @Override
+            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+                    float distanceY) {
+                return true;
+            }
+
+            @Override
+            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+                    float velocityY) {
+                return true;
+            }
+        });
+    }
+
+    @Override
+    public void getTouchInitiationRegion(Rect bounds, Region region) {
+        final Rect outBounds = new Rect(bounds);
+        outBounds.inset(0, 0, 0, outBounds.height() - mInitiationHeight);
+        region.op(outBounds, Region.Op.UNION);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
index dad0004..b719126 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
@@ -23,6 +23,7 @@
  */
 @Module(includes = {
             BouncerSwipeModule.class,
+            ShadeModule.class,
         }, subcomponents = {
             InputSessionComponent.class,
 })
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
new file mode 100644
index 0000000..9e0ae41
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.touch.dagger;
+
+import android.content.res.Resources;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.touch.DreamTouchHandler;
+import com.android.systemui.dreams.touch.ShadeTouchHandler;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.IntoSet;
+
+import javax.inject.Named;
+
+/**
+ * Dependencies for swipe down to notification over dream.
+ */
+@Module
+public class ShadeModule {
+    /**
+     * The height, defined in pixels, of the gesture initiation region at the top of the screen for
+     * swiping down notifications.
+     */
+    public static final String NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT =
+            "notification_shade_gesture_initiation_height";
+
+    /**
+     * Provides {@link ShadeTouchHandler} to handle notification swipe down over dream.
+     */
+    @Provides
+    @IntoSet
+    public static DreamTouchHandler providesNotificationShadeTouchHandler(
+            ShadeTouchHandler touchHandler) {
+        return touchHandler;
+    }
+
+    /**
+     * Provides the height of the gesture area for notification swipe down.
+     */
+    @Provides
+    @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT)
+    public static int providesNotificationShadeGestureRegionHeight(@Main Resources resources) {
+        return resources.getDimensionPixelSize(R.dimen.dream_overlay_status_bar_height);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 095ae38..0cd2791 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -101,16 +101,16 @@
         releasedFlag(174148361, "notification_inline_reply_animation")
 
     val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
-        releasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
+        releasedFlag(254647461, "filter_unseen_notifs_on_keyguard")
 
     // TODO(b/263414400): Tracking Bug
     @JvmField
     val NOTIFICATION_ANIMATE_BIG_PICTURE =
-        releasedFlag(120, "notification_animate_big_picture", teamfood = true)
+        releasedFlag(120, "notification_animate_big_picture")
 
     @JvmField
     val ANIMATED_NOTIFICATION_SHADE_INSETS =
-        unreleasedFlag(270682168, "animated_notification_shade_insets", teamfood = true)
+        releasedFlag(270682168, "animated_notification_shade_insets")
 
     // TODO(b/268005230): Tracking Bug
     @JvmField val SENSITIVE_REVEAL_ANIM = unreleasedFlag(268005230, "sensitive_reveal_anim")
@@ -184,7 +184,7 @@
     // flag for controlling auto pin confirmation and material u shapes in bouncer
     @JvmField
     val AUTO_PIN_CONFIRMATION =
-        releasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation", teamfood = true)
+        releasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation")
 
     // TODO(b/262859270): Tracking Bug
     @JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag(225, "falsing_off_for_unfolded")
@@ -621,15 +621,15 @@
     @JvmField val NOTE_TASKS = releasedFlag(1900, "keycode_flag")
 
     // 2000 - device controls
-    @Keep @JvmField val USE_APP_PANELS = releasedFlag(2000, "use_app_panels", teamfood = true)
+    @Keep @JvmField val USE_APP_PANELS = releasedFlag(2000, "use_app_panels")
 
     @JvmField
     val APP_PANELS_ALL_APPS_ALLOWED =
-        releasedFlag(2001, "app_panels_all_apps_allowed", teamfood = true)
+        releasedFlag(2001, "app_panels_all_apps_allowed")
 
     @JvmField
     val CONTROLS_MANAGEMENT_NEW_FLOWS =
-        releasedFlag(2002, "controls_management_new_flows", teamfood = true)
+        releasedFlag(2002, "controls_management_new_flows")
 
     // Enables removing app from Home control panel as a part of a new flow
     // TODO(b/269132640): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
new file mode 100644
index 0000000..801b165
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
@@ -0,0 +1,493 @@
+package com.android.systemui.graphics
+
+import android.annotation.AnyThread
+import android.annotation.DrawableRes
+import android.annotation.Px
+import android.annotation.SuppressLint
+import android.annotation.WorkerThread
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.content.res.Resources.NotFoundException
+import android.graphics.Bitmap
+import android.graphics.ImageDecoder
+import android.graphics.ImageDecoder.DecodeException
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.util.Log
+import android.util.Size
+import androidx.core.content.res.ResourcesCompat
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import java.io.IOException
+import javax.inject.Inject
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/**
+ * Helper class to load images for SystemUI. It allows for memory efficient image loading with size
+ * restriction and attempts to use hardware bitmaps when sensible.
+ */
+@SysUISingleton
+class ImageLoader
+@Inject
+constructor(
+    private val defaultContext: Context,
+    @Background private val backgroundDispatcher: CoroutineDispatcher
+) {
+
+    /** Source of the image data. */
+    sealed interface Source
+
+    /**
+     * Load image from a Resource ID. If the resource is part of another package or if it requires
+     * tinting, pass in a correct [Context].
+     */
+    data class Res(@DrawableRes val resId: Int, val context: Context?) : Source {
+        constructor(@DrawableRes resId: Int) : this(resId, null)
+    }
+
+    /** Load image from a Uri. */
+    data class Uri(val uri: android.net.Uri) : Source {
+        constructor(uri: String) : this(android.net.Uri.parse(uri))
+    }
+
+    /** Load image from a [File]. */
+    data class File(val file: java.io.File) : Source {
+        constructor(path: String) : this(java.io.File(path))
+    }
+
+    /** Load image from an [InputStream]. */
+    data class InputStream(val inputStream: java.io.InputStream, val context: Context?) : Source {
+        constructor(inputStream: java.io.InputStream) : this(inputStream, null)
+    }
+
+    /**
+     * Loads passed [Source] on a background thread and returns the [Bitmap].
+     *
+     * Maximum height and width can be passed as optional parameters - the image decoder will make
+     * sure to keep the decoded drawable size within those passed constraints while keeping aspect
+     * ratio.
+     *
+     * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+     *   to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+     * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+     *   Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+     * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+     *   ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+     * @return loaded [Bitmap] or `null` if loading failed.
+     */
+    @AnyThread
+    suspend fun loadBitmap(
+        source: Source,
+        @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+        @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+    ): Bitmap? =
+        withContext(backgroundDispatcher) { loadBitmapSync(source, maxWidth, maxHeight, allocator) }
+
+    /**
+     * Loads passed [Source] synchronously and returns the [Bitmap].
+     *
+     * Maximum height and width can be passed as optional parameters - the image decoder will make
+     * sure to keep the decoded drawable size within those passed constraints while keeping aspect
+     * ratio.
+     *
+     * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+     *   to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+     * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+     *   Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+     * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+     *   ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+     * @return loaded [Bitmap] or `null` if loading failed.
+     */
+    @WorkerThread
+    fun loadBitmapSync(
+        source: Source,
+        @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+        @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+    ): Bitmap? {
+        return try {
+            loadBitmapSync(
+                toImageDecoderSource(source, defaultContext),
+                maxWidth,
+                maxHeight,
+                allocator
+            )
+        } catch (e: NotFoundException) {
+            Log.w(TAG, "Couldn't load resource $source", e)
+            null
+        }
+    }
+
+    /**
+     * Loads passed [ImageDecoder.Source] synchronously and returns the drawable.
+     *
+     * Maximum height and width can be passed as optional parameters - the image decoder will make
+     * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+     * ratio).
+     *
+     * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+     *   to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+     * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+     *   Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+     * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+     *   ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+     * @return loaded [Bitmap] or `null` if loading failed.
+     */
+    @WorkerThread
+    fun loadBitmapSync(
+        source: ImageDecoder.Source,
+        @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+        @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+    ): Bitmap? {
+        return try {
+            ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
+                configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+                decoder.allocator = allocator
+            }
+        } catch (e: IOException) {
+            Log.w(TAG, "Failed to load source $source", e)
+            return null
+        } catch (e: DecodeException) {
+            Log.w(TAG, "Failed to decode source $source", e)
+            return null
+        }
+    }
+
+    /**
+     * Loads passed [Source] on a background thread and returns the [Drawable].
+     *
+     * Maximum height and width can be passed as optional parameters - the image decoder will make
+     * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+     * ratio).
+     *
+     * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+     *   to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+     * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+     *   Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+     * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+     *   ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+     * @return loaded [Drawable] or `null` if loading failed.
+     */
+    @AnyThread
+    suspend fun loadDrawable(
+        source: Source,
+        @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+        @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+    ): Drawable? =
+        withContext(backgroundDispatcher) {
+            loadDrawableSync(source, maxWidth, maxHeight, allocator)
+        }
+
+    /**
+     * Loads passed [Icon] on a background thread and returns the drawable.
+     *
+     * Maximum height and width can be passed as optional parameters - the image decoder will make
+     * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+     * ratio).
+     *
+     * @param context Alternate context to use for resource loading (for e.g. cross-process use)
+     * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+     *   to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+     * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+     *   Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+     * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+     *   ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+     * @return loaded [Drawable] or `null` if loading failed.
+     */
+    @AnyThread
+    suspend fun loadDrawable(
+        icon: Icon,
+        context: Context = defaultContext,
+        @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+        @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+    ): Drawable? =
+        withContext(backgroundDispatcher) {
+            loadDrawableSync(icon, context, maxWidth, maxHeight, allocator)
+        }
+
+    /**
+     * Loads passed [Source] synchronously and returns the drawable.
+     *
+     * Maximum height and width can be passed as optional parameters - the image decoder will make
+     * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+     * ratio).
+     *
+     * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+     *   to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+     * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+     *   Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+     * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+     *   ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+     * @return loaded [Drawable] or `null` if loading failed.
+     */
+    @WorkerThread
+    @SuppressLint("UseCompatLoadingForDrawables")
+    fun loadDrawableSync(
+        source: Source,
+        @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+        @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+    ): Drawable? {
+        return try {
+            loadDrawableSync(
+                toImageDecoderSource(source, defaultContext),
+                maxWidth,
+                maxHeight,
+                allocator
+            )
+                ?:
+                // If we have a resource, retry fallback using the "normal" Resource loading system.
+                // This will come into effect in cases like trying to load AnimatedVectorDrawable.
+                if (source is Res) {
+                    val context = source.context ?: defaultContext
+                    ResourcesCompat.getDrawable(context.resources, source.resId, context.theme)
+                } else {
+                    null
+                }
+        } catch (e: NotFoundException) {
+            Log.w(TAG, "Couldn't load resource $source", e)
+            null
+        }
+    }
+
+    /**
+     * Loads passed [ImageDecoder.Source] synchronously and returns the drawable.
+     *
+     * Maximum height and width can be passed as optional parameters - the image decoder will make
+     * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+     * ratio).
+     *
+     * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+     *   to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+     * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+     *   Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+     * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+     *   ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+     * @return loaded [Drawable] or `null` if loading failed.
+     */
+    @WorkerThread
+    fun loadDrawableSync(
+        source: ImageDecoder.Source,
+        @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+        @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+    ): Drawable? {
+        return try {
+            ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
+                configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+                decoder.allocator = allocator
+            }
+        } catch (e: IOException) {
+            Log.w(TAG, "Failed to load source $source", e)
+            return null
+        } catch (e: DecodeException) {
+            Log.w(TAG, "Failed to decode source $source", e)
+            return null
+        }
+    }
+
+    /** Loads icon drawable while attempting to size restrict the drawable. */
+    @WorkerThread
+    fun loadDrawableSync(
+        icon: Icon,
+        context: Context = defaultContext,
+        @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+        @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+    ): Drawable? {
+        return when (icon.type) {
+            Icon.TYPE_URI,
+            Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
+                val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
+                loadDrawableSync(source, maxWidth, maxHeight, allocator)
+            }
+            Icon.TYPE_RESOURCE -> {
+                val resources = resolveResourcesForIcon(context, icon)
+                resources?.let {
+                    loadDrawableSync(
+                        ImageDecoder.createSource(it, icon.resId),
+                        maxWidth,
+                        maxHeight,
+                        allocator
+                    )
+                }
+                // Fallback to non-ImageDecoder load if the attempt failed (e.g. the resource
+                // is a Vector drawable which ImageDecoder doesn't support.)
+                ?: icon.loadDrawable(context)
+            }
+            Icon.TYPE_BITMAP -> {
+                BitmapDrawable(context.resources, icon.bitmap)
+            }
+            Icon.TYPE_ADAPTIVE_BITMAP -> {
+                AdaptiveIconDrawable(null, BitmapDrawable(context.resources, icon.bitmap))
+            }
+            Icon.TYPE_DATA -> {
+                loadDrawableSync(
+                    ImageDecoder.createSource(icon.dataBytes, icon.dataOffset, icon.dataLength),
+                    maxWidth,
+                    maxHeight,
+                    allocator
+                )
+            }
+            else -> {
+                // We don't recognize this icon, just fallback.
+                icon.loadDrawable(context)
+            }
+        }?.let { drawable ->
+            // Icons carry tint which we need to propagate down to a Drawable.
+            tintDrawable(icon, drawable)
+            drawable
+        }
+    }
+
+    companion object {
+        const val TAG = "ImageLoader"
+
+        // 4096 is a reasonable default - most devices will support 4096x4096 texture size for
+        // Canvas rendering and by default we SystemUI has no need to render larger bitmaps.
+        // This prevents exceptions and crashes if the code accidentally loads larger Bitmap
+        // and then attempts to render it on Canvas.
+        // It can always be overridden by the parameters.
+        const val DEFAULT_MAX_SAFE_BITMAP_SIZE_PX = 4096
+
+        /**
+         * This constant signals that ImageLoader shouldn't attempt to resize the passed bitmap in a
+         * given dimension.
+         *
+         * Set both maxWidth and maxHeight to [DO_NOT_RESIZE] if you wish to prevent resizing.
+         */
+        const val DO_NOT_RESIZE = 0
+
+        /** Maps [Source] to [ImageDecoder.Source]. */
+        private fun toImageDecoderSource(source: Source, defaultContext: Context) =
+            when (source) {
+                is Res -> {
+                    val context = source.context ?: defaultContext
+                    ImageDecoder.createSource(context.resources, source.resId)
+                }
+                is File -> ImageDecoder.createSource(source.file)
+                is Uri -> ImageDecoder.createSource(defaultContext.contentResolver, source.uri)
+                is InputStream -> {
+                    val context = source.context ?: defaultContext
+                    ImageDecoder.createSource(context.resources, source.inputStream)
+                }
+            }
+
+        /**
+         * This sets target size on the image decoder to conform to the maxWidth / maxHeight
+         * parameters. The parameters are chosen to keep the existing drawable aspect ratio.
+         */
+        @AnyThread
+        private fun configureDecoderForMaximumSize(
+            decoder: ImageDecoder,
+            imgSize: Size,
+            @Px maxWidth: Int,
+            @Px maxHeight: Int
+        ) {
+            if (maxWidth == DO_NOT_RESIZE && maxHeight == DO_NOT_RESIZE) {
+                return
+            }
+
+            if (imgSize.width <= maxWidth && imgSize.height <= maxHeight) {
+                return
+            }
+
+            // Determine the scale factor for each dimension so it fits within the set constraint
+            val wScale =
+                if (maxWidth <= 0) {
+                    1.0f
+                } else {
+                    maxWidth.toFloat() / imgSize.width.toFloat()
+                }
+
+            val hScale =
+                if (maxHeight <= 0) {
+                    1.0f
+                } else {
+                    maxHeight.toFloat() / imgSize.height.toFloat()
+                }
+
+            // Scale down to the dimension that demands larger scaling (smaller scale factor).
+            // Use the same scale for both dimensions to keep the aspect ratio.
+            val scale = min(wScale, hScale)
+            if (scale < 1.0f) {
+                val targetWidth = (imgSize.width * scale).toInt()
+                val targetHeight = (imgSize.height * scale).toInt()
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Configured image size to $targetWidth x $targetHeight")
+                }
+
+                decoder.setTargetSize(targetWidth, targetHeight)
+            }
+        }
+
+        /**
+         * Attempts to retrieve [Resources] class required to load the passed icon. Icons can
+         * originate from other processes so we need to make sure we load them from the right
+         * package source.
+         *
+         * @return [Resources] to load the icon drawble or null if icon doesn't carry a resource or
+         *   the resource package couldn't be resolved.
+         */
+        @WorkerThread
+        private fun resolveResourcesForIcon(context: Context, icon: Icon): Resources? {
+            if (icon.type != Icon.TYPE_RESOURCE) {
+                return null
+            }
+
+            val resources = icon.resources
+            if (resources != null) {
+                return resources
+            }
+
+            val resPackage = icon.resPackage
+            if (
+                resPackage == null || resPackage.isEmpty() || context.packageName.equals(resPackage)
+            ) {
+                return context.resources
+            }
+
+            if ("android" == resPackage) {
+                return Resources.getSystem()
+            }
+
+            val pm = context.packageManager
+            try {
+                val ai =
+                    pm.getApplicationInfo(
+                        resPackage,
+                        PackageManager.MATCH_UNINSTALLED_PACKAGES or
+                            PackageManager.GET_SHARED_LIBRARY_FILES
+                    )
+                if (ai != null) {
+                    return pm.getResourcesForApplication(ai)
+                } else {
+                    Log.w(TAG, "Failed to resolve application info for $resPackage")
+                }
+            } catch (e: PackageManager.NameNotFoundException) {
+                Log.w(TAG, "Failed to resolve resource package", e)
+                return null
+            }
+            return null
+        }
+
+        /** Applies tinting from [Icon] to the passed [Drawable]. */
+        @AnyThread
+        private fun tintDrawable(icon: Icon, drawable: Drawable) {
+            if (icon.hasTint()) {
+                drawable.mutate()
+                drawable.setTintList(icon.tintList)
+                drawable.setTintBlendMode(icon.tintBlendMode)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index eef7ccc..107e685 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -231,22 +231,20 @@
                 );
             }
 
-            public void mergeAnimation(IBinder transition, TransitionInfo info,
-                    SurfaceControl.Transaction t, IBinder mergeTarget,
-                    IRemoteTransitionFinishedCallback finishCallback) {
+            public void mergeAnimation(IBinder candidateTransition, TransitionInfo candidateInfo,
+                    SurfaceControl.Transaction candidateT, IBinder currentTransition,
+                    IRemoteTransitionFinishedCallback candidateFinishCallback) {
                 try {
-                    final IRemoteTransitionFinishedCallback origFinishCB;
+                    final IRemoteTransitionFinishedCallback currentFinishCB;
                     synchronized (mFinishCallbacks) {
-                        origFinishCB = mFinishCallbacks.remove(transition);
+                        currentFinishCB = mFinishCallbacks.remove(currentTransition);
                     }
-                    info.releaseAllSurfaces();
-                    t.close();
-                    if (origFinishCB == null) {
-                        // already finished (or not started yet), so do nothing.
+                    if (currentFinishCB == null) {
+                        Slog.e(TAG, "Called mergeAnimation, but finish callback is missing");
                         return;
                     }
                     runner.onAnimationCancelled(false /* isKeyguardOccluded */);
-                    origFinishCB.onTransitionFinished(null /* wct */, null /* t */);
+                    currentFinishCB.onTransitionFinished(null /* wct */, null /* t */);
                 } catch (RemoteException e) {
                     // nothing, we'll just let it finish on its own I guess.
                 }
@@ -304,13 +302,13 @@
         Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_GOING_AWAY");
         TransitionFilter f = new TransitionFilter();
         f.mFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
-        mShellTransitions.registerRemote(f,
-                new RemoteTransition(wrap(mExitAnimationRunner), getIApplicationThread()));
+        mShellTransitions.registerRemote(f, new RemoteTransition(
+                wrap(mExitAnimationRunner), getIApplicationThread(), "ExitKeyguard"));
 
         Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_(UN)OCCLUDE");
         // Register for occluding
         final RemoteTransition occludeTransition = new RemoteTransition(
-                mOccludeAnimation, getIApplicationThread());
+                mOccludeAnimation, getIApplicationThread(), "KeyguardOcclude");
         f = new TransitionFilter();
         f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
         f.mRequirements = new TransitionFilter.Requirement[]{
@@ -329,7 +327,7 @@
 
         // Now register for un-occlude.
         final RemoteTransition unoccludeTransition = new RemoteTransition(
-                mUnoccludeAnimation, getIApplicationThread());
+                mUnoccludeAnimation, getIApplicationThread(), "KeyguardUnocclude");
         f = new TransitionFilter();
         f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
         f.mRequirements = new TransitionFilter.Requirement[]{
@@ -384,7 +382,7 @@
         f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
         mShellTransitions.registerRemote(f, new RemoteTransition(
                 wrap(mKeyguardViewMediator.getOccludeByDreamAnimationRunner()),
-                getIApplicationThread()));
+                getIApplicationThread(), "KeyguardOccludeByDream"));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 364e79c..ea5fc1a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -953,10 +953,15 @@
 
                 @Override
                 public void onAnimationCancelled(boolean isKeyguardOccluded) {
-                    if (mOccludeByDreamAnimator != null) {
-                        mOccludeByDreamAnimator.cancel();
-                    }
-                    setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */);
+                    mContext.getMainExecutor().execute(() -> {
+                        if (mOccludeByDreamAnimator != null) {
+                            mOccludeByDreamAnimator.cancel();
+                        }
+                    });
+                    // The value of isKeyguardOccluded here may come from mergeAnimation, which
+                    // isn't reliable. In all cases, after running or cancelling this animation,
+                    // keyguard should be occluded.
+                    setOccluded(true /* isOccluded */, false /* animate */);
                     if (DEBUG) {
                         Log.d(TAG, "Occlude by Dream animation cancelled. Occluded state is now: "
                                 + mOccluded);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 4cdcafd..8764f12 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -44,6 +44,7 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
+import com.android.systemui.keyguard.data.repository.KeyguardFaceAuthModule;
 import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
 import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
@@ -67,8 +68,6 @@
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
-import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.CoroutineScope;
 
 /**
  * Dagger Module providing keyguard.
@@ -83,6 +82,7 @@
             KeyguardDataQuickAffordanceModule.class,
             KeyguardQuickAffordanceModule.class,
             KeyguardRepositoryModule.class,
+            KeyguardFaceAuthModule.class,
             StartKeyguardTransitionModule.class,
         })
 public class KeyguardModule {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index d5129a6..09002fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -87,6 +87,13 @@
      */
     val isStrongBiometricAllowed: StateFlow<Boolean>
 
+    /**
+     * Whether the current user is allowed to use a convenience biometric for device entry based on
+     * Android Security policies. If false, the user may be able to use strong biometric or primary
+     * authentication for device entry.
+     */
+    val isNonStrongBiometricAllowed: StateFlow<Boolean>
+
     /** Whether fingerprint feature is enabled for the current user by the DevicePolicy */
     val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean>
 
@@ -276,6 +283,16 @@
             )
         )
 
+    override val isNonStrongBiometricAllowed: StateFlow<Boolean> =
+        strongAuthTracker.isNonStrongBiometricAllowed.stateIn(
+            scope,
+            SharingStarted.Eagerly,
+            strongAuthTracker.isBiometricAllowedForUser(
+                false,
+                userRepository.getSelectedUserInfo().id
+            )
+        )
+
     override val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
         selectedUserId
             .flatMapLatest { userId ->
@@ -297,40 +314,62 @@
 private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) :
     LockPatternUtils.StrongAuthTracker(context) {
 
-    private val _authFlags =
+    // Backing field for onStrongAuthRequiredChanged
+    private val _strongAuthFlags =
         MutableStateFlow(
             StrongAuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId))
         )
 
+    // Backing field for onIsNonStrongBiometricAllowedChanged
+    private val _nonStrongBiometricAllowed =
+        MutableStateFlow(
+            Pair(currentUserId, isNonStrongBiometricAllowedAfterIdleTimeout(currentUserId))
+        )
+
     val currentUserAuthFlags: Flow<StrongAuthenticationFlags> =
         userRepository.selectedUserInfo
             .map { it.id }
             .distinctUntilChanged()
-            .flatMapLatest { currUserId ->
-                _authFlags
-                    .filter { it.userId == currUserId }
+            .flatMapLatest { userId ->
+                _strongAuthFlags
+                    .filter { it.userId == userId }
                     .onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") }
                     .onStart {
-                        emit(
-                            StrongAuthenticationFlags(
-                                currentUserId,
-                                getStrongAuthForUser(currentUserId)
-                            )
-                        )
+                        emit(StrongAuthenticationFlags(userId, getStrongAuthForUser(userId)))
                     }
             }
 
+    /** isStrongBiometricAllowed for the current user. */
     val isStrongBiometricAllowed: Flow<Boolean> =
         currentUserAuthFlags.map { isBiometricAllowedForUser(true, it.userId) }
 
+    /** isNonStrongBiometricAllowed for the current user. */
+    val isNonStrongBiometricAllowed: Flow<Boolean> =
+        userRepository.selectedUserInfo
+            .map { it.id }
+            .distinctUntilChanged()
+            .flatMapLatest { userId ->
+                _nonStrongBiometricAllowed
+                    .filter { it.first == userId }
+                    .map { it.second }
+                    .onEach { Log.d(TAG, "isNonStrongBiometricAllowed changed for current user") }
+                    .onStart { emit(isNonStrongBiometricAllowedAfterIdleTimeout(userId)) }
+            }
+
     private val currentUserId
         get() = userRepository.getSelectedUserInfo().id
 
     override fun onStrongAuthRequiredChanged(userId: Int) {
         val newFlags = getStrongAuthForUser(userId)
-        _authFlags.value = StrongAuthenticationFlags(userId, newFlags)
+        _strongAuthFlags.value = StrongAuthenticationFlags(userId, newFlags)
         Log.d(TAG, "onStrongAuthRequiredChanged for userId: $userId, flag value: $newFlags")
     }
+
+    override fun onIsNonStrongBiometricAllowedChanged(userId: Int) {
+        val allowed = isNonStrongBiometricAllowedAfterIdleTimeout(userId)
+        _nonStrongBiometricAllowed.value = Pair(userId, allowed)
+        Log.d(TAG, "onIsNonStrongBiometricAllowedChanged for userId: $userId, $allowed")
+    }
 }
 
 private fun DevicePolicyManager.isFaceDisabled(userId: Int): Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
new file mode 100644
index 0000000..56e7398
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -0,0 +1,540 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.hardware.face.FaceAuthenticateOptions
+import android.hardware.face.FaceManager
+import android.os.CancellationSignal
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.log.SessionTracker
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.user.data.repository.UserRepository
+import java.io.PrintWriter
+import java.util.Arrays
+import java.util.stream.Collectors
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * API to run face authentication and detection for device entry / on keyguard (as opposed to the
+ * biometric prompt).
+ */
+interface DeviceEntryFaceAuthRepository {
+    /** Provide the current face authentication state for device entry. */
+    val isAuthenticated: Flow<Boolean>
+
+    /** Whether face auth can run at this point. */
+    val canRunFaceAuth: Flow<Boolean>
+
+    /** Provide the current status of face authentication. */
+    val authenticationStatus: Flow<AuthenticationStatus>
+
+    /** Provide the current status of face detection. */
+    val detectionStatus: Flow<DetectionStatus>
+
+    /** Current state of whether face authentication is locked out or not. */
+    val isLockedOut: Flow<Boolean>
+
+    /** Current state of whether face authentication is running. */
+    val isAuthRunning: Flow<Boolean>
+
+    /**
+     * Trigger face authentication.
+     *
+     * [uiEvent] provided should be logged whenever face authentication runs. Invocation should be
+     * ignored if face authentication is already running. Results should be propagated through
+     * [authenticationStatus]
+     *
+     * Run only face detection when [fallbackToDetection] is true and [canRunFaceAuth] is false.
+     */
+    suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean = false)
+
+    /** Stop currently running face authentication or detection. */
+    fun cancel()
+}
+
+@SysUISingleton
+class DeviceEntryFaceAuthRepositoryImpl
+@Inject
+constructor(
+    context: Context,
+    private val faceManager: FaceManager? = null,
+    private val userRepository: UserRepository,
+    private val keyguardBypassController: KeyguardBypassController? = null,
+    @Application private val applicationScope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    private val sessionTracker: SessionTracker,
+    private val uiEventsLogger: UiEventLogger,
+    private val faceAuthLogger: FaceAuthenticationLogger,
+    private val biometricSettingsRepository: BiometricSettingsRepository,
+    private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+    private val trustRepository: TrustRepository,
+    private val keyguardRepository: KeyguardRepository,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    dumpManager: DumpManager,
+) : DeviceEntryFaceAuthRepository, Dumpable {
+    private var authCancellationSignal: CancellationSignal? = null
+    private var detectCancellationSignal: CancellationSignal? = null
+    private var faceAcquiredInfoIgnoreList: Set<Int>
+
+    private var cancelNotReceivedHandlerJob: Job? = null
+
+    private val _authenticationStatus: MutableStateFlow<AuthenticationStatus?> =
+        MutableStateFlow(null)
+    override val authenticationStatus: Flow<AuthenticationStatus>
+        get() = _authenticationStatus.filterNotNull()
+
+    private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
+    override val detectionStatus: Flow<DetectionStatus>
+        get() = _detectionStatus.filterNotNull()
+
+    private val _isLockedOut = MutableStateFlow(false)
+    override val isLockedOut: StateFlow<Boolean> = _isLockedOut
+
+    val isDetectionSupported =
+        faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection ?: false
+
+    private val _isAuthRunning = MutableStateFlow(false)
+    override val isAuthRunning: StateFlow<Boolean>
+        get() = _isAuthRunning
+
+    private val keyguardSessionId: InstanceId?
+        get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
+
+    private val _canRunFaceAuth = MutableStateFlow(true)
+    override val canRunFaceAuth: StateFlow<Boolean>
+        get() = _canRunFaceAuth
+
+    private val canRunDetection = MutableStateFlow(false)
+
+    private val _isAuthenticated = MutableStateFlow(false)
+    override val isAuthenticated: Flow<Boolean>
+        get() = _isAuthenticated
+
+    private val bypassEnabled: Flow<Boolean> =
+        keyguardBypassController?.let {
+            conflatedCallbackFlow {
+                val callback =
+                    object : KeyguardBypassController.OnBypassStateChangedListener {
+                        override fun onBypassStateChanged(isEnabled: Boolean) {
+                            trySendWithFailureLogging(isEnabled, TAG, "BypassStateChanged")
+                        }
+                    }
+                it.registerOnBypassStateChangedListener(callback)
+                trySendWithFailureLogging(it.bypassEnabled, TAG, "BypassStateChanged")
+                awaitClose { it.unregisterOnBypassStateChangedListener(callback) }
+            }
+        }
+            ?: flowOf(false)
+
+    private val faceLockoutResetCallback =
+        object : FaceManager.LockoutResetCallback() {
+            override fun onLockoutReset(sensorId: Int) {
+                _isLockedOut.value = false
+            }
+        }
+
+    init {
+        faceManager?.addLockoutResetCallback(faceLockoutResetCallback)
+        faceAcquiredInfoIgnoreList =
+            Arrays.stream(
+                    context.resources.getIntArray(
+                        R.array.config_face_acquire_device_entry_ignorelist
+                    )
+                )
+                .boxed()
+                .collect(Collectors.toSet())
+        dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this)
+
+        observeFaceAuthGatingChecks()
+        observeFaceDetectGatingChecks()
+        observeFaceAuthResettingConditions()
+    }
+
+    private fun observeFaceAuthResettingConditions() {
+        // Clear auth status when keyguard is going away or when the user is switching.
+        merge(keyguardRepository.isKeyguardGoingAway, userRepository.userSwitchingInProgress)
+            .onEach { goingAwayOrUserSwitchingInProgress ->
+                if (goingAwayOrUserSwitchingInProgress) {
+                    _isAuthenticated.value = false
+                }
+            }
+            .launchIn(applicationScope)
+    }
+
+    private fun observeFaceDetectGatingChecks() {
+        // Face detection can run only when lockscreen bypass is enabled
+        // & detection is supported & biometric unlock is not allowed.
+        listOf(
+                canFaceAuthOrDetectRun(),
+                logAndObserve(bypassEnabled, "bypassEnabled"),
+                logAndObserve(
+                    biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(),
+                    "nonStrongBiometricIsNotAllowed"
+                ),
+                // We don't want to run face detect if it's not possible to authenticate with FP
+                // from the bouncer. UDFPS is the only fp sensor type that won't support this.
+                logAndObserve(
+                    and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning).isFalse(),
+                    "udfpsAuthIsNotPossibleAnymore"
+                )
+            )
+            .reduce(::and)
+            .distinctUntilChanged()
+            .onEach {
+                faceAuthLogger.canRunDetectionChanged(it)
+                canRunDetection.value = it
+                if (!it) {
+                    cancelDetection()
+                }
+            }
+            .launchIn(applicationScope)
+    }
+
+    private fun isUdfps() =
+        deviceEntryFingerprintAuthRepository.availableFpSensorType.map {
+            it == BiometricType.UNDER_DISPLAY_FINGERPRINT
+        }
+
+    private fun canFaceAuthOrDetectRun(): Flow<Boolean> {
+        return listOf(
+                logAndObserve(biometricSettingsRepository.isFaceEnrolled, "isFaceEnrolled"),
+                logAndObserve(
+                    biometricSettingsRepository.isFaceAuthenticationEnabled,
+                    "isFaceAuthenticationEnabled"
+                ),
+                logAndObserve(
+                    userRepository.userSwitchingInProgress.isFalse(),
+                    "userSwitchingNotInProgress"
+                ),
+                logAndObserve(
+                    keyguardRepository.isKeyguardGoingAway.isFalse(),
+                    "keyguardNotGoingAway"
+                ),
+                logAndObserve(
+                    keyguardRepository.wakefulness
+                        .map { WakefulnessModel.isSleepingOrStartingToSleep(it) }
+                        .isFalse(),
+                    "deviceNotSleepingOrNotStartingToSleep"
+                ),
+                logAndObserve(
+                    combine(
+                        keyguardInteractor.isSecureCameraActive,
+                        alternateBouncerInteractor.isVisible,
+                    ) { a, b ->
+                        !a || b
+                    },
+                    "secureCameraNotActiveOrAltBouncerIsShowing"
+                ),
+                logAndObserve(
+                    biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture,
+                    "isFaceAuthSupportedInCurrentPosture"
+                ),
+                logAndObserve(
+                    biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
+                    "userHasNotLockedDownDevice"
+                )
+            )
+            .reduce(::and)
+    }
+
+    private fun observeFaceAuthGatingChecks() {
+        // Face auth can run only if all of the gating conditions are true.
+        listOf(
+                canFaceAuthOrDetectRun(),
+                logAndObserve(isLockedOut.isFalse(), "isNotLocked"),
+                logAndObserve(
+                    deviceEntryFingerprintAuthRepository.isLockedOut.isFalse(),
+                    "fpLockedOut"
+                ),
+                logAndObserve(trustRepository.isCurrentUserTrusted.isFalse(), "currentUserTrusted"),
+                logAndObserve(
+                    biometricSettingsRepository.isNonStrongBiometricAllowed,
+                    "nonStrongBiometricIsAllowed"
+                ),
+                logAndObserve(
+                    userRepository.selectedUserInfo.map { it.isPrimary },
+                    "userIsPrimaryUser"
+                ),
+            )
+            .reduce(::and)
+            .distinctUntilChanged()
+            .onEach {
+                faceAuthLogger.canFaceAuthRunChanged(it)
+                _canRunFaceAuth.value = it
+                if (!it) {
+                    // Cancel currently running auth if any of the gating checks are false.
+                    faceAuthLogger.cancellingFaceAuth()
+                    cancel()
+                }
+            }
+            .launchIn(applicationScope)
+    }
+
+    private val faceAuthCallback =
+        object : FaceManager.AuthenticationCallback() {
+            override fun onAuthenticationFailed() {
+                _authenticationStatus.value = FailedAuthenticationStatus
+                _isAuthenticated.value = false
+                faceAuthLogger.authenticationFailed()
+                onFaceAuthRequestCompleted()
+            }
+
+            override fun onAuthenticationAcquired(acquireInfo: Int) {
+                _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo)
+                faceAuthLogger.authenticationAcquired(acquireInfo)
+            }
+
+            override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
+                val errorStatus = ErrorAuthenticationStatus(errorCode, errString.toString())
+                if (errorStatus.isLockoutError()) {
+                    _isLockedOut.value = true
+                }
+                _authenticationStatus.value = errorStatus
+                _isAuthenticated.value = false
+                if (errorStatus.isCancellationError()) {
+                    cancelNotReceivedHandlerJob?.cancel()
+                    applicationScope.launch {
+                        faceAuthLogger.launchingQueuedFaceAuthRequest(
+                            faceAuthRequestedWhileCancellation
+                        )
+                        faceAuthRequestedWhileCancellation?.let { authenticate(it) }
+                        faceAuthRequestedWhileCancellation = null
+                    }
+                }
+                faceAuthLogger.authenticationError(
+                    errorCode,
+                    errString,
+                    errorStatus.isLockoutError(),
+                    errorStatus.isCancellationError()
+                )
+                onFaceAuthRequestCompleted()
+            }
+
+            override fun onAuthenticationHelp(code: Int, helpStr: CharSequence?) {
+                if (faceAcquiredInfoIgnoreList.contains(code)) {
+                    return
+                }
+                _authenticationStatus.value = HelpAuthenticationStatus(code, helpStr.toString())
+            }
+
+            override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
+                _authenticationStatus.value = SuccessAuthenticationStatus(result)
+                _isAuthenticated.value = true
+                faceAuthLogger.faceAuthSuccess(result)
+                onFaceAuthRequestCompleted()
+            }
+        }
+
+    private fun onFaceAuthRequestCompleted() {
+        cancellationInProgress = false
+        _isAuthRunning.value = false
+        authCancellationSignal = null
+    }
+
+    private val detectionCallback =
+        FaceManager.FaceDetectionCallback { sensorId, userId, isStrong ->
+            faceAuthLogger.faceDetected()
+            _detectionStatus.value = DetectionStatus(sensorId, userId, isStrong)
+        }
+
+    private var cancellationInProgress = false
+    private var faceAuthRequestedWhileCancellation: FaceAuthUiEvent? = null
+
+    override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+        if (_isAuthRunning.value) {
+            faceAuthLogger.ignoredFaceAuthTrigger(uiEvent)
+            return
+        }
+
+        if (cancellationInProgress) {
+            faceAuthLogger.queuingRequestWhileCancelling(
+                faceAuthRequestedWhileCancellation,
+                uiEvent
+            )
+            faceAuthRequestedWhileCancellation = uiEvent
+            return
+        } else {
+            faceAuthRequestedWhileCancellation = null
+        }
+
+        if (canRunFaceAuth.value) {
+            withContext(mainDispatcher) {
+                // We always want to invoke face auth in the main thread.
+                authCancellationSignal = CancellationSignal()
+                _isAuthRunning.value = true
+                uiEventsLogger.logWithInstanceIdAndPosition(
+                    uiEvent,
+                    0,
+                    null,
+                    keyguardSessionId,
+                    uiEvent.extraInfo
+                )
+                faceAuthLogger.authenticating(uiEvent)
+                faceManager?.authenticate(
+                    null,
+                    authCancellationSignal,
+                    faceAuthCallback,
+                    null,
+                    FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
+                )
+            }
+        } else if (fallbackToDetection && canRunDetection.value) {
+            detect()
+        }
+    }
+
+    suspend fun detect() {
+        if (!isDetectionSupported) {
+            faceAuthLogger.detectionNotSupported(faceManager, faceManager?.sensorPropertiesInternal)
+            return
+        }
+        if (_isAuthRunning.value || detectCancellationSignal != null) {
+            faceAuthLogger.skippingDetection(_isAuthRunning.value, detectCancellationSignal != null)
+            return
+        }
+
+        detectCancellationSignal = CancellationSignal()
+        withContext(mainDispatcher) {
+            // We always want to invoke face detect in the main thread.
+            faceAuthLogger.faceDetectionStarted()
+            faceManager?.detectFace(
+                detectCancellationSignal,
+                detectionCallback,
+                FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
+            )
+        }
+    }
+
+    private val currentUserId: Int
+        get() = userRepository.getSelectedUserInfo().id
+
+    fun cancelDetection() {
+        detectCancellationSignal?.cancel()
+        detectCancellationSignal = null
+    }
+
+    override fun cancel() {
+        if (authCancellationSignal == null) return
+
+        authCancellationSignal?.cancel()
+        cancelNotReceivedHandlerJob =
+            applicationScope.launch {
+                delay(DEFAULT_CANCEL_SIGNAL_TIMEOUT)
+                faceAuthLogger.cancelSignalNotReceived(
+                    _isAuthRunning.value,
+                    _isLockedOut.value,
+                    cancellationInProgress,
+                    faceAuthRequestedWhileCancellation
+                )
+                onFaceAuthRequestCompleted()
+            }
+        cancellationInProgress = true
+        _isAuthRunning.value = false
+    }
+
+    private fun logAndObserve(cond: Flow<Boolean>, loggingContext: String): Flow<Boolean> {
+        return cond.distinctUntilChanged().onEach {
+            faceAuthLogger.observedConditionChanged(it, loggingContext)
+        }
+    }
+
+    companion object {
+        const val TAG = "DeviceEntryFaceAuthRepository"
+
+        /**
+         * If no cancel signal has been received after this amount of time, assume that it is
+         * cancelled.
+         */
+        const val DEFAULT_CANCEL_SIGNAL_TIMEOUT = 3000L
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("DeviceEntryFaceAuthRepositoryImpl state:")
+        pw.println("  cancellationInProgress: $cancellationInProgress")
+        pw.println("  _isLockedOut.value: ${_isLockedOut.value}")
+        pw.println("  _isAuthRunning.value: ${_isAuthRunning.value}")
+        pw.println("  isDetectionSupported: $isDetectionSupported")
+        pw.println("  FaceManager state:")
+        pw.println("    faceManager: $faceManager")
+        pw.println("    sensorPropertiesInternal: ${faceManager?.sensorPropertiesInternal}")
+        pw.println(
+            "    supportsFaceDetection: " +
+                "${faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection}"
+        )
+        pw.println(
+            "  faceAuthRequestedWhileCancellation: ${faceAuthRequestedWhileCancellation?.reason}"
+        )
+        pw.println("  authCancellationSignal: $authCancellationSignal")
+        pw.println("  detectCancellationSignal: $detectCancellationSignal")
+        pw.println("  faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList")
+        pw.println("  _authenticationStatus: ${_authenticationStatus.value}")
+        pw.println("  _detectionStatus: ${_detectionStatus.value}")
+        pw.println("  currentUserId: $currentUserId")
+        pw.println("  keyguardSessionId: $keyguardSessionId")
+        pw.println("  lockscreenBypassEnabled: ${keyguardBypassController?.bypassEnabled ?: false}")
+    }
+}
+/** Combine two boolean flows by and-ing both of them */
+private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
+    flow.combine(anotherFlow) { a, b -> a && b }
+
+/** "Not" the given flow. The return [Flow] will be true when [this] flow is false. */
+private fun Flow<Boolean>.isFalse(): Flow<Boolean> {
+    return this.map { !it }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 4fa56ee..52234b3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import android.hardware.biometrics.BiometricAuthenticator
+import android.hardware.biometrics.BiometricAuthenticator.Modality
 import android.hardware.biometrics.BiometricSourceType
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
@@ -33,6 +35,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.stateIn
 
 /** Encapsulates state about device entry fingerprint auth mechanism. */
@@ -49,7 +52,7 @@
     /**
      * Fingerprint sensor type present on the device, null if fingerprint sensor is not available.
      */
-    val availableFpSensorType: BiometricType?
+    val availableFpSensorType: Flow<BiometricType?>
 }
 
 /**
@@ -77,11 +80,39 @@
         pw.println("isLockedOut=${isLockedOut.value}")
     }
 
-    override val availableFpSensorType: BiometricType?
-        get() =
-            if (authController.isUdfpsSupported) BiometricType.UNDER_DISPLAY_FINGERPRINT
-            else if (authController.isSfpsSupported) BiometricType.SIDE_FINGERPRINT
-            else if (authController.isRearFpsSupported) BiometricType.REAR_FINGERPRINT else null
+    override val availableFpSensorType: Flow<BiometricType?>
+        get() {
+            return if (authController.areAllFingerprintAuthenticatorsRegistered()) {
+                flowOf(getFpSensorType())
+            } else {
+                conflatedCallbackFlow {
+                    val callback =
+                        object : AuthController.Callback {
+                            override fun onAllAuthenticatorsRegistered(@Modality modality: Int) {
+                                if (modality == BiometricAuthenticator.TYPE_FINGERPRINT)
+                                    trySendWithFailureLogging(
+                                        getFpSensorType(),
+                                        TAG,
+                                        "onAllAuthenticatorsRegistered, emitting fpSensorType"
+                                    )
+                            }
+                        }
+                    authController.addCallback(callback)
+                    trySendWithFailureLogging(
+                        getFpSensorType(),
+                        TAG,
+                        "initial value for fpSensorType"
+                    )
+                    awaitClose { authController.removeCallback(callback) }
+                }
+            }
+        }
+
+    private fun getFpSensorType(): BiometricType? {
+        return if (authController.isUdfpsSupported) BiometricType.UNDER_DISPLAY_FINGERPRINT
+        else if (authController.isSfpsSupported) BiometricType.SIDE_FINGERPRINT
+        else if (authController.isRearFpsSupported) BiometricType.REAR_FINGERPRINT else null
+    }
 
     override val isLockedOut: StateFlow<Boolean> =
         conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 64e2a2c..0b506cf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -65,8 +65,6 @@
     val keyguardAuthenticated: StateFlow<Boolean?>
     val showMessage: StateFlow<BouncerShowMessageModel?>
     val resourceUpdateRequests: StateFlow<Boolean>
-    val bouncerPromptReason: Int
-    val bouncerErrorMessage: CharSequence?
     val alternateBouncerVisible: StateFlow<Boolean>
     val alternateBouncerUIAvailable: StateFlow<Boolean>
     val sideFpsShowing: StateFlow<Boolean>
@@ -145,11 +143,6 @@
     override val showMessage = _showMessage.asStateFlow()
     private val _resourceUpdateRequests = MutableStateFlow(false)
     override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
-    override val bouncerPromptReason: Int
-        get() = viewMediatorCallback.bouncerPromptReason
-    override val bouncerErrorMessage: CharSequence?
-        get() = viewMediatorCallback.consumeCustomMessage()
-
     /** Values associated with the AlternateBouncer */
     private val _alternateBouncerVisible = MutableStateFlow(false)
     override val alternateBouncerVisible = _alternateBouncerVisible.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManager.kt
deleted file mode 100644
index a326840..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManager.kt
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.data.repository
-
-import android.app.StatusBarManager
-import android.content.Context
-import android.hardware.face.FaceAuthenticateOptions
-import android.hardware.face.FaceManager
-import android.os.CancellationSignal
-import com.android.internal.logging.InstanceId
-import com.android.internal.logging.UiEventLogger
-import com.android.keyguard.FaceAuthUiEvent
-import com.android.systemui.Dumpable
-import com.android.systemui.R
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
-import com.android.systemui.log.FaceAuthenticationLogger
-import com.android.systemui.log.SessionTracker
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.user.data.repository.UserRepository
-import java.io.PrintWriter
-import java.util.Arrays
-import java.util.stream.Collectors
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-/**
- * API to run face authentication and detection for device entry / on keyguard (as opposed to the
- * biometric prompt).
- */
-interface KeyguardFaceAuthManager {
-    /**
-     * Trigger face authentication.
-     *
-     * [uiEvent] provided should be logged whenever face authentication runs. Invocation should be
-     * ignored if face authentication is already running. Results should be propagated through
-     * [authenticationStatus]
-     */
-    suspend fun authenticate(uiEvent: FaceAuthUiEvent)
-
-    /**
-     * Trigger face detection.
-     *
-     * Invocation should be ignored if face authentication is currently running.
-     */
-    suspend fun detect()
-
-    /** Stop currently running face authentication or detection. */
-    fun cancel()
-
-    /** Provide the current status of face authentication. */
-    val authenticationStatus: Flow<AuthenticationStatus>
-
-    /** Provide the current status of face detection. */
-    val detectionStatus: Flow<DetectionStatus>
-
-    /** Current state of whether face authentication is locked out or not. */
-    val isLockedOut: Flow<Boolean>
-
-    /** Current state of whether face authentication is running. */
-    val isAuthRunning: Flow<Boolean>
-
-    /** Is face detection supported. */
-    val isDetectionSupported: Boolean
-}
-
-@SysUISingleton
-class KeyguardFaceAuthManagerImpl
-@Inject
-constructor(
-    context: Context,
-    private val faceManager: FaceManager? = null,
-    private val userRepository: UserRepository,
-    private val keyguardBypassController: KeyguardBypassController? = null,
-    @Application private val applicationScope: CoroutineScope,
-    @Main private val mainDispatcher: CoroutineDispatcher,
-    private val sessionTracker: SessionTracker,
-    private val uiEventsLogger: UiEventLogger,
-    private val faceAuthLogger: FaceAuthenticationLogger,
-    dumpManager: DumpManager,
-) : KeyguardFaceAuthManager, Dumpable {
-    private var cancellationSignal: CancellationSignal? = null
-    private val lockscreenBypassEnabled: Boolean
-        get() = keyguardBypassController?.bypassEnabled ?: false
-    private var faceAcquiredInfoIgnoreList: Set<Int>
-
-    private val faceLockoutResetCallback =
-        object : FaceManager.LockoutResetCallback() {
-            override fun onLockoutReset(sensorId: Int) {
-                _isLockedOut.value = false
-            }
-        }
-
-    init {
-        faceManager?.addLockoutResetCallback(faceLockoutResetCallback)
-        faceAcquiredInfoIgnoreList =
-            Arrays.stream(
-                    context.resources.getIntArray(
-                        R.array.config_face_acquire_device_entry_ignorelist
-                    )
-                )
-                .boxed()
-                .collect(Collectors.toSet())
-        dumpManager.registerCriticalDumpable("KeyguardFaceAuthManagerImpl", this)
-    }
-
-    private val faceAuthCallback =
-        object : FaceManager.AuthenticationCallback() {
-            override fun onAuthenticationFailed() {
-                _authenticationStatus.value = FailedAuthenticationStatus
-                faceAuthLogger.authenticationFailed()
-                onFaceAuthRequestCompleted()
-            }
-
-            override fun onAuthenticationAcquired(acquireInfo: Int) {
-                _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo)
-                faceAuthLogger.authenticationAcquired(acquireInfo)
-            }
-
-            override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
-                val errorStatus = ErrorAuthenticationStatus(errorCode, errString.toString())
-                if (errorStatus.isLockoutError()) {
-                    _isLockedOut.value = true
-                }
-                _authenticationStatus.value = errorStatus
-                if (errorStatus.isCancellationError()) {
-                    cancelNotReceivedHandlerJob?.cancel()
-                    applicationScope.launch {
-                        faceAuthLogger.launchingQueuedFaceAuthRequest(
-                            faceAuthRequestedWhileCancellation
-                        )
-                        faceAuthRequestedWhileCancellation?.let { authenticate(it) }
-                        faceAuthRequestedWhileCancellation = null
-                    }
-                }
-                faceAuthLogger.authenticationError(
-                    errorCode,
-                    errString,
-                    errorStatus.isLockoutError(),
-                    errorStatus.isCancellationError()
-                )
-                onFaceAuthRequestCompleted()
-            }
-
-            override fun onAuthenticationHelp(code: Int, helpStr: CharSequence?) {
-                if (faceAcquiredInfoIgnoreList.contains(code)) {
-                    return
-                }
-                _authenticationStatus.value = HelpAuthenticationStatus(code, helpStr.toString())
-            }
-
-            override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
-                _authenticationStatus.value = SuccessAuthenticationStatus(result)
-                faceAuthLogger.faceAuthSuccess(result)
-                onFaceAuthRequestCompleted()
-            }
-        }
-
-    private fun onFaceAuthRequestCompleted() {
-        cancellationInProgress = false
-        _isAuthRunning.value = false
-        cancellationSignal = null
-    }
-
-    private val detectionCallback =
-        FaceManager.FaceDetectionCallback { sensorId, userId, isStrong ->
-            faceAuthLogger.faceDetected()
-            _detectionStatus.value = DetectionStatus(sensorId, userId, isStrong)
-        }
-
-    private var cancellationInProgress = false
-    private var faceAuthRequestedWhileCancellation: FaceAuthUiEvent? = null
-
-    override suspend fun authenticate(uiEvent: FaceAuthUiEvent) {
-        if (_isAuthRunning.value) {
-            faceAuthLogger.ignoredFaceAuthTrigger(uiEvent)
-            return
-        }
-
-        if (cancellationInProgress) {
-            faceAuthLogger.queuingRequestWhileCancelling(
-                faceAuthRequestedWhileCancellation,
-                uiEvent
-            )
-            faceAuthRequestedWhileCancellation = uiEvent
-            return
-        } else {
-            faceAuthRequestedWhileCancellation = null
-        }
-
-        withContext(mainDispatcher) {
-            // We always want to invoke face auth in the main thread.
-            cancellationSignal = CancellationSignal()
-            _isAuthRunning.value = true
-            uiEventsLogger.logWithInstanceIdAndPosition(
-                uiEvent,
-                0,
-                null,
-                keyguardSessionId,
-                uiEvent.extraInfo
-            )
-            faceAuthLogger.authenticating(uiEvent)
-            faceManager?.authenticate(
-                null,
-                cancellationSignal,
-                faceAuthCallback,
-                null,
-                FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
-            )
-        }
-    }
-
-    override suspend fun detect() {
-        if (!isDetectionSupported) {
-            faceAuthLogger.detectionNotSupported(faceManager, faceManager?.sensorPropertiesInternal)
-            return
-        }
-        if (_isAuthRunning.value) {
-            faceAuthLogger.skippingBecauseAlreadyRunning("detection")
-            return
-        }
-
-        cancellationSignal = CancellationSignal()
-        withContext(mainDispatcher) {
-            // We always want to invoke face detect in the main thread.
-            faceAuthLogger.faceDetectionStarted()
-            faceManager?.detectFace(
-                cancellationSignal,
-                detectionCallback,
-                FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
-            )
-        }
-    }
-
-    private val currentUserId: Int
-        get() = userRepository.getSelectedUserInfo().id
-
-    override fun cancel() {
-        if (cancellationSignal == null) return
-
-        cancellationSignal?.cancel()
-        cancelNotReceivedHandlerJob =
-            applicationScope.launch {
-                delay(DEFAULT_CANCEL_SIGNAL_TIMEOUT)
-                faceAuthLogger.cancelSignalNotReceived(
-                    _isAuthRunning.value,
-                    _isLockedOut.value,
-                    cancellationInProgress,
-                    faceAuthRequestedWhileCancellation
-                )
-                onFaceAuthRequestCompleted()
-            }
-        cancellationInProgress = true
-        _isAuthRunning.value = false
-    }
-
-    private var cancelNotReceivedHandlerJob: Job? = null
-
-    private val _authenticationStatus: MutableStateFlow<AuthenticationStatus?> =
-        MutableStateFlow(null)
-    override val authenticationStatus: Flow<AuthenticationStatus>
-        get() = _authenticationStatus.filterNotNull()
-
-    private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
-    override val detectionStatus: Flow<DetectionStatus>
-        get() = _detectionStatus.filterNotNull()
-
-    private val _isLockedOut = MutableStateFlow(false)
-    override val isLockedOut: Flow<Boolean> = _isLockedOut
-
-    override val isDetectionSupported =
-        faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection ?: false
-
-    private val _isAuthRunning = MutableStateFlow(false)
-    override val isAuthRunning: Flow<Boolean>
-        get() = _isAuthRunning
-
-    private val keyguardSessionId: InstanceId?
-        get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
-
-    companion object {
-        const val TAG = "KeyguardFaceAuthManager"
-
-        /**
-         * If no cancel signal has been received after this amount of time, assume that it is
-         * cancelled.
-         */
-        const val DEFAULT_CANCEL_SIGNAL_TIMEOUT = 3000L
-    }
-
-    override fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.println("KeyguardFaceAuthManagerImpl state:")
-        pw.println("  cancellationInProgress: $cancellationInProgress")
-        pw.println("  _isLockedOut.value: ${_isLockedOut.value}")
-        pw.println("  _isAuthRunning.value: ${_isAuthRunning.value}")
-        pw.println("  isDetectionSupported: $isDetectionSupported")
-        pw.println("  FaceManager state:")
-        pw.println("    faceManager: $faceManager")
-        pw.println("    sensorPropertiesInternal: ${faceManager?.sensorPropertiesInternal}")
-        pw.println(
-            "    supportsFaceDetection: " +
-                "${faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection}"
-        )
-        pw.println(
-            "  faceAuthRequestedWhileCancellation: ${faceAuthRequestedWhileCancellation?.reason}"
-        )
-        pw.println("  cancellationSignal: $cancellationSignal")
-        pw.println("  faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList")
-        pw.println("  _authenticationStatus: ${_authenticationStatus.value}")
-        pw.println("  _detectionStatus: ${_detectionStatus.value}")
-        pw.println("  currentUserId: $currentUserId")
-        pw.println("  keyguardSessionId: $keyguardSessionId")
-        pw.println("  lockscreenBypassEnabled: $lockscreenBypassEnabled")
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
new file mode 100644
index 0000000..3c66f24
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface KeyguardFaceAuthModule {
+    @Binds
+    fun deviceEntryFaceAuthRepository(
+        impl: DeviceEntryFaceAuthRepositoryImpl
+    ): DeviceEntryFaceAuthRepository
+
+    @Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 33f4e2e..9212aa1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -27,7 +27,6 @@
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.settingslib.Utils
 import com.android.systemui.DejankUtils
 import com.android.systemui.R
 import com.android.systemui.classifier.FalsingCollector
@@ -42,12 +41,12 @@
 import com.android.systemui.shared.system.SysUiStatsLog
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
+import javax.inject.Inject
 
 /**
  * Encapsulates business logic for interacting with the lock-screen primary (pin/pattern/password)
@@ -82,12 +81,6 @@
     /** Runnable to show the primary bouncer. */
     val showRunnable = Runnable {
         repository.setPrimaryShow(true)
-        primaryBouncerView.delegate?.showPromptReason(repository.bouncerPromptReason)
-        (repository.bouncerErrorMessage as? String)?.let {
-            repository.setShowMessage(
-                BouncerShowMessageModel(message = it, Utils.getColorError(context))
-            )
-        }
         repository.setPrimaryShowingSoon(false)
         primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
index b1c5f8f..eded9c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
@@ -18,7 +18,10 @@
 
 import android.hardware.face.FaceManager
 
-/** Authentication status provided by [com.android.keyguard.faceauth.KeyguardFaceAuthManager] */
+/**
+ * Authentication status provided by
+ * [com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository]
+ */
 sealed class AuthenticationStatus
 
 /** Success authentication status. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 468a6b5..172f922 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -110,24 +110,24 @@
                     viewModel.setBouncerViewDelegate(delegate)
                     launch {
                         viewModel.isShowing.collect { isShowing ->
+                            view.visibility = if (isShowing) View.VISIBLE else View.INVISIBLE
                             if (isShowing) {
                                 // Reset Security Container entirely.
                                 securityContainerController.reinflateViewFlipper {
                                     // Reset Security Container entirely.
-                                    view.visibility = View.VISIBLE
                                     securityContainerController.onBouncerVisibilityChanged(
                                         /* isVisible= */ true
                                     )
                                     securityContainerController.showPrimarySecurityScreen(
                                         /* turningOff= */ false
                                     )
+                                    securityContainerController.setInitialMessage()
                                     securityContainerController.appear()
                                     securityContainerController.onResume(
                                         KeyguardSecurityView.SCREEN_ON
                                     )
                                 }
                             } else {
-                                view.visibility = View.INVISIBLE
                                 securityContainerController.onBouncerVisibilityChanged(
                                     /* isVisible= */ false
                                 )
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index 647e3a1..f7355d5 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -7,17 +7,17 @@
 import com.android.systemui.log.dagger.FaceAuthLog
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
-private const val TAG = "KeyguardFaceAuthManagerLog"
+private const val TAG = "DeviceEntryFaceAuthRepositoryLog"
 
 /**
- * Helper class for logging for [com.android.keyguard.faceauth.KeyguardFaceAuthManager]
+ * Helper class for logging for
+ * [com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository]
  *
  * To enable logcat echoing for an entire buffer:
  * ```
- *   adb shell settings put global systemui/buffer/KeyguardFaceAuthManagerLog <logLevel>
+ *   adb shell settings put global systemui/buffer/DeviceEntryFaceAuthRepositoryLog <logLevel>
  *
  * ```
  */
@@ -82,8 +82,19 @@
         )
     }
 
-    fun skippingBecauseAlreadyRunning(@CompileTimeConstant operation: String) {
-        logBuffer.log(TAG, DEBUG, "isAuthRunning is true, skipping $operation")
+    fun skippingDetection(isAuthRunning: Boolean, detectCancellationNotNull: Boolean) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                bool1 = isAuthRunning
+                bool2 = detectCancellationNotNull
+            },
+            {
+                "Skipping running detection: isAuthRunning: $bool1, " +
+                    "detectCancellationNotNull: $bool2"
+            }
+        )
     }
 
     fun faceDetectionStarted() {
@@ -177,4 +188,33 @@
             { "Face authenticated successfully: userId: $int1, isStrongBiometric: $bool1" }
         )
     }
+
+    fun observedConditionChanged(newValue: Boolean, context: String) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                bool1 = newValue
+                str1 = context
+            },
+            { "Observed condition changed: $str1, new value: $bool1" }
+        )
+    }
+
+    fun canFaceAuthRunChanged(canRun: Boolean) {
+        logBuffer.log(TAG, DEBUG, { bool1 = canRun }, { "canFaceAuthRun value changed to $bool1" })
+    }
+
+    fun canRunDetectionChanged(canRunDetection: Boolean) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { bool1 = canRunDetection },
+            { "canRunDetection value changed to $bool1" }
+        )
+    }
+
+    fun cancellingFaceAuth() {
+        logBuffer.log(TAG, DEBUG, "cancelling face auth because a gating condition became false")
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
index b98a92f..d848b43 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
@@ -28,6 +28,8 @@
 
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.InstanceIdSequence;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -60,6 +62,7 @@
     private final AuthController mAuthController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final KeyguardStateController mKeyguardStateController;
+    private final UiEventLogger mUiEventLogger;
     private final Map<Integer, InstanceId> mSessionToInstanceId = new HashMap<>();
 
     private boolean mKeyguardSessionStarted;
@@ -69,12 +72,14 @@
             IStatusBarService statusBarService,
             AuthController authController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            KeyguardStateController keyguardStateController
+            KeyguardStateController keyguardStateController,
+            UiEventLogger uiEventLogger
     ) {
         mStatusBarManagerService = statusBarService;
         mAuthController = authController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mKeyguardStateController = keyguardStateController;
+        mUiEventLogger = uiEventLogger;
     }
 
     @Override
@@ -116,6 +121,10 @@
     }
 
     private void endSession(int type) {
+        endSession(type, null);
+    }
+
+    private void endSession(int type, @Nullable SessionUiEvent endSessionUiEvent) {
         if (mSessionToInstanceId.getOrDefault(type, null) == null) {
             Log.e(TAG, "session [" + getString(type) + "] was not started");
             return;
@@ -127,6 +136,9 @@
             if (DEBUG) {
                 Log.d(TAG, "Session end for [" + getString(type) + "] id=" + instanceId);
             }
+            if (endSessionUiEvent != null) {
+                mUiEventLogger.log(endSessionUiEvent, instanceId);
+            }
             mStatusBarManagerService.onSessionEnded(type, instanceId);
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to send onSessionEnded for session="
@@ -139,7 +151,7 @@
         @Override
         public void onStartedGoingToSleep(int why) {
             if (mKeyguardSessionStarted) {
-                endSession(SESSION_KEYGUARD);
+                endSession(SESSION_KEYGUARD, SessionUiEvent.KEYGUARD_SESSION_END_GOING_TO_SLEEP);
             }
 
             // Start a new session whenever the device goes to sleep
@@ -162,7 +174,8 @@
                 startSession(SESSION_KEYGUARD);
             } else if (!keyguardShowing && wasSessionStarted) {
                 mKeyguardSessionStarted = false;
-                endSession(SESSION_KEYGUARD);
+                endSession(SESSION_KEYGUARD,
+                        SessionUiEvent.KEYGUARD_SESSION_END_KEYGUARD_GOING_AWAY);
             }
         }
     };
@@ -200,4 +213,22 @@
 
         return "unknownType=" + sessionType;
     }
+
+    enum SessionUiEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "A keyguard session ended due to the keyguard going away.")
+        KEYGUARD_SESSION_END_KEYGUARD_GOING_AWAY(1354),
+
+        @UiEvent(doc = "A keyguard session ended due to display going to sleep.")
+        KEYGUARD_SESSION_END_GOING_TO_SLEEP(1355);
+
+        private final int mId;
+        SessionUiEvent(int id) {
+            mId = id;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index e204def..3775e2c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -367,13 +367,13 @@
 
     /**
      * Provides a {@link LogBuffer} for use by
-     *  {@link com.android.keyguard.faceauth.KeyguardFaceAuthManagerImpl}.
+     *  {@link com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepositoryImpl}.
      */
     @Provides
     @SysUISingleton
     @FaceAuthLog
     public static LogBuffer provideFaceAuthLog(LogBufferFactory factory) {
-        return factory.create("KeyguardFaceAuthManagerLog", 300);
+        return factory.create("DeviceEntryFaceAuthRepositoryLog", 300);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index f92a5ab..731bb2f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -66,6 +66,8 @@
 
     protected final MediaOutputController mController;
 
+    private static final int UNMUTE_DEFAULT_VOLUME = 2;
+
     Context mContext;
     View mHolderView;
     boolean mIsDragging;
@@ -193,10 +195,6 @@
             mTwoLineTitleText.setTextColor(mController.getColorItemContent());
             if (mController.isAdvancedLayoutSupported()) {
                 mVolumeValueText.setTextColor(mController.getColorItemContent());
-                mTitleIcon.setOnTouchListener(((v, event) -> {
-                    mSeekBar.dispatchTouchEvent(event);
-                    return false;
-                }));
             }
             mSeekBar.setProgressTintList(
                     ColorStateList.valueOf(mController.getColorSeekbarProgress()));
@@ -546,13 +544,21 @@
         private void enableSeekBar(MediaDevice device) {
             mSeekBar.setEnabled(true);
             mSeekBar.setOnTouchListener((v, event) -> false);
-            if (mController.isAdvancedLayoutSupported()) {
-                updateIconAreaClickListener((v) -> {
+            updateIconAreaClickListener((v) -> {
+                if (device.getCurrentVolume() == 0) {
+                    mController.adjustVolume(device, UNMUTE_DEFAULT_VOLUME);
+                    updateUnmutedVolumeIcon();
+                    mTitleIcon.setOnTouchListener(((iconV, event) -> false));
+                } else {
                     mSeekBar.resetVolume();
                     mController.adjustVolume(device, 0);
                     updateMutedVolumeIcon();
-                });
-            }
+                    mTitleIcon.setOnTouchListener(((iconV, event) -> {
+                        mSeekBar.dispatchTouchEvent(event);
+                        return false;
+                    }));
+                }
+            });
         }
 
         protected void setUpDeviceIcon(MediaDevice device) {
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
index e352c61..1894bc4 100644
--- a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
@@ -20,12 +20,16 @@
 import android.view.MotionEvent
 import android.view.ViewConfiguration
 import com.android.systemui.classifier.Classifier
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.multishade.shared.math.isZero
 import com.android.systemui.multishade.shared.model.ProxiedInputModel
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.shade.ShadeController
 import javax.inject.Inject
 import kotlin.math.abs
 import kotlinx.coroutines.CoroutineScope
@@ -33,6 +37,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /**
  * Encapsulates business logic to handle [MotionEvent]-based user input.
@@ -40,15 +45,31 @@
  * This class is meant purely for the legacy `View`-based system to be able to pass `MotionEvent`s
  * into the newer multi-shade framework for processing.
  */
+@SysUISingleton
 class MultiShadeMotionEventInteractor
 @Inject
 constructor(
     @Application private val applicationContext: Context,
     @Application private val applicationScope: CoroutineScope,
     private val multiShadeInteractor: MultiShadeInteractor,
+    featureFlags: FeatureFlags,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val falsingManager: FalsingManager,
+    private val shadeController: ShadeController,
 ) {
+    init {
+        if (featureFlags.isEnabled(Flags.DUAL_SHADE)) {
+            applicationScope.launch {
+                multiShadeInteractor.isAnyShadeExpanded.collect {
+                    if (!it && !shadeController.isKeyguard) {
+                        shadeController.makeExpandedInvisible()
+                    } else {
+                        shadeController.makeExpandedVisible(false)
+                    }
+                }
+            }
+        }
+    }
 
     private val isAnyShadeExpanded: StateFlow<Boolean> =
         multiShadeInteractor.isAnyShadeExpanded.stateIn(
@@ -56,6 +77,7 @@
             started = SharingStarted.Eagerly,
             initialValue = false,
         )
+
     private val isBouncerShowing: StateFlow<Boolean> =
         keyguardTransitionInteractor
             .transitionValue(state = KeyguardState.PRIMARY_BOUNCER)
@@ -102,23 +124,7 @@
                 false
             }
             MotionEvent.ACTION_MOVE -> {
-                interactionState?.let {
-                    val pointerIndex = event.findPointerIndex(it.pointerId)
-                    val currentX = event.getX(pointerIndex)
-                    val currentY = event.getY(pointerIndex)
-                    if (!it.isDraggingHorizontally && !it.isDraggingShade) {
-                        val xDistanceTravelled = currentX - it.initialX
-                        val yDistanceTravelled = currentY - it.initialY
-                        val touchSlop = ViewConfiguration.get(applicationContext).scaledTouchSlop
-                        interactionState =
-                            when {
-                                yDistanceTravelled > touchSlop -> it.copy(isDraggingShade = true)
-                                abs(xDistanceTravelled) > touchSlop ->
-                                    it.copy(isDraggingHorizontally = true)
-                                else -> interactionState
-                            }
-                    }
-                }
+                onMove(event)
 
                 // We want to intercept the rest of the gesture if we're dragging the shade.
                 isDraggingShade()
@@ -162,7 +168,8 @@
                         }
                         true
                     } else {
-                        false
+                        onMove(event)
+                        isDraggingShade()
                     }
                 }
                     ?: false
@@ -225,6 +232,32 @@
         }
     }
 
+    /**
+     * Handles [MotionEvent.ACTION_MOVE] and sets whether or not we are dragging shade in our
+     * current interaction
+     *
+     * @param event The [MotionEvent] to handle.
+     */
+    private fun onMove(event: MotionEvent) {
+        interactionState?.let {
+            val pointerIndex = event.findPointerIndex(it.pointerId)
+            val currentX = event.getX(pointerIndex)
+            val currentY = event.getY(pointerIndex)
+            if (!it.isDraggingHorizontally && !it.isDraggingShade) {
+                val xDistanceTravelled = currentX - it.initialX
+                val yDistanceTravelled = currentY - it.initialY
+                val touchSlop = ViewConfiguration.get(applicationContext).scaledTouchSlop
+                interactionState =
+                    when {
+                        yDistanceTravelled > touchSlop -> it.copy(isDraggingShade = true)
+                        abs(xDistanceTravelled) > touchSlop ->
+                            it.copy(isDraggingHorizontally = true)
+                        else -> interactionState
+                    }
+            }
+        }
+    }
+
     private data class InteractionState(
         val initialX: Float,
         val initialY: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index f5c0a94..334c70b 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -296,7 +296,8 @@
 
         // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
         // was used to start the note task.
-        putExtra(Intent.EXTRA_USE_STYLUS_MODE, true)
+        val useStylusMode = info.entryPoint != NoteTaskEntryPoint.KEYBOARD_SHORTCUT
+        putExtra(Intent.EXTRA_USE_STYLUS_MODE, useStylusMode)
 
         addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
         // We should ensure the note experience can be opened both as a full screen (lockscreen)
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
index 2fa8f9a..fae325c 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
@@ -25,7 +25,8 @@
  * An entry point represents where the note task has ben called from. In rare cases, it may
  * represent a "re-entry" (i.e., [APP_CLIPS]).
  */
-enum class NoteTaskEntryPoint {
+enum class
+NoteTaskEntryPoint {
 
     /** @see [LaunchNoteTaskActivity] */
     WIDGET_PICKER_SHORTCUT,
@@ -38,4 +39,7 @@
 
     /** @see [AppClipsTrampolineActivity] */
     APP_CLIPS,
+
+    /** @see [NoteTaskInitializer.callbacks] */
+    KEYBOARD_SHORTCUT,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
index 16dd16e..48a5933 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
@@ -18,6 +18,7 @@
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.notetask.NoteTaskEntryPoint.APP_CLIPS
+import com.android.systemui.notetask.NoteTaskEntryPoint.KEYBOARD_SHORTCUT
 import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
 import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
 import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
@@ -51,6 +52,7 @@
                 WIDGET_PICKER_SHORTCUT -> NOTE_OPENED_VIA_SHORTCUT
                 QUICK_AFFORDANCE -> NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
                 APP_CLIPS -> return
+                KEYBOARD_SHORTCUT -> return
                 null -> return
             }
         uiEventLogger.log(event, info.uid, info.packageName)
@@ -70,6 +72,7 @@
                 WIDGET_PICKER_SHORTCUT -> return
                 QUICK_AFFORDANCE -> return
                 APP_CLIPS -> return
+                KEYBOARD_SHORTCUT -> return
                 null -> return
             }
         uiEventLogger.log(event, info.uid, info.packageName)
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 04ed08b..23ee13b 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -41,9 +41,12 @@
     @VisibleForTesting
     val callbacks =
         object : CommandQueue.Callbacks {
-            override fun handleSystemKey(keyCode: Int) {
-                if (keyCode == KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL) {
+            override fun handleSystemKey(key: KeyEvent) {
+                if (key.keyCode == KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL) {
                     controller.showNoteTask(NoteTaskEntryPoint.TAIL_BUTTON)
+                } else if (key.keyCode == KeyEvent.KEYCODE_N && key.isMetaPressed &&
+                        key.isCtrlPressed) {
+                    controller.showNoteTask(NoteTaskEntryPoint.KEYBOARD_SHORTCUT)
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 5355865..0641eec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -47,7 +47,6 @@
 import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_FOOTER_DOT
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS
@@ -80,8 +79,6 @@
 
 /** A controller for the dealing with services running in the foreground. */
 interface FgsManagerController {
-    /** Whether the TaskManager (and therefore this controller) is actually available. */
-    val isAvailable: StateFlow<Boolean>
 
     /** The number of packages with a service running in the foreground. */
     val numRunningPackages: Int
@@ -155,7 +152,6 @@
 
     companion object {
         private const val INTERACTION_JANK_TAG = "active_background_apps"
-        private const val DEFAULT_TASK_MANAGER_ENABLED = true
         private const val DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT = false
         private const val DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS = true
         private const val DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS = true
@@ -165,9 +161,6 @@
     override var newChangesSinceDialogWasDismissed = false
         private set
 
-    val _isAvailable = MutableStateFlow(false)
-    override val isAvailable: StateFlow<Boolean> = _isAvailable.asStateFlow()
-
     val _showFooterDot = MutableStateFlow(false)
     override val showFooterDot: StateFlow<Boolean> = _showFooterDot.asStateFlow()
 
@@ -264,7 +257,6 @@
                 NAMESPACE_SYSTEMUI,
                 backgroundExecutor
             ) {
-                _isAvailable.value = it.getBoolean(TASK_MANAGER_ENABLED, _isAvailable.value)
                 _showFooterDot.value =
                     it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, _showFooterDot.value)
                 showStopBtnForUserAllowlistedApps = it.getBoolean(
@@ -280,11 +272,6 @@
                     TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
                     informJobSchedulerOfPendingAppStop)
             }
-
-            _isAvailable.value = deviceConfigProxy.getBoolean(
-                NAMESPACE_SYSTEMUI,
-                TASK_MANAGER_ENABLED, DEFAULT_TASK_MANAGER_ENABLED
-            )
             _showFooterDot.value = deviceConfigProxy.getBoolean(
                 NAMESPACE_SYSTEMUI,
                 TASK_MANAGER_SHOW_FOOTER_DOT, DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 6be74a0..ce690e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -26,6 +26,7 @@
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTileView;
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.util.leak.GarbageMonitor;
 
@@ -34,7 +35,7 @@
 import java.util.Collection;
 import java.util.List;
 
-public interface QSHost extends PanelInteractor {
+public interface QSHost extends PanelInteractor, CustomTileAddedRepository {
     String TILES_SETTING = Settings.Secure.QS_TILES;
     int POSITION_AT_END = -1;
 
@@ -102,9 +103,6 @@
     void removeTileByUser(ComponentName tile);
     void changeTilesByUser(List<String> previousTiles, List<String> newTiles);
 
-    boolean isTileAdded(ComponentName componentName, int userId);
-    void setTileAdded(ComponentName componentName, int userId, boolean added);
-
     int indexOf(String tileSpec);
 
     InstanceId getNewInstanceId();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
index 958fa71..964fe71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
@@ -20,6 +20,8 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractorImpl
 import dagger.Binds
@@ -46,5 +48,19 @@
                 qsHost
             }
         }
+
+        @Provides
+        @JvmStatic
+        fun provideCustomTileAddedRepository(
+            featureFlags: FeatureFlags,
+            qsHost: QSHost,
+            customTileAddedRepository: CustomTileAddedSharedPrefsRepository
+        ): CustomTileAddedRepository {
+            return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
+                customTileAddedRepository
+            } else {
+                qsHost
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index 9f93e49..7a10a27 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -33,6 +33,7 @@
 
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
 import com.android.systemui.settings.UserTracker;
 
 import java.util.List;
@@ -59,6 +60,7 @@
     private final TileLifecycleManager mStateManager;
     private final Handler mHandler;
     private final UserTracker mUserTracker;
+    private final CustomTileAddedRepository mCustomTileAddedRepository;
     private boolean mBindRequested;
     private boolean mBindAllowed;
     private boolean mBound;
@@ -72,9 +74,10 @@
     private boolean mStarted = false;
 
     TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
-            BroadcastDispatcher broadcastDispatcher, UserTracker userTracker) {
-        this(tileServices, handler, userTracker, new TileLifecycleManager(handler,
-                tileServices.getContext(), tileServices,
+            BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
+            CustomTileAddedRepository customTileAddedRepository) {
+        this(tileServices, handler, userTracker, customTileAddedRepository,
+                new TileLifecycleManager(handler, tileServices.getContext(), tileServices,
                 new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher,
                 new Intent(TileService.ACTION_QS_TILE).setComponent(component),
                 userTracker.getUserHandle()));
@@ -82,11 +85,13 @@
 
     @VisibleForTesting
     TileServiceManager(TileServices tileServices, Handler handler, UserTracker userTracker,
+            CustomTileAddedRepository customTileAddedRepository,
             TileLifecycleManager tileLifecycleManager) {
         mServices = tileServices;
         mHandler = handler;
         mStateManager = tileLifecycleManager;
         mUserTracker = userTracker;
+        mCustomTileAddedRepository = customTileAddedRepository;
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -111,8 +116,8 @@
         mStarted = true;
         ComponentName component = mStateManager.getComponent();
         final int userId = mStateManager.getUserId();
-        if (!mServices.getHost().isTileAdded(component, userId)) {
-            mServices.getHost().setTileAdded(component, userId, true);
+        if (!mCustomTileAddedRepository.isTileAdded(component, userId)) {
+            mCustomTileAddedRepository.setTileAdded(component, userId, true);
             mStateManager.onTileAdded();
             mStateManager.flushMessagesAndUnbind();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 42536fe..121955c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -41,6 +41,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
@@ -77,6 +78,7 @@
     private final UserTracker mUserTracker;
     private final StatusBarIconController mStatusBarIconController;
     private final PanelInteractor mPanelInteractor;
+    private final CustomTileAddedRepository mCustomTileAddedRepository;
 
     private int mMaxBound = DEFAULT_MAX_BOUND;
 
@@ -89,7 +91,8 @@
             KeyguardStateController keyguardStateController,
             CommandQueue commandQueue,
             StatusBarIconController statusBarIconController,
-            PanelInteractor panelInteractor) {
+            PanelInteractor panelInteractor,
+            CustomTileAddedRepository customTileAddedRepository) {
         mHost = host;
         mKeyguardStateController = keyguardStateController;
         mContext = mHost.getContext();
@@ -101,6 +104,7 @@
         mStatusBarIconController = statusBarIconController;
         mCommandQueue.addCallback(mRequestListeningCallback);
         mPanelInteractor = panelInteractor;
+        mCustomTileAddedRepository = customTileAddedRepository;
     }
 
     public Context getContext() {
@@ -128,7 +132,7 @@
     protected TileServiceManager onCreateTileService(ComponentName component,
             BroadcastDispatcher broadcastDispatcher) {
         return new TileServiceManager(this, mHandlerProvider.get(), component,
-                broadcastDispatcher, mUserTracker);
+                broadcastDispatcher, mUserTracker, mCustomTileAddedRepository);
     }
 
     public void freeService(CustomTile tile, TileServiceManager service) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt
index 37a9c40..bd9d70c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt
@@ -32,8 +32,6 @@
 interface ForegroundServicesRepository {
     /**
      * The number of packages with a service running in the foreground.
-     *
-     * Note that this will be equal to 0 if [FgsManagerController.isAvailable] is false.
      */
     val foregroundServicesCount: Flow<Int>
 
@@ -52,32 +50,24 @@
     fgsManagerController: FgsManagerController,
 ) : ForegroundServicesRepository {
     override val foregroundServicesCount: Flow<Int> =
-        fgsManagerController.isAvailable
-            .flatMapLatest { isAvailable ->
-                if (!isAvailable) {
-                    return@flatMapLatest flowOf(0)
+            conflatedCallbackFlow<Int> {
+                fun updateState(numberOfPackages: Int) {
+                    trySendWithFailureLogging(numberOfPackages, TAG)
                 }
 
-                conflatedCallbackFlow {
-                    fun updateState(numberOfPackages: Int) {
-                        trySendWithFailureLogging(numberOfPackages, TAG)
-                    }
-
-                    val listener =
+                val listener =
                         object : FgsManagerController.OnNumberOfPackagesChangedListener {
                             override fun onNumberOfPackagesChanged(numberOfPackages: Int) {
                                 updateState(numberOfPackages)
                             }
                         }
 
-                    fgsManagerController.addOnNumberOfPackagesChangedListener(listener)
-                    updateState(fgsManagerController.numRunningPackages)
-                    awaitClose {
-                        fgsManagerController.removeOnNumberOfPackagesChangedListener(listener)
-                    }
+                fgsManagerController.addOnNumberOfPackagesChangedListener(listener)
+                updateState(fgsManagerController.numRunningPackages)
+                awaitClose {
+                    fgsManagerController.removeOnNumberOfPackagesChangedListener(listener)
                 }
-            }
-            .distinctUntilChanged()
+            }.distinctUntilChanged()
 
     override val hasNewChanges: Flow<Boolean> =
         fgsManagerController.showFooterDot.flatMapLatest { showFooterDot ->
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedRepository.kt
new file mode 100644
index 0000000..7fc906b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedRepository.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.content.ComponentName
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.UserFileManager
+import javax.inject.Inject
+
+/**
+ * Repository for keeping track of whether a given [CustomTile] [ComponentName] has been added to
+ * the set of current tiles for a user. This is used to determine when lifecycle methods in
+ * `TileService` about the tile being added/removed need to be called.
+ */
+interface CustomTileAddedRepository {
+    /**
+     * Check if a particular [CustomTile] associated with [componentName] has been added for
+     * [userId] and has not been removed since.
+     */
+    fun isTileAdded(componentName: ComponentName, userId: Int): Boolean
+
+    /**
+     * Persists whether a particular [CustomTile] associated with [componentName] has been added and
+     * it's currently in the set of selected tiles for [userId].
+     */
+    fun setTileAdded(componentName: ComponentName, userId: Int, added: Boolean)
+}
+
+@SysUISingleton
+class CustomTileAddedSharedPrefsRepository
+@Inject
+constructor(private val userFileManager: UserFileManager) : CustomTileAddedRepository {
+
+    override fun isTileAdded(componentName: ComponentName, userId: Int): Boolean {
+        return userFileManager
+            .getSharedPreferences(TILES, 0, userId)
+            .getBoolean(componentName.flattenToString(), false)
+    }
+
+    override fun setTileAdded(componentName: ComponentName, userId: Int, added: Boolean) {
+        userFileManager
+            .getSharedPreferences(TILES, 0, userId)
+            .edit()
+            .putBoolean(componentName.flattenToString(), added)
+            .apply()
+    }
+
+    companion object {
+        private const val TILES = "tiles_prefs"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 3090b79..4a31998 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -496,7 +496,7 @@
         }
 
         // Colors
-        if (state.state != lastState || state.disabledByPolicy || lastDisabledByPolicy) {
+        if (state.state != lastState || state.disabledByPolicy != lastDisabledByPolicy) {
             singleAnimator.cancel()
             mQsLogger?.logTileBackgroundColorUpdateIfInternetTile(
                     state.spec,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 1b83397..f62e939 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -28,15 +28,15 @@
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_ON;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_TRANSITION;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_TRANSITION;
 
 import android.annotation.FloatRange;
 import android.app.ActivityTaskManager;
@@ -85,6 +85,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationBar;
 import com.android.systemui.navigationbar.NavigationBarController;
@@ -336,7 +337,8 @@
         @Override
         public void expandNotificationPanel() {
             verifyCallerAndClearCallingIdentity("expandNotificationPanel",
-                    () -> mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN));
+                    () -> mCommandQueue.handleSystemKey(new KeyEvent(KeyEvent.ACTION_DOWN,
+                            KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN)));
         }
 
         @Override
@@ -520,6 +522,7 @@
             NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
             UserTracker userTracker,
             ScreenLifecycle screenLifecycle,
+            WakefulnessLifecycle wakefulnessLifecycle,
             UiEventLogger uiEventLogger,
             DisplayTracker displayTracker,
             KeyguardUnlockAnimationController sysuiUnlockAnimationController,
@@ -597,8 +600,8 @@
         // Listen for user setup
         mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
 
-        screenLifecycle.addObserver(mLifecycleObserver);
-
+        screenLifecycle.addObserver(mScreenLifecycleObserver);
+        wakefulnessLifecycle.addObserver(mWakefulnessLifecycleObserver);
         // Connect to the service
         updateEnabledState();
         startConnectionToCurrentUser();
@@ -863,81 +866,94 @@
         }
     }
 
-    private final ScreenLifecycle.Observer mLifecycleObserver = new ScreenLifecycle.Observer() {
-        /**
-         * Notifies the Launcher that screen turned on and ready to use
-         */
-        @Override
-        public void onScreenTurnedOn() {
-            mSysUiState
-                .setFlag(SYSUI_STATE_SCREEN_ON, true)
-                .setFlag(SYSUI_STATE_SCREEN_TRANSITION, false)
-                .commitUpdate(mContext.getDisplayId());
-
-            try {
-                if (mOverviewProxy != null) {
-                    mOverviewProxy.onScreenTurnedOn();
-                } else {
-                    Log.e(TAG_OPS, "Failed to get overview proxy for screen turned on event.");
+    private final ScreenLifecycle.Observer mScreenLifecycleObserver =
+            new ScreenLifecycle.Observer() {
+                /**
+                 * Notifies the Launcher that screen turned on and ready to use
+                 */
+                @Override
+                public void onScreenTurnedOn() {
+                    try {
+                        if (mOverviewProxy != null) {
+                            mOverviewProxy.onScreenTurnedOn();
+                        } else {
+                            Log.e(TAG_OPS,
+                                    "Failed to get overview proxy for screen turned on event.");
+                        }
+                    } catch (RemoteException e) {
+                        Log.e(TAG_OPS, "Failed to call onScreenTurnedOn()", e);
+                    }
                 }
-            } catch (RemoteException e) {
-                Log.e(TAG_OPS, "Failed to call onScreenTurnedOn()", e);
-            }
-        }
 
-        /**
-         * Notifies the Launcher that screen turned off.
-         */
-        @Override
-        public void onScreenTurnedOff() {
-            mSysUiState
-                .setFlag(SYSUI_STATE_SCREEN_ON, false)
-                .setFlag(SYSUI_STATE_SCREEN_TRANSITION, false)
-                .commitUpdate(mContext.getDisplayId());
-        }
-
-        /**
-         * Notifies the Launcher that screen is starting to turn on.
-         */
-        @Override
-        public void onScreenTurningOff() {
-            mSysUiState
-                .setFlag(SYSUI_STATE_SCREEN_ON, false)
-                .setFlag(SYSUI_STATE_SCREEN_TRANSITION, true)
-                .commitUpdate(mContext.getDisplayId());
-
-            try {
-                if (mOverviewProxy != null) {
-                    mOverviewProxy.onScreenTurningOff();
-                } else {
-                    Log.e(TAG_OPS, "Failed to get overview proxy for screen turning off event.");
+                /**
+                 * Notifies the Launcher that screen is starting to turn on.
+                 */
+                @Override
+                public void onScreenTurningOff() {
+                    try {
+                        if (mOverviewProxy != null) {
+                            mOverviewProxy.onScreenTurningOff();
+                        } else {
+                            Log.e(TAG_OPS,
+                                    "Failed to get overview proxy for screen turning off event.");
+                        }
+                    } catch (RemoteException e) {
+                        Log.e(TAG_OPS, "Failed to call onScreenTurningOff()", e);
+                    }
                 }
-            } catch (RemoteException e) {
-                Log.e(TAG_OPS, "Failed to call onScreenTurningOff()", e);
-            }
-        }
 
-        /**
-         * Notifies the Launcher that screen is starting to turn on.
-         */
-        @Override
-        public void onScreenTurningOn() {
-            mSysUiState
-                .setFlag(SYSUI_STATE_SCREEN_ON, true)
-                .setFlag(SYSUI_STATE_SCREEN_TRANSITION, true)
-                .commitUpdate(mContext.getDisplayId());
-
-            try {
-                if (mOverviewProxy != null) {
-                    mOverviewProxy.onScreenTurningOn();
-                } else {
-                    Log.e(TAG_OPS, "Failed to get overview proxy for screen turning on event.");
+                /**
+                 * Notifies the Launcher that screen is starting to turn on.
+                 */
+                @Override
+                public void onScreenTurningOn() {
+                    try {
+                        if (mOverviewProxy != null) {
+                            mOverviewProxy.onScreenTurningOn();
+                        } else {
+                            Log.e(TAG_OPS,
+                                    "Failed to get overview proxy for screen turning on event.");
+                        }
+                    } catch (RemoteException e) {
+                        Log.e(TAG_OPS, "Failed to call onScreenTurningOn()", e);
+                    }
                 }
-            } catch (RemoteException e) {
-                Log.e(TAG_OPS, "Failed to call onScreenTurningOn()", e);
-            }
-        }
-    };
+            };
+
+    private final WakefulnessLifecycle.Observer mWakefulnessLifecycleObserver =
+            new WakefulnessLifecycle.Observer() {
+                @Override
+                public void onStartedWakingUp() {
+                    mSysUiState
+                            .setFlag(SYSUI_STATE_AWAKE, true)
+                            .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, true)
+                            .commitUpdate(mContext.getDisplayId());
+                }
+
+                @Override
+                public void onFinishedWakingUp() {
+                    mSysUiState
+                            .setFlag(SYSUI_STATE_AWAKE, true)
+                            .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, false)
+                            .commitUpdate(mContext.getDisplayId());
+                }
+
+                @Override
+                public void onStartedGoingToSleep() {
+                    mSysUiState
+                            .setFlag(SYSUI_STATE_AWAKE, false)
+                            .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, true)
+                            .commitUpdate(mContext.getDisplayId());
+                }
+
+                @Override
+                public void onFinishedGoingToSleep() {
+                    mSysUiState
+                            .setFlag(SYSUI_STATE_AWAKE, false)
+                            .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, false)
+                            .commitUpdate(mContext.getDisplayId());
+                }
+            };
 
     void notifyToggleRecentApps() {
         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index a9af1a2..6f85c45 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -44,7 +44,6 @@
 import android.app.Notification;
 import android.app.assist.AssistContent;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -525,39 +524,6 @@
                 mWindowManager.getCurrentWindowMetrics().getWindowInsets());
     }
 
-    @MainThread
-    void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher,
-            RequestCallback requestCallback) {
-        Assert.isMainThread();
-        mCurrentRequestCallback = requestCallback;
-        takeScreenshotInternal(topComponent, finisher, getFullScreenRect());
-    }
-
-    @MainThread
-    void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
-            Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
-            Consumer<Uri> finisher, RequestCallback requestCallback) {
-        Assert.isMainThread();
-        if (screenshot == null) {
-            Log.e(TAG, "Got null bitmap from screenshot message");
-            mNotificationsController.notifyScreenshotError(
-                    R.string.screenshot_failed_to_capture_text);
-            requestCallback.reportError();
-            return;
-        }
-
-        boolean showFlash = false;
-        if (screenshotScreenBounds == null
-                || !aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
-            showFlash = true;
-            visibleInsets = Insets.NONE;
-            screenshotScreenBounds = new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight());
-        }
-        mCurrentRequestCallback = requestCallback;
-        saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent,
-                showFlash, UserHandle.of(userId));
-    }
-
     /**
      * Clears current screenshot
      */
@@ -695,103 +661,6 @@
         setContentView(mScreenshotView);
     }
 
-    /**
-     * Takes a screenshot of the current display and shows an animation.
-     */
-    private void takeScreenshotInternal(ComponentName topComponent, Consumer<Uri> finisher,
-            Rect crop) {
-        mScreenshotTakenInPortrait =
-                mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
-
-        // copy the input Rect, since SurfaceControl.screenshot can mutate it
-        Rect screenRect = new Rect(crop);
-        Bitmap screenshot = mImageCapture.captureDisplay(mDisplayTracker.getDefaultDisplayId(),
-                crop);
-
-        if (screenshot == null) {
-            Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null");
-            mNotificationsController.notifyScreenshotError(
-                    R.string.screenshot_failed_to_capture_text);
-            if (mCurrentRequestCallback != null) {
-                mCurrentRequestCallback.reportError();
-            }
-            return;
-        }
-
-        saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true,
-                Process.myUserHandle());
-
-        mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
-                ClipboardOverlayController.SELF_PERMISSION);
-    }
-
-    private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
-            Insets screenInsets, ComponentName topComponent, boolean showFlash, UserHandle owner) {
-        withWindowAttached(() -> {
-            if (mUserManager.isManagedProfile(owner.getIdentifier())) {
-                mScreenshotView.announceForAccessibility(mContext.getResources().getString(
-                        R.string.screenshot_saving_work_profile_title));
-            } else {
-                mScreenshotView.announceForAccessibility(
-                        mContext.getResources().getString(R.string.screenshot_saving_title));
-            }
-        });
-
-        mScreenshotView.reset();
-
-        if (mScreenshotView.isAttachedToWindow()) {
-            // if we didn't already dismiss for another reason
-            if (!mScreenshotView.isDismissing()) {
-                mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0, mPackageName);
-            }
-            if (DEBUG_WINDOW) {
-                Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
-                        + "(dismissing=" + mScreenshotView.isDismissing() + ")");
-            }
-        }
-        mPackageName = topComponent == null ? "" : topComponent.getPackageName();
-        mScreenshotView.setPackageName(mPackageName);
-
-        mScreenshotView.updateOrientation(
-                mWindowManager.getCurrentWindowMetrics().getWindowInsets());
-
-        mScreenBitmap = screenshot;
-
-        if (!isUserSetupComplete(owner)) {
-            Log.w(TAG, "User setup not complete, displaying toast only");
-            // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
-            // and sharing shouldn't be exposed to the user.
-            saveScreenshotAndToast(owner, finisher);
-            return;
-        }
-
-        // Optimizations
-        mScreenBitmap.setHasAlpha(false);
-        mScreenBitmap.prepareToDraw();
-
-        saveScreenshotInWorkerThread(owner, finisher, this::showUiOnActionsReady,
-                this::showUiOnQuickShareActionReady);
-
-        // The window is focusable by default
-        setWindowFocusable(true);
-        mScreenshotView.requestFocus();
-
-        enqueueScrollCaptureRequest(owner);
-
-        attachWindow();
-        prepareAnimation(screenRect, showFlash, () -> {
-            mMessageContainerController.onScreenshotTaken(owner);
-        });
-
-        mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
-                mContext.getDrawable(R.drawable.overlay_badge_background), owner));
-        mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
-        // ignore system bar insets for the purpose of window layout
-        mWindow.getDecorView().setOnApplyWindowInsetsListener(
-                (v, insets) -> WindowInsets.CONSUMED);
-        mScreenshotHandler.cancelTimeout(); // restarted after animation
-    }
-
     private void prepareAnimation(Rect screenRect, boolean showFlash,
             Runnable onAnimationComplete) {
         mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index f3d2828..1cdad83 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -36,9 +36,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.graphics.Bitmap;
-import android.graphics.Insets;
-import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
@@ -49,7 +46,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
-import android.view.WindowManager;
 import android.widget.Toast;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -58,7 +54,6 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -222,30 +217,17 @@
             return;
         }
 
-        if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_METADATA_REFACTOR)) {
-            Log.d(TAG, "Processing screenshot data");
-            ScreenshotData screenshotData = ScreenshotData.fromRequest(request);
-            try {
-                mProcessor.processAsync(screenshotData,
-                        (data) -> dispatchToController(data, onSaved, callback));
-            } catch (IllegalStateException e) {
-                Log.e(TAG, "Failed to process screenshot request!", e);
-                logFailedRequest(request);
-                mNotificationsController.notifyScreenshotError(
-                        R.string.screenshot_failed_to_capture_text);
-                callback.reportError();
-            }
-        } else {
-            try {
-                mProcessor.processAsync(request,
-                        (r) -> dispatchToController(r, onSaved, callback));
-            } catch (IllegalStateException e) {
-                Log.e(TAG, "Failed to process screenshot request!", e);
-                logFailedRequest(request);
-                mNotificationsController.notifyScreenshotError(
-                        R.string.screenshot_failed_to_capture_text);
-                callback.reportError();
-            }
+        Log.d(TAG, "Processing screenshot data");
+        ScreenshotData screenshotData = ScreenshotData.fromRequest(request);
+        try {
+            mProcessor.processAsync(screenshotData,
+                    (data) -> dispatchToController(data, onSaved, callback));
+        } catch (IllegalStateException e) {
+            Log.e(TAG, "Failed to process screenshot request!", e);
+            logFailedRequest(request);
+            mNotificationsController.notifyScreenshotError(
+                    R.string.screenshot_failed_to_capture_text);
+            callback.reportError();
         }
     }
 
@@ -257,38 +239,6 @@
         mScreenshot.handleScreenshot(screenshot, uriConsumer, callback);
     }
 
-    private void dispatchToController(ScreenshotRequest request,
-            Consumer<Uri> uriConsumer, RequestCallback callback) {
-        ComponentName topComponent = request.getTopComponent();
-        String packageName = topComponent == null ? "" : topComponent.getPackageName();
-        mUiEventLogger.log(
-                ScreenshotEvent.getScreenshotSource(request.getSource()), 0, packageName);
-
-        switch (request.getType()) {
-            case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
-                if (DEBUG_SERVICE) {
-                    Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_FULLSCREEN");
-                }
-                mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, callback);
-                break;
-            case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
-                if (DEBUG_SERVICE) {
-                    Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
-                }
-                Bitmap screenshot = request.getBitmap();
-                Rect screenBounds = request.getBoundsInScreen();
-                Insets insets = request.getInsets();
-                int taskId = request.getTaskId();
-                int userId = request.getUserId();
-
-                mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
-                        taskId, userId, topComponent, uriConsumer, callback);
-                break;
-            default:
-                Log.wtf(TAG, "Invalid screenshot option: " + request.getType());
-        }
-    }
-
     private void logFailedRequest(ScreenshotRequest request) {
         ComponentName topComponent = request.getTopComponent();
         String packageName = topComponent == null ? "" : topComponent.getPackageName();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 7b46852..b7243ae9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -18,6 +18,7 @@
 
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
+import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
@@ -157,6 +158,7 @@
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -310,7 +312,7 @@
      */
 
     public final boolean mAnimateBack;
-    private final boolean mTrackpadGestureBack;
+    private final boolean mTrackpadGestureFeaturesEnabled;
     /**
      * The minimum scale to "squish" the Shade and associated elements down to, for Back gesture
      */
@@ -391,6 +393,7 @@
     private KeyguardBottomAreaView mKeyguardBottomArea;
     private boolean mExpanding;
     private boolean mSplitShadeEnabled;
+    private boolean mDualShadeEnabled;
     /** The bottom padding reserved for elements of the keyguard measuring notifications. */
     private float mKeyguardNotificationBottomPadding;
     /**
@@ -622,7 +625,9 @@
 
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final KeyguardInteractor mKeyguardInteractor;
+    private final @Nullable MultiShadeInteractor mMultiShadeInteractor;
     private final CoroutineDispatcher mMainDispatcher;
+    private boolean mIsAnyMultiShadeExpanded;
     private boolean mIsOcclusionTransitionRunning = false;
     private int mDreamingToLockscreenTransitionTranslationY;
     private int mOccludedToLockscreenTransitionTranslationY;
@@ -644,6 +649,9 @@
         }
     };
 
+    private final Consumer<Boolean> mMultiShadeExpansionConsumer =
+            (Boolean expanded) -> mIsAnyMultiShadeExpanded = expanded;
+
     private final Consumer<TransitionStep> mDreamingToLockscreenTransition =
             (TransitionStep step) -> {
                 mIsOcclusionTransitionRunning =
@@ -760,6 +768,7 @@
             LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel,
             @Main CoroutineDispatcher mainDispatcher,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
+            Provider<MultiShadeInteractor> multiShadeInteractorProvider,
             DumpManager dumpManager,
             KeyguardLongPressViewModel keyguardLongPressViewModel,
             KeyguardInteractor keyguardInteractor) {
@@ -859,7 +868,9 @@
         mLayoutInflater = layoutInflater;
         mFeatureFlags = featureFlags;
         mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
-        mTrackpadGestureBack = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
+        mTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
+        mDualShadeEnabled = mFeatureFlags.isEnabled(Flags.DUAL_SHADE);
+        mMultiShadeInteractor = mDualShadeEnabled ? multiShadeInteractorProvider.get() : null;
         mFalsingCollector = falsingCollector;
         mPowerManager = powerManager;
         mWakeUpCoordinator = coordinator;
@@ -1096,6 +1107,11 @@
         mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
                 controller.setup(mNotificationContainerParent));
 
+        if (mDualShadeEnabled) {
+            collectFlow(mView, mMultiShadeInteractor.isAnyShadeExpanded(),
+                    mMultiShadeExpansionConsumer, mMainDispatcher);
+        }
+
         // Dreaming->Lockscreen
         collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(),
                 mDreamingToLockscreenTransition, mMainDispatcher);
@@ -4616,7 +4632,9 @@
             mQsController.setExpandImmediate(false);
             // Close the status bar in the next frame so we can show the end of the
             // animation.
-            mView.post(mMaybeHideExpandedRunnable);
+            if (!mIsAnyMultiShadeExpanded) {
+                mView.post(mMaybeHideExpandedRunnable);
+            }
         }
         mCurrentPanelState = state;
     }
@@ -4937,7 +4955,8 @@
             }
 
             // On expanding, single mouse click expands the panel instead of dragging.
-            if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+            if (isFullyCollapsed() && (event.isFromSource(InputDevice.SOURCE_MOUSE)
+                    && !isTrackpadMotionEvent(event))) {
                 if (event.getAction() == MotionEvent.ACTION_UP) {
                     expand(true /* animate */);
                 }
@@ -5092,8 +5111,9 @@
         }
 
         private boolean isTrackpadMotionEvent(MotionEvent ev) {
-            return mTrackpadGestureBack
-                    && ev.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE;
+            return mTrackpadGestureFeaturesEnabled && (
+                    ev.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
+                            || ev.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index ad5a68e4d..e08bc33 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -64,6 +64,11 @@
     boolean closeShadeIfOpen();
 
     /**
+     * Returns whether the shade state is the keyguard or not.
+     */
+    boolean isKeyguard();
+
+    /**
      * Returns whether the shade is currently open.
      * Even though in the current implementation shade is in expanded state on keyguard, this
      * method makes distinction between shade being truly open and plain keyguard state:
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index 826b3ee..c71467b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -154,6 +154,11 @@
     }
 
     @Override
+    public boolean isKeyguard() {
+        return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+    }
+
+    @Override
     public boolean isShadeFullyOpen() {
         return mNotificationPanelViewController.isShadeFullyExpanded();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index c435799..fb4feb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -53,6 +53,7 @@
 import android.os.RemoteException;
 import android.util.Pair;
 import android.util.SparseArray;
+import android.view.KeyEvent;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
@@ -302,7 +303,7 @@
         default void remQsTile(ComponentName tile) { }
         default void clickTile(ComponentName tile) { }
 
-        default void handleSystemKey(int arg1) { }
+        default void handleSystemKey(KeyEvent arg1) { }
         default void showPinningEnterExitToast(boolean entering) { }
         default void showPinningEscapeToast() { }
         default void handleShowGlobalActionsMenu() { }
@@ -891,9 +892,9 @@
     }
 
     @Override
-    public void handleSystemKey(int key) {
+    public void handleSystemKey(KeyEvent key) {
         synchronized (mLock) {
-            mHandler.obtainMessage(MSG_HANDLE_SYSTEM_KEY, key, 0).sendToTarget();
+            mHandler.obtainMessage(MSG_HANDLE_SYSTEM_KEY, key).sendToTarget();
         }
     }
 
@@ -1534,7 +1535,7 @@
                     break;
                 case MSG_HANDLE_SYSTEM_KEY:
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).handleSystemKey(msg.arg1);
+                        mCallbacks.get(i).handleSystemKey((KeyEvent) msg.obj);
                     }
                     break;
                 case MSG_SHOW_GLOBAL_ACTIONS:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index fda2277..765c93e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -869,12 +869,9 @@
         // Walk down a precedence-ordered list of what indication
         // should be shown based on device state
         if (mDozing) {
+            boolean useMisalignmentColor = false;
             mLockScreenIndicationView.setVisibility(View.GONE);
             mTopIndicationView.setVisibility(VISIBLE);
-            // When dozing we ignore any text color and use white instead, because
-            // colors can be hard to read in low brightness.
-            mTopIndicationView.setTextColor(Color.WHITE);
-
             CharSequence newIndication;
             if (!TextUtils.isEmpty(mBiometricMessage)) {
                 newIndication = mBiometricMessage; // note: doesn't show mBiometricMessageFollowUp
@@ -885,8 +882,8 @@
                 mIndicationArea.setVisibility(GONE);
                 return;
             } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
+                useMisalignmentColor = true;
                 newIndication = mAlignmentIndication;
-                mTopIndicationView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
             } else if (mPowerPluggedIn || mEnableBatteryDefender) {
                 newIndication = computePowerIndication();
             } else {
@@ -896,7 +893,14 @@
 
             if (!TextUtils.equals(mTopIndicationView.getText(), newIndication)) {
                 mWakeLock.setAcquired(true);
-                mTopIndicationView.switchIndication(newIndication, null,
+                mTopIndicationView.switchIndication(newIndication,
+                        new KeyguardIndication.Builder()
+                                .setMessage(newIndication)
+                                .setTextColor(ColorStateList.valueOf(
+                                        useMisalignmentColor
+                                                ? mContext.getColor(R.color.misalignment_text_color)
+                                                : Color.WHITE))
+                                .build(),
                         true, () -> mWakeLock.setAcquired(false));
             }
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 9272f06..0195d45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -157,7 +157,8 @@
         if (animationAdapter != null) {
             if (ENABLE_SHELL_TRANSITIONS) {
                 options = ActivityOptions.makeRemoteTransition(
-                        RemoteTransitionAdapter.adaptRemoteAnimation(animationAdapter));
+                        RemoteTransitionAdapter.adaptRemoteAnimation(animationAdapter,
+                                "SysUILaunch"));
             } else {
                 options = ActivityOptions.makeRemoteAnimation(animationAdapter);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index e776015..8b6617b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -308,7 +308,7 @@
      * settings. Down action closes the entire panel.
      */
     @Override
-    public void handleSystemKey(int key) {
+    public void handleSystemKey(KeyEvent key) {
         if (CentralSurfaces.SPEW) {
             Log.d(CentralSurfaces.TAG, "handleNavigationKey: " + key);
         }
@@ -320,11 +320,11 @@
         // Panels are not available in setup
         if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
 
-        if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP == key) {
+        if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP == key.getKeyCode()) {
             mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_UP);
             mNotificationPanelViewController.collapse(
                     false /* delayed */, 1.0f /* speedUpFactor */);
-        } else if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN == key) {
+        } else if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN == key.getKeyCode()) {
             mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN);
             if (mNotificationPanelViewController.isFullyCollapsed()) {
                 if (mVibrateOnOpening) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
index c817466..b303151 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
@@ -85,16 +85,17 @@
 
     /**
      * Called when keyguard is about to be displayed and allows to perform custom animation
+     *
+     * @return A handle that can be used for cancelling the animation, if necessary
      */
-    fun animateInKeyguard(keyguardView: View, after: Runnable) =
-        animations.firstOrNull {
+    fun animateInKeyguard(keyguardView: View, after: Runnable): AnimatorHandle? {
+        animations.forEach {
             if (it.shouldAnimateInKeyguard()) {
-                it.animateInKeyguard(keyguardView, after)
-                true
-            } else {
-                false
+                return@animateInKeyguard it.animateInKeyguard(keyguardView, after)
             }
         }
+        return null
+    }
 
     /**
      * If returns true it will disable propagating touches to apps and keyguard
@@ -211,7 +212,10 @@
     fun onAlwaysOnChanged(alwaysOn: Boolean) {}
 
     fun shouldAnimateInKeyguard(): Boolean = false
-    fun animateInKeyguard(keyguardView: View, after: Runnable) = after.run()
+    fun animateInKeyguard(keyguardView: View, after: Runnable): AnimatorHandle? {
+        after.run()
+        return null
+    }
 
     fun shouldDelayKeyguardShow(): Boolean = false
     fun isKeyguardShowDelayed(): Boolean = false
@@ -224,3 +228,7 @@
     fun shouldAnimateDozingChange(): Boolean = true
     fun shouldAnimateClockChange(): Boolean = true
 }
+
+interface AnimatorHandle {
+    fun cancel()
+}
\ No newline at end of file
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 118bfc5..deb0414 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -160,7 +160,7 @@
      * Animates in the provided keyguard view, ending in the same position that it will be in on
      * AOD.
      */
-    override fun animateInKeyguard(keyguardView: View, after: Runnable) {
+    override fun animateInKeyguard(keyguardView: View, after: Runnable): AnimatorHandle {
         shouldAnimateInKeyguard = false
         keyguardView.alpha = 0f
         keyguardView.visibility = View.VISIBLE
@@ -175,11 +175,36 @@
         // We animate the Y properly separately using the PropertyAnimator, as the panel
         // view also needs to update the end position.
         PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.Y)
-        PropertyAnimator.setProperty<View>(keyguardView, AnimatableProperty.Y, currentY,
-                AnimationProperties().setDuration(duration.toLong()),
-                true /* animate */)
 
-        keyguardView.animate()
+        // Start the animation on the next frame using Choreographer APIs. animateInKeyguard() is
+        // called while the system is busy processing lots of requests, so delaying the animation a
+        // frame will mitigate jank. In the event the animation is cancelled before the next frame
+        // is called, this callback will be removed
+        val keyguardAnimator = keyguardView.animate()
+        val nextFrameCallback = TraceUtils.namedRunnable("startAnimateInKeyguard") {
+            PropertyAnimator.setProperty(keyguardView, AnimatableProperty.Y, currentY,
+                    AnimationProperties().setDuration(duration.toLong()),
+                    true /* animate */)
+            keyguardAnimator.start()
+        }
+        DejankUtils.postAfterTraversal(nextFrameCallback)
+        val animatorHandle = object : AnimatorHandle {
+            private var hasCancelled = false
+            override fun cancel() {
+                if (!hasCancelled) {
+                    DejankUtils.removeCallbacks(nextFrameCallback)
+                    // 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)
+                    hasCancelled = true
+                }
+            }
+        }
+        keyguardAnimator
                 .setDuration(duration.toLong())
                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
                 .alpha(1f)
@@ -205,14 +230,7 @@
                 }
                 .setListener(object : AnimatorListenerAdapter() {
                     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)
-
+                        animatorHandle.cancel()
                         interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
                     }
 
@@ -222,7 +240,7 @@
                                 CUJ_SCREEN_OFF_SHOW_AOD)
                     }
                 })
-                .start()
+        return animatorHandle
     }
 
     override fun onStartedWakingUp() {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index bd60401..e492534 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -295,8 +295,8 @@
             @Override
             public void notifyExpandNotification() {
                 mSysUiMainExecutor.execute(
-                        () -> mCommandQueue.handleSystemKey(
-                                KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN));
+                        () -> mCommandQueue.handleSystemKey(new KeyEvent(KeyEvent.ACTION_DOWN,
+                                KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN)));
             }
         });
 
diff --git a/packages/SystemUI/tests/res/drawable-nodpi/romainguy_rockaway.jpg b/packages/SystemUI/tests/res/drawable-nodpi/romainguy_rockaway.jpg
new file mode 100644
index 0000000..68473ba
--- /dev/null
+++ b/packages/SystemUI/tests/res/drawable-nodpi/romainguy_rockaway.jpg
Binary files differ
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 7ce2b1c..1ba9931 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -152,19 +152,16 @@
                 false);
     }
 
+
     @Test
     public void testReset() {
         mKeyguardAbsKeyInputViewController.reset();
         verify(mKeyguardMessageAreaController).setMessage("", false);
-        verify(mAbsKeyInputView).resetPasswordText(false, false);
-        verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt());
     }
 
     @Test
-    public void onResume_Reset() {
+    public void testResume() {
         mKeyguardAbsKeyInputViewController.onResume(KeyguardSecurityView.VIEW_REVEALED);
-        verify(mKeyguardMessageAreaController).setMessage("", false);
-        verify(mAbsKeyInputView).resetPasswordText(false, false);
         verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 6ae28b7..a8d5569 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -122,22 +122,8 @@
   }
 
   @Test
-  fun reset() {
-    mKeyguardPatternViewController.reset()
-    verify(mLockPatternView).setInStealthMode(anyBoolean())
-    verify(mLockPatternView).enableInput()
-    verify(mLockPatternView).setEnabled(true)
-    verify(mLockPatternView).clearPattern()
-    verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt())
-  }
-
-  @Test
   fun resume() {
     mKeyguardPatternViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
-    verify(mLockPatternView).setInStealthMode(anyBoolean())
-    verify(mLockPatternView).enableInput()
-    verify(mLockPatternView).setEnabled(true)
-    verify(mLockPatternView).clearPattern()
     verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt())
   }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index 33f0ae5..b6287598 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -141,27 +141,6 @@
     }
 
     @Test
-    public void testUnlockIconShows_biometricUnlockedTrue() {
-        // GIVEN UDFPS sensor location is available
-        setupUdfps();
-
-        // GIVEN lock icon controller is initialized and view is attached
-        init(/* useMigrationFlag= */false);
-        captureKeyguardUpdateMonitorCallback();
-
-        // GIVEN user has unlocked with a biometric auth (ie: face auth)
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
-        reset(mLockIconView);
-
-        // WHEN face auth's biometric running state changes
-        mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
-                BiometricSourceType.FACE);
-
-        // THEN the unlock icon is shown
-        verify(mLockIconView).setContentDescription(UNLOCKED_LABEL);
-    }
-
-    @Test
     public void testLockIconStartState() {
         // GIVEN lock icon state
         setupShowLockIcon();
@@ -268,27 +247,6 @@
     }
 
     @Test
-    public void lockIconShows_afterBiometricsCleared() {
-        // GIVEN lock icon controller is initialized and view is attached
-        init(/* useMigrationFlag= */false);
-        captureKeyguardUpdateMonitorCallback();
-
-        // GIVEN user has unlocked with a biometric auth (ie: face auth)
-        // and biometric running state changes
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
-        mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
-                BiometricSourceType.FACE);
-        reset(mLockIconView);
-
-        // WHEN biometrics are cleared
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false);
-        mKeyguardUpdateMonitorCallback.onBiometricsCleared();
-
-        // THEN the lock icon is shown
-        verify(mLockIconView).setContentDescription(LOCKED_LABEL);
-    }
-
-    @Test
     public void lockIconShows_afterUnlockStateChanges() {
         // GIVEN lock icon controller is initialized and view is attached
         init(/* useMigrationFlag= */false);
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 bb037642..b2c2ae7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -21,6 +21,7 @@
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
 import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
 import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
@@ -29,6 +30,7 @@
 import android.view.LayoutInflater
 import android.view.MotionEvent
 import android.view.Surface
+import android.view.Surface.ROTATION_0
 import android.view.Surface.Rotation
 import android.view.View
 import android.view.WindowManager
@@ -42,6 +44,7 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -159,9 +162,10 @@
     private fun withRotation(@Rotation rotation: Int, block: () -> Unit) {
         // Sensor that's in the top left corner of the display in natural orientation.
         val sensorBounds = Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT)
+        val overlayBounds = Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)
         overlayParams = UdfpsOverlayParams(
             sensorBounds,
-            sensorBounds,
+            overlayBounds,
             DISPLAY_WIDTH,
             DISPLAY_HEIGHT,
             scaleFactor = 1f,
@@ -314,4 +318,24 @@
         assertThat(controllerOverlay.matchesRequestId(REQUEST_ID)).isTrue()
         assertThat(controllerOverlay.matchesRequestId(REQUEST_ID + 1)).isFalse()
     }
+
+    @Test
+    fun smallOverlayOnEnrollmentWithA11y() = withRotation(ROTATION_0) {
+        withReason(REASON_ENROLL_ENROLLING) {
+            // When a11y enabled during enrollment
+            whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(true)
+            whenever(featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true)
+
+            controllerOverlay.show(udfpsController, overlayParams)
+            verify(windowManager).addView(
+                eq(controllerOverlay.overlayView),
+                layoutParamsCaptor.capture()
+            )
+
+            // Layout params should use sensor bounds
+            val lp = layoutParamsCaptor.value
+            assertThat(lp.width).isEqualTo(overlayParams.sensorBounds.width())
+            assertThat(lp.height).isEqualTo(overlayParams.sensorBounds.height())
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 445cc87..edee3f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -1344,6 +1344,46 @@
     }
 
     @Test
+    public void onTouch_withNewTouchDetection_pilferPointerWhenAltBouncerShowing()
+            throws RemoteException {
+        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+                0L);
+        final TouchProcessorResult processorResultUnchanged =
+                new TouchProcessorResult.ProcessedTouch(InteractionEvent.UNCHANGED,
+                        1 /* pointerId */, touchData);
+
+        // Enable new touch detection.
+        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
+
+        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
+        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+
+        // Configure UdfpsView to not accept the ACTION_DOWN event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false);
+
+        // GIVEN that the alternate bouncer is showing and a11y touch exploration NOT enabled
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+        // WHEN ACTION_DOWN is received and touch is not within sensor
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultUnchanged);
+        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+        mBiometricExecutor.runAllReady();
+        downEvent.recycle();
+
+        // THEN the touch is pilfered
+        verify(mInputManager, times(1)).pilferPointers(any());
+    }
+
+    @Test
     public void onAodInterrupt_onAcquiredGood_fingerNoLongerDown() throws RemoteException {
         // GIVEN UDFPS overlay is showing
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index f437a8f..6d9acb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.never;
@@ -157,6 +158,22 @@
     }
 
     @Test
+    public void onBiometricAuthenticated_pauseAuth() {
+        // GIVEN view is attached and we're on the keyguard (see testShouldNotPauseAuthOnKeyguard)
+        mController.onViewAttached();
+        captureStatusBarStateListeners();
+        sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+        // WHEN biometric is authenticated
+        captureKeyguardStateControllerCallback();
+        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+        mKeyguardStateControllerCallback.onUnlockedChanged();
+
+        // THEN pause auth
+        assertTrue(mController.shouldPauseAuth());
+    }
+
+    @Test
     public void testShouldPauseAuthIsLaunchTransitionFadingAway() {
         // GIVEN view is attached and we're on the keyguard (see testShouldNotPauseAuthOnKeyguard)
         mController.onViewAttached();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt
index 236384b..4ea9616 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt
@@ -32,6 +32,7 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -161,6 +162,7 @@
         }
 
         verify(controlsModelCallback).onFirstChange()
+        verify(controlsModelCallback).onChange()
     }
 
     @Test
@@ -176,6 +178,7 @@
         )
 
         verify(controlsModelCallback).onFirstChange()
+        verify(controlsModelCallback).onChange()
     }
 
     @Test
@@ -191,6 +194,7 @@
         }
 
         verify(controlsModelCallback, never()).onFirstChange()
+        verify(controlsModelCallback, never()).onChange()
     }
 
     @Test
@@ -207,6 +211,7 @@
         }
 
         verify(controlsModelCallback).onFirstChange()
+        verify(controlsModelCallback).onChange()
     }
 
     @Test
@@ -222,6 +227,7 @@
         )
 
         verify(controlsModelCallback).onFirstChange()
+        verify(controlsModelCallback).onChange()
     }
 
     @Test
@@ -236,5 +242,24 @@
         }
 
         verify(controlsModelCallback, never()).onFirstChange()
+        verify(controlsModelCallback, never()).onChange()
+    }
+
+    @Test
+    fun testAddSecondChange_callbacks() {
+        model.changeFavoriteStatus("${idPrefix}4", true)
+        model.changeFavoriteStatus("${idPrefix}5", true)
+
+        verify(controlsModelCallback).onFirstChange()
+        verify(controlsModelCallback, times(2)).onChange()
+    }
+
+    @Test
+    fun testRemoveSecondChange_callbacks() {
+        model.changeFavoriteStatus("${idPrefix}1", false)
+        model.changeFavoriteStatus("${idPrefix}3", false)
+
+        verify(controlsModelCallback).onFirstChange()
+        verify(controlsModelCallback, times(2)).onChange()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
index 3b6f7d1..4210675 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
@@ -2,27 +2,33 @@
 
 import android.content.ComponentName
 import android.content.Intent
+import android.os.Bundle
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.View
+import android.widget.Button
 import android.window.OnBackInvokedCallback
 import android.window.OnBackInvokedDispatcher
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
 import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlsControllerImpl
-import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.verify
@@ -32,7 +38,15 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class ControlsEditingActivityTest : SysuiTestCase() {
+
+    private companion object {
+        val TEST_COMPONENT = ComponentName("TestPackageName", "TestClassName")
+        val TEST_STRUCTURE: CharSequence = "TestStructure"
+        val TEST_APP: CharSequence = "TestApp"
+    }
+
     private val uiExecutor = FakeExecutor(FakeSystemClock())
+    private val featureFlags = FakeFeatureFlags()
 
     @Mock lateinit var controller: ControlsControllerImpl
 
@@ -40,9 +54,6 @@
 
     @Mock lateinit var customIconCache: CustomIconCache
 
-    @Mock lateinit var uiController: ControlsUiController
-
-    private lateinit var controlsEditingActivity: ControlsEditingActivity_Factory
     private var latch: CountDownLatch = CountDownLatch(1)
 
     @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
@@ -58,11 +69,11 @@
                 ) {
                 override fun create(intent: Intent?): TestableControlsEditingActivity {
                     return TestableControlsEditingActivity(
+                        featureFlags,
                         uiExecutor,
                         controller,
                         userTracker,
                         customIconCache,
-                        uiController,
                         mockDispatcher,
                         latch
                     )
@@ -75,19 +86,17 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        val intent = Intent()
-        intent.putExtra(ControlsEditingActivity.EXTRA_STRUCTURE, "TestTitle")
-        val cname = ComponentName("TestPackageName", "TestClassName")
-        intent.putExtra(Intent.EXTRA_COMPONENT_NAME, cname)
-        activityRule.launchActivity(intent)
+
+        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false)
     }
 
     @Test
     fun testBackCallbackRegistrationAndUnregistration() {
+        launchActivity()
         // 1. ensure that launching the activity results in it registering a callback
         verify(mockDispatcher)
             .registerOnBackInvokedCallback(
-                ArgumentMatchers.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
                 captureCallback.capture()
             )
         activityRule.finishActivity()
@@ -96,15 +105,102 @@
         verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
     }
 
-    public class TestableControlsEditingActivity(
-        private val executor: FakeExecutor,
-        private val controller: ControlsControllerImpl,
-        private val userTracker: UserTracker,
-        private val customIconCache: CustomIconCache,
-        private val uiController: ControlsUiController,
+    @Test
+    fun testNewFlowDisabled_addControlsButton_gone() {
+        with(launchActivity()) {
+            val addControlsButton = requireViewById<Button>(R.id.addControls)
+            assertThat(addControlsButton.visibility).isEqualTo(View.GONE)
+        }
+    }
+
+    @Test
+    fun testNewFlowEnabled_addControlsButton_visible() {
+        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+        with(launchActivity()) {
+            val addControlsButton = requireViewById<Button>(R.id.addControls)
+            assertThat(addControlsButton.visibility).isEqualTo(View.VISIBLE)
+            assertThat(addControlsButton.isEnabled).isTrue()
+        }
+    }
+
+    @Test
+    fun testNotLaunchFromFavoriting_saveButton_disabled() {
+        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+        with(launchActivity(isFromFavoriting = false)) {
+            val saveButton = requireViewById<Button>(R.id.done)
+            assertThat(saveButton.isEnabled).isFalse()
+        }
+    }
+
+    @Test
+    fun testLaunchFromFavoriting_saveButton_enabled() {
+        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+        with(launchActivity(isFromFavoriting = true)) {
+            val saveButton = requireViewById<Button>(R.id.done)
+            assertThat(saveButton.isEnabled).isTrue()
+        }
+    }
+
+    @Test
+    fun testNotFromFavoriting_addControlsPressed_launchesFavouriting() {
+        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+        with(launchActivity(isFromFavoriting = false)) {
+            val addControls = requireViewById<Button>(R.id.addControls)
+
+            activityRule.runOnUiThread { addControls.performClick() }
+
+            with(startActivityData!!.intent) {
+                assertThat(component)
+                    .isEqualTo(ComponentName(context, ControlsFavoritingActivity::class.java))
+                assertThat(getCharSequenceExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE))
+                    .isEqualTo(TEST_STRUCTURE)
+                assertThat(
+                        getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java)
+                    )
+                    .isEqualTo(TEST_COMPONENT)
+                assertThat(getCharSequenceExtra(ControlsFavoritingActivity.EXTRA_APP))
+                    .isEqualTo(TEST_APP)
+                assertThat(getByteExtra(ControlsFavoritingActivity.EXTRA_SOURCE, -1))
+                    .isEqualTo(ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_EDITING)
+            }
+        }
+    }
+
+    private fun launchActivity(
+        componentName: ComponentName = TEST_COMPONENT,
+        structure: CharSequence = TEST_STRUCTURE,
+        isFromFavoriting: Boolean = false,
+        app: CharSequence = TEST_APP,
+    ): TestableControlsEditingActivity =
+        activityRule.launchActivity(
+            Intent().apply {
+                putExtra(ControlsEditingActivity.EXTRA_FROM_FAVORITING, isFromFavoriting)
+                putExtra(ControlsEditingActivity.EXTRA_STRUCTURE, structure)
+                putExtra(Intent.EXTRA_COMPONENT_NAME, componentName)
+                putExtra(ControlsEditingActivity.EXTRA_APP, app)
+            }
+        )
+
+    class TestableControlsEditingActivity(
+        featureFlags: FakeFeatureFlags,
+        executor: FakeExecutor,
+        controller: ControlsControllerImpl,
+        userTracker: UserTracker,
+        customIconCache: CustomIconCache,
         private val mockDispatcher: OnBackInvokedDispatcher,
         private val latch: CountDownLatch
-    ) : ControlsEditingActivity(executor, controller, userTracker, customIconCache, uiController) {
+    ) :
+        ControlsEditingActivity(
+            featureFlags,
+            executor,
+            controller,
+            userTracker,
+            customIconCache,
+        ) {
+
+        var startActivityData: StartActivityData? = null
+            private set
+
         override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
             return mockDispatcher
         }
@@ -114,5 +210,13 @@
             // ensures that test runner thread does not proceed until ui thread is done
             latch.countDown()
         }
+
+        override fun startActivity(intent: Intent) {
+            startActivityData = StartActivityData(intent, null)
+        }
+
+        override fun startActivity(intent: Intent, options: Bundle?) {
+            startActivityData = StartActivityData(intent, options)
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
index 3655232..6884616 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -1,30 +1,49 @@
 package com.android.systemui.controls.management
 
+import android.content.ComponentName
 import android.content.Intent
+import android.os.Bundle
+import android.service.controls.Control
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.View
+import android.widget.Button
 import android.window.OnBackInvokedCallback
 import android.window.OnBackInvokedDispatcher
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
 import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.ControlsControllerImpl
-import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.controller.createLoadDataObject
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
 import com.google.common.util.concurrent.MoreExecutors
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
+import java.util.function.Consumer
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Answers
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -32,7 +51,19 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class ControlsFavoritingActivityTest : SysuiTestCase() {
+
+    private companion object {
+        val TEST_COMPONENT = ComponentName("TestPackageName", "TestClassName")
+        val TEST_CONTROL =
+            mock(Control::class.java, Answers.RETURNS_MOCKS)!!.apply {
+                whenever(structure).thenReturn(TEST_STRUCTURE)
+            }
+        val TEST_STRUCTURE: CharSequence = "TestStructure"
+        val TEST_APP: CharSequence = "TestApp"
+    }
+
     @Main private val executor: Executor = MoreExecutors.directExecutor()
+    private val featureFlags = FakeFeatureFlags()
 
     @Mock lateinit var controller: ControlsControllerImpl
 
@@ -40,13 +71,15 @@
 
     @Mock lateinit var userTracker: UserTracker
 
-    @Mock lateinit var uiController: ControlsUiController
-
-    private lateinit var controlsFavoritingActivity: ControlsFavoritingActivity_Factory
     private var latch: CountDownLatch = CountDownLatch(1)
 
     @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
     @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback>
+    @Captor
+    private lateinit var listingCallback:
+        ArgumentCaptor<ControlsListingController.ControlsListingCallback>
+    @Captor
+    private lateinit var controlsCallback: ArgumentCaptor<Consumer<ControlsController.LoadData>>
 
     @Rule
     @JvmField
@@ -58,11 +91,11 @@
                 ) {
                 override fun create(intent: Intent?): TestableControlsFavoritingActivity {
                     return TestableControlsFavoritingActivity(
+                        featureFlags,
                         executor,
                         controller,
                         listingController,
                         userTracker,
-                        uiController,
                         mockDispatcher,
                         latch
                     )
@@ -75,19 +108,18 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        val intent = Intent()
-        intent.putExtra(ControlsFavoritingActivity.EXTRA_FROM_PROVIDER_SELECTOR, true)
-        activityRule.launchActivity(intent)
+        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false)
     }
 
     // b/259549854 to root-cause and fix
     @FlakyTest
     @Test
     fun testBackCallbackRegistrationAndUnregistration() {
+        launchActivity()
         // 1. ensure that launching the activity results in it registering a callback
         verify(mockDispatcher)
             .registerOnBackInvokedCallback(
-                ArgumentMatchers.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
                 captureCallback.capture()
             )
         activityRule.finishActivity()
@@ -96,22 +128,116 @@
         verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
     }
 
-    public class TestableControlsFavoritingActivity(
+    @Test
+    fun testNewFlowEnabled_buttons() {
+        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+        with(launchActivity()) {
+            verify(listingController).addCallback(listingCallback.capture())
+            listingCallback.value.onServicesUpdated(
+                listOf(mock(ControlsServiceInfo::class.java), mock(ControlsServiceInfo::class.java))
+            )
+
+            val rearrangeButton = requireViewById<Button>(R.id.rearrange)
+            assertThat(rearrangeButton.visibility).isEqualTo(View.VISIBLE)
+            assertThat(rearrangeButton.isEnabled).isFalse()
+            assertThat(requireViewById<Button>(R.id.other_apps).visibility).isEqualTo(View.GONE)
+        }
+    }
+
+    @Test
+    fun testNewFlowDisabled_buttons() {
+        with(launchActivity()) {
+            verify(listingController).addCallback(listingCallback.capture())
+            activityRule.runOnUiThread {
+                listingCallback.value.onServicesUpdated(
+                    listOf(
+                        mock(ControlsServiceInfo::class.java),
+                        mock(ControlsServiceInfo::class.java)
+                    )
+                )
+            }
+
+            val rearrangeButton = requireViewById<Button>(R.id.rearrange)
+            assertThat(rearrangeButton.visibility).isEqualTo(View.GONE)
+            assertThat(rearrangeButton.isEnabled).isFalse()
+            assertThat(requireViewById<Button>(R.id.other_apps).visibility).isEqualTo(View.VISIBLE)
+        }
+    }
+
+    @Test
+    fun testNewFlowEnabled_rearrangePressed_savesAndlaunchesActivity() {
+        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+        with(launchActivity()) {
+            verify(listingController).addCallback(capture(listingCallback))
+            listingCallback.value.onServicesUpdated(
+                listOf(mock(ControlsServiceInfo::class.java), mock(ControlsServiceInfo::class.java))
+            )
+            verify(controller).loadForComponent(any(), capture(controlsCallback), any())
+            activityRule.runOnUiThread {
+                controlsCallback.value.accept(
+                    createLoadDataObject(
+                        listOf(ControlStatus(TEST_CONTROL, TEST_COMPONENT, true)),
+                        emptyList(),
+                    )
+                )
+                requireViewById<Button>(R.id.rearrange).performClick()
+            }
+
+            verify(controller).replaceFavoritesForStructure(any())
+            with(startActivityData!!.intent) {
+                assertThat(component)
+                    .isEqualTo(ComponentName(context, ControlsEditingActivity::class.java))
+                assertThat(
+                        getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java)
+                    )
+                    .isEqualTo(TEST_COMPONENT)
+                assertThat(getCharSequenceExtra(ControlsEditingActivity.EXTRA_APP))
+                    .isEqualTo(TEST_APP)
+                assertThat(getBooleanExtra(ControlsEditingActivity.EXTRA_FROM_FAVORITING, false))
+                    .isTrue()
+                assertThat(getCharSequenceExtra(ControlsEditingActivity.EXTRA_STRUCTURE))
+                    .isEqualTo("")
+            }
+        }
+    }
+
+    private fun launchActivity(
+        componentName: ComponentName = TEST_COMPONENT,
+        structure: CharSequence = TEST_STRUCTURE,
+        app: CharSequence = TEST_APP,
+        source: Byte = ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR,
+    ): TestableControlsFavoritingActivity =
+        activityRule.launchActivity(
+            Intent().apply {
+                putExtra(Intent.EXTRA_COMPONENT_NAME, componentName)
+                putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, structure)
+                putExtra(ControlsFavoritingActivity.EXTRA_APP, app)
+                putExtra(ControlsFavoritingActivity.EXTRA_SOURCE, source)
+            }
+        )
+
+    class TestableControlsFavoritingActivity(
+        featureFlags: FeatureFlags,
         executor: Executor,
         controller: ControlsControllerImpl,
         listingController: ControlsListingController,
         userTracker: UserTracker,
-        uiController: ControlsUiController,
         private val mockDispatcher: OnBackInvokedDispatcher,
         private val latch: CountDownLatch
     ) :
         ControlsFavoritingActivity(
+            featureFlags,
             executor,
             controller,
             listingController,
             userTracker,
-            uiController
         ) {
+
+        var triedToFinish = false
+
+        var startActivityData: StartActivityData? = null
+            private set
+
         override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
             return mockDispatcher
         }
@@ -121,5 +247,17 @@
             // ensures that test runner thread does not proceed until ui thread is done
             latch.countDown()
         }
+
+        override fun startActivity(intent: Intent) {
+            startActivityData = StartActivityData(intent, null)
+        }
+
+        override fun startActivity(intent: Intent, options: Bundle?) {
+            startActivityData = StartActivityData(intent, options)
+        }
+
+        override fun animateExitAndFinish() {
+            triedToFinish = true
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/StartActivityData.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/StartActivityData.kt
new file mode 100644
index 0000000..977e3ba
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/StartActivityData.kt
@@ -0,0 +1,6 @@
+package com.android.systemui.controls.management
+
+import android.content.Intent
+import android.os.Bundle
+
+data class StartActivityData(val intent: Intent, val options: Bundle?)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
index 6b3ec68..a7d7b93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
@@ -28,20 +28,29 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.concurrent.Executor;
+
 @SmallTest
 public class DozeScreenStatePreventingAdapterTest extends SysuiTestCase {
 
+    private Executor mExecutor;
     private DozeMachine.Service mInner;
     private DozeScreenStatePreventingAdapter mWrapper;
 
     @Before
     public void setup() throws Exception {
+        mExecutor = new FakeExecutor(new FakeSystemClock());
         mInner = mock(DozeMachine.Service.class);
-        mWrapper = new DozeScreenStatePreventingAdapter(mInner);
+        mWrapper = new DozeScreenStatePreventingAdapter(
+                mInner,
+                mExecutor
+        );
     }
 
     @Test
@@ -86,7 +95,8 @@
         when(params.getDisplayStateSupported()).thenReturn(false);
 
         assertEquals(DozeScreenStatePreventingAdapter.class,
-                DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params).getClass());
+                DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params, mExecutor)
+                        .getClass());
     }
 
     @Test
@@ -94,6 +104,7 @@
         DozeParameters params = mock(DozeParameters.class);
         when(params.getDisplayStateSupported()).thenReturn(true);
 
-        assertSame(mInner, DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params));
+        assertSame(mInner, DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params,
+                mExecutor));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
index 9ae7217..240d2d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
@@ -28,20 +28,26 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.concurrent.Executor;
+
 @SmallTest
 public class DozeSuspendScreenStatePreventingAdapterTest extends SysuiTestCase {
 
+    private Executor mExecutor;
     private DozeMachine.Service mInner;
     private DozeSuspendScreenStatePreventingAdapter mWrapper;
 
     @Before
     public void setup() throws Exception {
+        mExecutor = new FakeExecutor(new FakeSystemClock());
         mInner = mock(DozeMachine.Service.class);
-        mWrapper = new DozeSuspendScreenStatePreventingAdapter(mInner);
+        mWrapper = new DozeSuspendScreenStatePreventingAdapter(mInner, mExecutor);
     }
 
     @Test
@@ -92,7 +98,8 @@
         when(params.getDozeSuspendDisplayStateSupported()).thenReturn(false);
 
         assertEquals(DozeSuspendScreenStatePreventingAdapter.class,
-                DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params).getClass());
+                DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params, mExecutor)
+                        .getClass());
     }
 
     @Test
@@ -100,6 +107,7 @@
         DozeParameters params = mock(DozeParameters.class);
         when(params.getDozeSuspendDisplayStateSupported()).thenReturn(true);
 
-        assertSame(mInner, DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params));
+        assertSame(mInner, DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params,
+                mExecutor));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
index 7f6e2ba..08427da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
@@ -399,7 +399,21 @@
     }
 
     @Test
-    public void testPause() {
+    public void testPauseWithNoActiveSessions() {
+        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+
+        final Environment environment = new Environment(Stream.of(touchHandler)
+                .collect(Collectors.toCollection(HashSet::new)));
+
+        environment.updateLifecycle(observerOwnerPair -> {
+            observerOwnerPair.first.onPause(observerOwnerPair.second);
+        });
+
+        environment.verifyInputSessionDispose();
+    }
+
+    @Test
+    public void testDeferredPauseWithActiveSessions() {
         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
 
         final Environment environment = new Environment(Stream.of(touchHandler)
@@ -417,14 +431,59 @@
         environment.publishInputEvent(event);
         verify(eventListener).onInputEvent(eq(event));
 
+        final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
+                ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+
+        verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
+
         environment.updateLifecycle(observerOwnerPair -> {
             observerOwnerPair.first.onPause(observerOwnerPair.second);
         });
 
+        verify(environment.mInputSession, never()).dispose();
+
+        // End session
+        touchSessionArgumentCaptor.getValue().pop();
+        environment.executeAll();
+
+        // Check to make sure the input session is now disposed.
         environment.verifyInputSessionDispose();
     }
 
     @Test
+    public void testDestroyWithActiveSessions() {
+        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+
+        final Environment environment = new Environment(Stream.of(touchHandler)
+                .collect(Collectors.toCollection(HashSet::new)));
+
+        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+        environment.publishInputEvent(initialEvent);
+
+        // Ensure session started
+        final InputChannelCompat.InputEventListener eventListener =
+                registerInputEventListener(touchHandler);
+
+        // First event will be missed since we register after the execution loop,
+        final InputEvent event = Mockito.mock(InputEvent.class);
+        environment.publishInputEvent(event);
+        verify(eventListener).onInputEvent(eq(event));
+
+        final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
+                ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+
+        verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
+
+        environment.updateLifecycle(observerOwnerPair -> {
+            observerOwnerPair.first.onDestroy(observerOwnerPair.second);
+        });
+
+        // Check to make sure the input session is now disposed.
+        environment.verifyInputSessionDispose();
+    }
+
+
+    @Test
     public void testPilfering() {
         final DreamTouchHandler touchHandler1 = Mockito.mock(DreamTouchHandler.class);
         final DreamTouchHandler touchHandler2 = Mockito.mock(DreamTouchHandler.class);
@@ -476,7 +535,7 @@
         environment.executeAll();
 
         environment.updateLifecycle(observerOwnerPair -> {
-            observerOwnerPair.first.onPause(observerOwnerPair.second);
+            observerOwnerPair.first.onDestroy(observerOwnerPair.second);
         });
 
         environment.executeAll();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
new file mode 100644
index 0000000..5704ef3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.touch;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ShadeTouchHandlerTest extends SysuiTestCase {
+    @Mock
+    CentralSurfaces mCentralSurfaces;
+
+    @Mock
+    NotificationPanelViewController mNotificationPanelViewController;
+
+    @Mock
+    DreamTouchHandler.TouchSession mTouchSession;
+
+    ShadeTouchHandler mTouchHandler;
+
+    private static final int TOUCH_HEIGHT = 20;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces),
+                TOUCH_HEIGHT);
+        when(mCentralSurfaces.getNotificationPanelViewController())
+                .thenReturn(mNotificationPanelViewController);
+    }
+
+    /**
+     * Verify that touches aren't handled when the bouncer is showing.
+     */
+    @Test
+    public void testInactiveOnBouncer() {
+        when(mCentralSurfaces.isBouncerShowing()).thenReturn(true);
+        mTouchHandler.onSessionStart(mTouchSession);
+        verify(mTouchSession).pop();
+    }
+
+    /**
+     * Make sure {@link ShadeTouchHandler}
+     */
+    @Test
+    public void testTouchPilferingOnScroll() {
+        final MotionEvent motionEvent1 = Mockito.mock(MotionEvent.class);
+        final MotionEvent motionEvent2 = Mockito.mock(MotionEvent.class);
+
+        final ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerArgumentCaptor =
+                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+
+        mTouchHandler.onSessionStart(mTouchSession);
+        verify(mTouchSession).registerGestureListener(gestureListenerArgumentCaptor.capture());
+
+        assertThat(gestureListenerArgumentCaptor.getValue()
+                .onScroll(motionEvent1, motionEvent2, 1, 1))
+                .isTrue();
+    }
+
+    /**
+     * Ensure touches are propagated to the {@link NotificationPanelViewController}.
+     */
+    @Test
+    public void testEventPropagation() {
+        final MotionEvent motionEvent = Mockito.mock(MotionEvent.class);
+
+        final ArgumentCaptor<InputChannelCompat.InputEventListener>
+                inputEventListenerArgumentCaptor =
+                    ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+
+        mTouchHandler.onSessionStart(mTouchSession);
+        verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture());
+        inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent);
+        verify(mNotificationPanelViewController).handleExternalTouch(motionEvent);
+    }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
new file mode 100644
index 0000000..ccd631e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
@@ -0,0 +1,346 @@
+package com.android.systemui.graphics
+
+import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.ImageDecoder
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.graphics.drawable.VectorDrawable
+import android.net.Uri
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+@RunWith(AndroidJUnit4::class)
+class ImageLoaderTest : SysuiTestCase() {
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+    private val imageLoader = ImageLoader(context, testDispatcher)
+
+    private lateinit var imgFile: File
+
+    @Before
+    fun setUp() {
+        val context = context.createPackageContext("com.android.systemui.tests", 0)
+        val bitmap =
+            BitmapFactory.decodeResource(
+                context.resources,
+                com.android.systemui.tests.R.drawable.romainguy_rockaway
+            )
+
+        imgFile = File.createTempFile("image", ".png", context.cacheDir)
+        imgFile.deleteOnExit()
+        bitmap.compress(Bitmap.CompressFormat.PNG, 100, FileOutputStream(imgFile))
+    }
+
+    @After
+    fun tearDown() {
+        imgFile.delete()
+    }
+
+    @Test
+    fun invalidResource_drawable_returnsNull() =
+        testScope.runTest { assertThat(imageLoader.loadDrawable(ImageLoader.Res(-1))).isNull() }
+
+    @Test
+    fun invalidResource_bitmap_returnsNull() =
+        testScope.runTest { assertThat(imageLoader.loadBitmap(ImageLoader.Res(-1))).isNull() }
+
+    @Test
+    fun invalidUri_returnsNull() =
+        testScope.runTest {
+            assertThat(imageLoader.loadBitmap(ImageLoader.Uri("this.is/bogus"))).isNull()
+        }
+
+    @Test
+    fun invalidFile_returnsNull() =
+        testScope.runTest {
+            assertThat(imageLoader.loadBitmap(ImageLoader.File("this is broken!"))).isNull()
+        }
+
+    @Test
+    fun invalidIcon_returnsNull() =
+        testScope.runTest {
+            assertThat(imageLoader.loadDrawable(Icon.createWithFilePath("this is broken"))).isNull()
+        }
+
+    @Test
+    fun invalidIS_returnsNull() =
+        testScope.runTest {
+            assertThat(
+                    imageLoader.loadDrawable(
+                        ImageLoader.InputStream(ByteArrayInputStream(ByteArray(0)))
+                    )
+                )
+                .isNull()
+        }
+
+    @Test
+    fun validBitmapResource_loadDrawable_returnsBitmapDrawable() =
+        testScope.runTest {
+            val context = context.createPackageContext("com.android.systemui.tests", 0)
+            val bitmap =
+                BitmapFactory.decodeResource(
+                    context.resources,
+                    com.android.systemui.tests.R.drawable.romainguy_rockaway
+                )
+            assertThat(bitmap).isNotNull()
+            val loadedDrawable =
+                imageLoader.loadDrawable(
+                    ImageLoader.Res(
+                        com.android.systemui.tests.R.drawable.romainguy_rockaway,
+                        context
+                    )
+                )
+            assertBitmapEqualToDrawable(loadedDrawable, bitmap)
+        }
+
+    @Test
+    fun validBitmapResource_loadBitmap_returnsBitmapDrawable() =
+        testScope.runTest {
+            val bitmap =
+                BitmapFactory.decodeResource(
+                    context.resources,
+                    R.drawable.dessert_zombiegingerbread
+                )
+            val loadedBitmap =
+                imageLoader.loadBitmap(ImageLoader.Res(R.drawable.dessert_zombiegingerbread))
+            assertBitmapEqualToBitmap(loadedBitmap, bitmap)
+        }
+
+    @Test
+    fun validBitmapUri_returnsBitmapDrawable() =
+        testScope.runTest {
+            val bitmap =
+                BitmapFactory.decodeResource(
+                    context.resources,
+                    R.drawable.dessert_zombiegingerbread
+                )
+
+            val uri =
+                "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}"
+            val loadedBitmap = imageLoader.loadBitmap(ImageLoader.Uri(uri))
+            assertBitmapEqualToBitmap(loadedBitmap, bitmap)
+        }
+
+    @Test
+    fun validBitmapFile_returnsBitmapDrawable() =
+        testScope.runTest {
+            val bitmap = BitmapFactory.decodeFile(imgFile.absolutePath)
+            val loadedBitmap = imageLoader.loadBitmap(ImageLoader.File(imgFile))
+            assertBitmapEqualToBitmap(loadedBitmap, bitmap)
+        }
+
+    @Test
+    fun validInputStream_returnsBitmapDrawable() =
+        testScope.runTest {
+            val bitmap = BitmapFactory.decodeFile(imgFile.absolutePath)
+            val loadedBitmap =
+                imageLoader.loadBitmap(ImageLoader.InputStream(FileInputStream(imgFile)))
+            assertBitmapEqualToBitmap(loadedBitmap, bitmap)
+        }
+
+    @Test
+    fun validBitmapIcon_returnsBitmapDrawable() =
+        testScope.runTest {
+            val bitmap =
+                BitmapFactory.decodeResource(
+                    context.resources,
+                    R.drawable.dessert_zombiegingerbread
+                )
+            val loadedDrawable = imageLoader.loadDrawable(Icon.createWithBitmap(bitmap))
+            assertBitmapEqualToDrawable(loadedDrawable, bitmap)
+        }
+
+    @Test
+    fun validUriIcon_returnsBitmapDrawable() =
+        testScope.runTest {
+            val bitmap =
+                BitmapFactory.decodeResource(
+                    context.resources,
+                    R.drawable.dessert_zombiegingerbread
+                )
+            val uri =
+                "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}"
+            val loadedDrawable = imageLoader.loadDrawable(Icon.createWithContentUri(Uri.parse(uri)))
+            assertBitmapEqualToDrawable(loadedDrawable, bitmap)
+        }
+
+    @Test
+    fun validDataIcon_returnsBitmapDrawable() =
+        testScope.runTest {
+            val bitmap =
+                BitmapFactory.decodeResource(
+                    context.resources,
+                    R.drawable.dessert_zombiegingerbread
+                )
+            val bos =
+                ByteArrayOutputStream(
+                    bitmap.byteCount * 2
+                ) // Compressed bitmap should be smaller than its source.
+            bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos)
+
+            val array = bos.toByteArray()
+            val loadedDrawable = imageLoader.loadDrawable(Icon.createWithData(array, 0, array.size))
+            assertBitmapEqualToDrawable(loadedDrawable, bitmap)
+        }
+
+    @Test
+    fun validSystemResourceIcon_returnsBitmapDrawable() =
+        testScope.runTest {
+            val bitmap =
+                Resources.getSystem().getDrawable(android.R.drawable.ic_dialog_alert, context.theme)
+            val loadedDrawable =
+                imageLoader.loadDrawable(
+                    Icon.createWithResource("android", android.R.drawable.ic_dialog_alert)
+                )
+            assertBitmapEqualToDrawable(loadedDrawable, (bitmap as BitmapDrawable).bitmap)
+        }
+
+    @Test
+    fun invalidDifferentPackageResourceIcon_returnsNull() =
+        testScope.runTest {
+            val loadedDrawable =
+                imageLoader.loadDrawable(
+                    Icon.createWithResource(
+                        "noooope.wrong.package",
+                        R.drawable.dessert_zombiegingerbread
+                    )
+                )
+            assertThat(loadedDrawable).isNull()
+        }
+
+    @Test
+    fun validBitmapResource_widthMoreRestricted_downsizesKeepingAspectRatio() =
+        testScope.runTest {
+            val loadedDrawable =
+                imageLoader.loadDrawable(ImageLoader.File(imgFile), maxWidth = 160, maxHeight = 160)
+            val loadedBitmap = assertBitmapInDrawable(loadedDrawable)
+            assertThat(loadedBitmap.width).isEqualTo(160)
+            assertThat(loadedBitmap.height).isEqualTo(106)
+        }
+
+    @Test
+    fun validBitmapResource_heightMoreRestricted_downsizesKeepingAspectRatio() =
+        testScope.runTest {
+            val loadedDrawable =
+                imageLoader.loadDrawable(ImageLoader.File(imgFile), maxWidth = 160, maxHeight = 50)
+            val loadedBitmap = assertBitmapInDrawable(loadedDrawable)
+            assertThat(loadedBitmap.width).isEqualTo(74)
+            assertThat(loadedBitmap.height).isEqualTo(50)
+        }
+
+    @Test
+    fun validBitmapResource_onlyWidthRestricted_downsizesKeepingAspectRatio() =
+        testScope.runTest {
+            val loadedDrawable =
+                imageLoader.loadDrawable(
+                    ImageLoader.File(imgFile),
+                    maxWidth = 160,
+                    maxHeight = ImageLoader.DO_NOT_RESIZE
+                )
+            val loadedBitmap = assertBitmapInDrawable(loadedDrawable)
+            assertThat(loadedBitmap.width).isEqualTo(160)
+            assertThat(loadedBitmap.height).isEqualTo(106)
+        }
+
+    @Test
+    fun validBitmapResource_onlyHeightRestricted_downsizesKeepingAspectRatio() =
+        testScope.runTest {
+            val loadedDrawable =
+                imageLoader.loadDrawable(
+                    ImageLoader.Res(R.drawable.bubble_thumbnail),
+                    maxWidth = ImageLoader.DO_NOT_RESIZE,
+                    maxHeight = 120
+                )
+            val loadedBitmap = assertBitmapInDrawable(loadedDrawable)
+            assertThat(loadedBitmap.width).isEqualTo(123)
+            assertThat(loadedBitmap.height).isEqualTo(120)
+        }
+
+    @Test
+    fun validVectorDrawable_loadDrawable_successfullyLoaded() =
+        testScope.runTest {
+            val loadedDrawable = imageLoader.loadDrawable(ImageLoader.Res(R.drawable.ic_settings))
+            assertThat(loadedDrawable).isNotNull()
+            assertThat(loadedDrawable).isInstanceOf(VectorDrawable::class.java)
+        }
+
+    @Test
+    fun validVectorDrawable_loadBitmap_returnsNull() =
+        testScope.runTest {
+            val loadedBitmap = imageLoader.loadBitmap(ImageLoader.Res(R.drawable.ic_settings))
+            assertThat(loadedBitmap).isNull()
+        }
+
+    @Test
+    fun validVectorDrawableIcon_loadDrawable_successfullyLoaded() =
+        testScope.runTest {
+            val loadedDrawable =
+                imageLoader.loadDrawable(Icon.createWithResource(context, R.drawable.ic_settings))
+            assertThat(loadedDrawable).isNotNull()
+            assertThat(loadedDrawable).isInstanceOf(VectorDrawable::class.java)
+        }
+
+    @Test
+    fun hardwareAllocator_returnsHardwareBitmap() =
+        testScope.runTest {
+            val loadedDrawable =
+                imageLoader.loadDrawable(
+                    ImageLoader.File(imgFile),
+                    allocator = ImageDecoder.ALLOCATOR_HARDWARE
+                )
+            assertThat(loadedDrawable).isNotNull()
+            assertThat((loadedDrawable as BitmapDrawable).bitmap.config)
+                .isEqualTo(Bitmap.Config.HARDWARE)
+        }
+
+    @Test
+    fun softwareAllocator_returnsSoftwareBitmap() =
+        testScope.runTest {
+            val loadedDrawable =
+                imageLoader.loadDrawable(
+                    ImageLoader.File(imgFile),
+                    allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+                )
+            assertThat(loadedDrawable).isNotNull()
+            assertThat((loadedDrawable as BitmapDrawable).bitmap.config)
+                .isNotEqualTo(Bitmap.Config.HARDWARE)
+        }
+
+    private fun assertBitmapInDrawable(drawable: Drawable?): Bitmap {
+        assertThat(drawable).isNotNull()
+        assertThat(drawable).isInstanceOf(BitmapDrawable::class.java)
+        return (drawable as BitmapDrawable).bitmap
+    }
+
+    private fun assertBitmapEqualToDrawable(actual: Drawable?, expected: Bitmap) {
+        val actualBitmap = assertBitmapInDrawable(actual)
+        assertBitmapEqualToBitmap(actualBitmap, expected)
+    }
+
+    private fun assertBitmapEqualToBitmap(actual: Bitmap?, expected: Bitmap) {
+        assertThat(actual).isNotNull()
+        assertThat(actual?.width).isEqualTo(expected.width)
+        assertThat(actual?.height).isEqualTo(expected.height)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index fb7d379..5d83f56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -157,11 +157,34 @@
             assertThat(strongBiometricAllowed()).isFalse()
         }
 
+    @Test
+    fun convenienceBiometricAllowedChange() =
+        testScope.runTest {
+            createBiometricSettingsRepository()
+            val convenienceBiometricAllowed =
+                collectLastValue(underTest.isNonStrongBiometricAllowed)
+            runCurrent()
+
+            onNonStrongAuthChanged(true, PRIMARY_USER_ID)
+            assertThat(convenienceBiometricAllowed()).isTrue()
+
+            onNonStrongAuthChanged(false, ANOTHER_USER_ID)
+            assertThat(convenienceBiometricAllowed()).isTrue()
+
+            onNonStrongAuthChanged(false, PRIMARY_USER_ID)
+            assertThat(convenienceBiometricAllowed()).isFalse()
+        }
+
     private fun onStrongAuthChanged(flags: Int, userId: Int) {
         strongAuthTracker.value.stub.onStrongAuthRequiredChanged(flags, userId)
         testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper
     }
 
+    private fun onNonStrongAuthChanged(allowed: Boolean, userId: Int) {
+        strongAuthTracker.value.stub.onIsNonStrongBiometricAllowedChanged(allowed, userId)
+        testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper
+    }
+
     @Test
     fun fingerprintDisabledByDpmChange() =
         testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
new file mode 100644
index 0000000..6e002f5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -0,0 +1,920 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
+import android.app.StatusBarManager.SESSION_KEYGUARD
+import android.content.pm.UserInfo
+import android.content.pm.UserInfo.FLAG_PRIMARY
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_CANCELED
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT
+import android.hardware.biometrics.ComponentInfoInternal
+import android.hardware.face.FaceAuthenticateOptions
+import android.hardware.face.FaceManager
+import android.hardware.face.FaceSensorProperties
+import android.hardware.face.FaceSensorPropertiesInternal
+import android.os.CancellationSignal
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId.fakeInstanceId
+import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
+import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.FlowValue
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.log.SessionTracker
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.FakeKeyguardStateController
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.SystemClock
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
+    private lateinit var underTest: DeviceEntryFaceAuthRepositoryImpl
+
+    @Mock private lateinit var faceManager: FaceManager
+    @Mock private lateinit var bypassController: KeyguardBypassController
+    @Mock private lateinit var sessionTracker: SessionTracker
+    @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var dumpManager: DumpManager
+
+    @Captor
+    private lateinit var authenticationCallback: ArgumentCaptor<FaceManager.AuthenticationCallback>
+
+    @Captor
+    private lateinit var detectionCallback: ArgumentCaptor<FaceManager.FaceDetectionCallback>
+    @Captor private lateinit var cancellationSignal: ArgumentCaptor<CancellationSignal>
+
+    private lateinit var bypassStateChangedListener:
+        KotlinArgumentCaptor<KeyguardBypassController.OnBypassStateChangedListener>
+
+    @Captor
+    private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback>
+    private lateinit var testDispatcher: TestDispatcher
+
+    private lateinit var testScope: TestScope
+    private lateinit var fakeUserRepository: FakeUserRepository
+    private lateinit var authStatus: FlowValue<AuthenticationStatus?>
+    private lateinit var detectStatus: FlowValue<DetectionStatus?>
+    private lateinit var authRunning: FlowValue<Boolean?>
+    private lateinit var lockedOut: FlowValue<Boolean?>
+    private lateinit var canFaceAuthRun: FlowValue<Boolean?>
+    private lateinit var authenticated: FlowValue<Boolean?>
+    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+    private lateinit var deviceEntryFingerprintAuthRepository:
+        FakeDeviceEntryFingerprintAuthRepository
+    private lateinit var trustRepository: FakeTrustRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var keyguardInteractor: KeyguardInteractor
+    private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+    private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
+    private lateinit var fakeCommandQueue: FakeCommandQueue
+    private lateinit var featureFlags: FakeFeatureFlags
+
+    private var wasAuthCancelled = false
+    private var wasDetectCancelled = false
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        fakeUserRepository = FakeUserRepository()
+        fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser))
+        testDispatcher = StandardTestDispatcher()
+        biometricSettingsRepository = FakeBiometricSettingsRepository()
+        deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+        trustRepository = FakeTrustRepository()
+        keyguardRepository = FakeKeyguardRepository()
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
+        fakeCommandQueue = FakeCommandQueue()
+        keyguardInteractor =
+            KeyguardInteractor(
+                keyguardRepository,
+                fakeCommandQueue,
+                featureFlags,
+                bouncerRepository
+            )
+        alternateBouncerInteractor =
+            AlternateBouncerInteractor(
+                bouncerRepository = bouncerRepository,
+                biometricSettingsRepository = biometricSettingsRepository,
+                deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository,
+                systemClock = mock(SystemClock::class.java),
+                keyguardStateController = FakeKeyguardStateController(),
+                statusBarStateController = mock(StatusBarStateController::class.java),
+            )
+
+        bypassStateChangedListener =
+            KotlinArgumentCaptor(KeyguardBypassController.OnBypassStateChangedListener::class.java)
+        testScope = TestScope(testDispatcher)
+        whenever(sessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(keyguardSessionId)
+        whenever(faceManager.sensorPropertiesInternal)
+            .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
+        whenever(bypassController.bypassEnabled).thenReturn(true)
+        underTest = createDeviceEntryFaceAuthRepositoryImpl(faceManager, bypassController)
+    }
+
+    private fun createDeviceEntryFaceAuthRepositoryImpl(
+        fmOverride: FaceManager? = faceManager,
+        bypassControllerOverride: KeyguardBypassController? = bypassController
+    ) =
+        DeviceEntryFaceAuthRepositoryImpl(
+            mContext,
+            fmOverride,
+            fakeUserRepository,
+            bypassControllerOverride,
+            testScope.backgroundScope,
+            testDispatcher,
+            sessionTracker,
+            uiEventLogger,
+            FaceAuthenticationLogger(logcatLogBuffer("DeviceEntryFaceAuthRepositoryLog")),
+            biometricSettingsRepository,
+            deviceEntryFingerprintAuthRepository,
+            trustRepository,
+            keyguardRepository,
+            keyguardInteractor,
+            alternateBouncerInteractor,
+            dumpManager,
+        )
+
+    @Test
+    fun faceAuthRunsAndProvidesAuthStatusUpdates() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+
+            FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER.extraInfo = 10
+            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            faceAuthenticateIsCalled()
+            uiEventIsLogged(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+
+            assertThat(authRunning()).isTrue()
+
+            val successResult = successResult()
+            authenticationCallback.value.onAuthenticationSucceeded(successResult)
+
+            assertThat(authStatus()).isEqualTo(SuccessAuthenticationStatus(successResult))
+            assertThat(authenticated()).isTrue()
+            assertThat(authRunning()).isFalse()
+        }
+
+    private fun uiEventIsLogged(faceAuthUiEvent: FaceAuthUiEvent) {
+        verify(uiEventLogger)
+            .logWithInstanceIdAndPosition(
+                faceAuthUiEvent,
+                0,
+                null,
+                keyguardSessionId,
+                faceAuthUiEvent.extraInfo
+            )
+    }
+
+    @Test
+    fun faceAuthDoesNotRunWhileItIsAlreadyRunning() =
+        testScope.runTest {
+            initCollectors()
+
+            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            faceAuthenticateIsCalled()
+            clearInvocations(faceManager)
+            clearInvocations(uiEventLogger)
+
+            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            verifyNoMoreInteractions(faceManager)
+            verifyNoMoreInteractions(uiEventLogger)
+        }
+
+    @Test
+    fun faceLockoutStatusIsPropagated() =
+        testScope.runTest {
+            initCollectors()
+            verify(faceManager).addLockoutResetCallback(faceLockoutResetCallback.capture())
+
+            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            faceAuthenticateIsCalled()
+
+            authenticationCallback.value.onAuthenticationError(
+                FACE_ERROR_LOCKOUT_PERMANENT,
+                "face locked out"
+            )
+
+            assertThat(lockedOut()).isTrue()
+
+            faceLockoutResetCallback.value.onLockoutReset(0)
+            assertThat(lockedOut()).isFalse()
+        }
+
+    @Test
+    fun faceDetectionSupportIsTheCorrectValue() =
+        testScope.runTest {
+            assertThat(
+                    createDeviceEntryFaceAuthRepositoryImpl(fmOverride = null).isDetectionSupported
+                )
+                .isFalse()
+
+            whenever(faceManager.sensorPropertiesInternal).thenReturn(null)
+            assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isFalse()
+
+            whenever(faceManager.sensorPropertiesInternal).thenReturn(listOf())
+            assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isFalse()
+
+            whenever(faceManager.sensorPropertiesInternal)
+                .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = false)))
+            assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isFalse()
+
+            whenever(faceManager.sensorPropertiesInternal)
+                .thenReturn(
+                    listOf(
+                        createFaceSensorProperties(supportsFaceDetection = false),
+                        createFaceSensorProperties(supportsFaceDetection = true)
+                    )
+                )
+            assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isFalse()
+
+            whenever(faceManager.sensorPropertiesInternal)
+                .thenReturn(
+                    listOf(
+                        createFaceSensorProperties(supportsFaceDetection = true),
+                        createFaceSensorProperties(supportsFaceDetection = false)
+                    )
+                )
+            assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isTrue()
+        }
+
+    @Test
+    fun cancelStopsFaceAuthentication() =
+        testScope.runTest {
+            initCollectors()
+
+            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            faceAuthenticateIsCalled()
+
+            var wasAuthCancelled = false
+            cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
+
+            underTest.cancel()
+            assertThat(wasAuthCancelled).isTrue()
+            assertThat(authRunning()).isFalse()
+        }
+
+    @Test
+    fun cancelInvokedWithoutFaceAuthRunningIsANoop() = testScope.runTest { underTest.cancel() }
+
+    @Test
+    fun faceDetectionRunsAndPropagatesDetectionStatus() =
+        testScope.runTest {
+            whenever(faceManager.sensorPropertiesInternal)
+                .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
+            underTest = createDeviceEntryFaceAuthRepositoryImpl()
+            initCollectors()
+
+            underTest.detect()
+            faceDetectIsCalled()
+
+            detectionCallback.value.onFaceDetected(1, 1, true)
+
+            assertThat(detectStatus()).isEqualTo(DetectionStatus(1, 1, true))
+        }
+
+    @Test
+    fun faceDetectDoesNotRunIfDetectionIsNotSupported() =
+        testScope.runTest {
+            whenever(faceManager.sensorPropertiesInternal)
+                .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = false)))
+            underTest = createDeviceEntryFaceAuthRepositoryImpl()
+            initCollectors()
+            clearInvocations(faceManager)
+
+            underTest.detect()
+
+            verify(faceManager, never())
+                .detectFace(any(), any(), any(FaceAuthenticateOptions::class.java))
+        }
+
+    @Test
+    fun faceAuthShouldWaitAndRunIfTriggeredWhileCancelling() =
+        testScope.runTest {
+            initCollectors()
+
+            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            faceAuthenticateIsCalled()
+
+            // Enter cancelling state
+            underTest.cancel()
+            clearInvocations(faceManager)
+
+            // Auth is while cancelling.
+            underTest.authenticate(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
+            // Auth is not started
+            verifyNoMoreInteractions(faceManager)
+
+            // Auth is done cancelling.
+            authenticationCallback.value.onAuthenticationError(
+                FACE_ERROR_CANCELED,
+                "First auth attempt cancellation completed"
+            )
+            assertThat(authStatus())
+                .isEqualTo(
+                    ErrorAuthenticationStatus(
+                        FACE_ERROR_CANCELED,
+                        "First auth attempt cancellation completed"
+                    )
+                )
+
+            faceAuthenticateIsCalled()
+            uiEventIsLogged(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
+        }
+
+    @Test
+    fun faceAuthAutoCancelsAfterDefaultCancellationTimeout() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+
+            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            faceAuthenticateIsCalled()
+
+            clearInvocations(faceManager)
+            underTest.cancel()
+            advanceTimeBy(DeviceEntryFaceAuthRepositoryImpl.DEFAULT_CANCEL_SIGNAL_TIMEOUT + 1)
+
+            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            faceAuthenticateIsCalled()
+        }
+
+    @Test
+    fun faceHelpMessagesAreIgnoredBasedOnConfig() =
+        testScope.runTest {
+            overrideResource(
+                R.array.config_face_acquire_device_entry_ignorelist,
+                intArrayOf(10, 11)
+            )
+            underTest = createDeviceEntryFaceAuthRepositoryImpl()
+            initCollectors()
+
+            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            faceAuthenticateIsCalled()
+
+            authenticationCallback.value.onAuthenticationHelp(9, "help msg")
+            authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg")
+            authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg")
+
+            assertThat(authStatus()).isEqualTo(HelpAuthenticationStatus(9, "help msg"))
+        }
+
+    @Test
+    fun dumpDoesNotErrorOutWhenFaceManagerOrBypassControllerIsNull() =
+        testScope.runTest {
+            fakeUserRepository.setSelectedUserInfo(primaryUser)
+            underTest.dump(PrintWriter(StringWriter()), emptyArray())
+
+            underTest =
+                createDeviceEntryFaceAuthRepositoryImpl(
+                    fmOverride = null,
+                    bypassControllerOverride = null
+                )
+            fakeUserRepository.setSelectedUserInfo(primaryUser)
+
+            underTest.dump(PrintWriter(StringWriter()), emptyArray())
+        }
+
+    @Test
+    fun authenticateDoesNotRunIfFaceIsNotEnrolled() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth { biometricSettingsRepository.setFaceEnrolled(false) }
+        }
+
+    @Test
+    fun authenticateDoesNotRunIfFaceIsNotEnabled() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth { biometricSettingsRepository.setIsFaceAuthEnabled(false) }
+        }
+
+    @Test
+    fun authenticateDoesNotRunIfUserIsInLockdown() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth { biometricSettingsRepository.setIsUserInLockdown(true) }
+        }
+
+    @Test
+    fun authenticateDoesNotRunIfUserIsCurrentlySwitching() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth { fakeUserRepository.setUserSwitching(true) }
+        }
+
+    @Test
+    fun authenticateDoesNotRunWhenFpIsLockedOut() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth { deviceEntryFingerprintAuthRepository.setLockedOut(true) }
+        }
+
+    @Test
+    fun authenticateDoesNotRunWhenUserIsCurrentlyTrusted() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth { trustRepository.setCurrentUserTrusted(true) }
+        }
+
+    @Test
+    fun authenticateDoesNotRunWhenKeyguardIsGoingAway() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth { keyguardRepository.setKeyguardGoingAway(true) }
+        }
+
+    @Test
+    fun authenticateDoesNotRunWhenDeviceIsGoingToSleep() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth {
+                keyguardRepository.setWakefulnessModel(
+                    WakefulnessModel(
+                        state = WakefulnessState.STARTING_TO_SLEEP,
+                        isWakingUpOrAwake = false,
+                        lastWakeReason = WakeSleepReason.OTHER,
+                        lastSleepReason = WakeSleepReason.OTHER,
+                    )
+                )
+            }
+        }
+
+    @Test
+    fun authenticateDoesNotRunWhenDeviceIsSleeping() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth {
+                keyguardRepository.setWakefulnessModel(
+                    WakefulnessModel(
+                        state = WakefulnessState.ASLEEP,
+                        isWakingUpOrAwake = false,
+                        lastWakeReason = WakeSleepReason.OTHER,
+                        lastSleepReason = WakeSleepReason.OTHER,
+                    )
+                )
+            }
+        }
+
+    @Test
+    fun authenticateDoesNotRunWhenNonStrongBiometricIsNotAllowed() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth {
+                biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
+            }
+        }
+
+    @Test
+    fun authenticateDoesNotRunWhenCurrentUserIsNotPrimary() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth {
+                launch { fakeUserRepository.setSelectedUserInfo(secondaryUser) }
+            }
+        }
+
+    @Test
+    fun authenticateDoesNotRunWhenSecureCameraIsActive() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth {
+                bouncerRepository.setAlternateVisible(false)
+                fakeCommandQueue.doForEachCallback {
+                    it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+                }
+            }
+        }
+
+    @Test
+    fun authenticateDoesNotRunOnUnsupportedPosture() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth {
+                biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(false)
+            }
+        }
+
+    @Test
+    fun authenticateFallbacksToDetectionWhenItCannotRun() =
+        testScope.runTest {
+            whenever(faceManager.sensorPropertiesInternal)
+                .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
+            whenever(bypassController.bypassEnabled).thenReturn(true)
+            underTest = createDeviceEntryFaceAuthRepositoryImpl()
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+
+            // Flip one precondition to false.
+            biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
+            assertThat(canFaceAuthRun()).isFalse()
+            underTest.authenticate(
+                FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
+                fallbackToDetection = true
+            )
+            faceAuthenticateIsNotCalled()
+
+            faceDetectIsCalled()
+        }
+
+    @Test
+    fun everythingWorksWithFaceAuthRefactorFlagDisabled() =
+        testScope.runTest {
+            featureFlags.set(FACE_AUTH_REFACTOR, false)
+
+            underTest = createDeviceEntryFaceAuthRepositoryImpl()
+            initCollectors()
+
+            // Collecting any flows exposed in the public API doesn't throw any error
+            authStatus()
+            detectStatus()
+            authRunning()
+            lockedOut()
+            canFaceAuthRun()
+            authenticated()
+        }
+
+    @Test
+    fun isAuthenticatedIsFalseWhenFaceAuthFails() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+
+            triggerFaceAuth(false)
+
+            authenticationCallback.value.onAuthenticationFailed()
+
+            assertThat(authenticated()).isFalse()
+        }
+
+    @Test
+    fun isAuthenticatedIsFalseWhenFaceAuthErrorsOut() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+
+            triggerFaceAuth(false)
+
+            authenticationCallback.value.onAuthenticationError(-1, "some error")
+
+            assertThat(authenticated()).isFalse()
+        }
+
+    @Test
+    fun isAuthenticatedIsResetToFalseWhenKeyguardIsGoingAway() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+
+            triggerFaceAuth(false)
+
+            authenticationCallback.value.onAuthenticationSucceeded(
+                mock(FaceManager.AuthenticationResult::class.java)
+            )
+
+            assertThat(authenticated()).isTrue()
+
+            keyguardRepository.setKeyguardGoingAway(true)
+
+            assertThat(authenticated()).isFalse()
+        }
+
+    @Test
+    fun isAuthenticatedIsResetToFalseWhenUserIsSwitching() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+
+            triggerFaceAuth(false)
+
+            authenticationCallback.value.onAuthenticationSucceeded(
+                mock(FaceManager.AuthenticationResult::class.java)
+            )
+
+            assertThat(authenticated()).isTrue()
+
+            fakeUserRepository.setUserSwitching(true)
+
+            assertThat(authenticated()).isFalse()
+        }
+
+    @Test
+    fun detectDoesNotRunWhenFaceIsNotEnrolled() =
+        testScope.runTest {
+            testGatingCheckForDetect { biometricSettingsRepository.setFaceEnrolled(false) }
+        }
+
+    @Test
+    fun detectDoesNotRunWhenFaceIsNotEnabled() =
+        testScope.runTest {
+            testGatingCheckForDetect { biometricSettingsRepository.setIsFaceAuthEnabled(false) }
+        }
+
+    @Test
+    fun detectDoesNotRunWhenUserSwitchingInProgress() =
+        testScope.runTest { testGatingCheckForDetect { fakeUserRepository.setUserSwitching(true) } }
+
+    @Test
+    fun detectDoesNotRunWhenKeyguardGoingAway() =
+        testScope.runTest {
+            testGatingCheckForDetect { keyguardRepository.setKeyguardGoingAway(true) }
+        }
+
+    @Test
+    fun detectDoesNotRunWhenDeviceSleepingStartingToSleep() =
+        testScope.runTest {
+            testGatingCheckForDetect {
+                keyguardRepository.setWakefulnessModel(
+                    WakefulnessModel(
+                        state = WakefulnessState.STARTING_TO_SLEEP,
+                        isWakingUpOrAwake = false,
+                        lastWakeReason = WakeSleepReason.OTHER,
+                        lastSleepReason = WakeSleepReason.OTHER,
+                    )
+                )
+            }
+        }
+
+    @Test
+    fun detectDoesNotRunWhenSecureCameraIsActive() =
+        testScope.runTest {
+            testGatingCheckForDetect {
+                bouncerRepository.setAlternateVisible(false)
+                fakeCommandQueue.doForEachCallback {
+                    it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+                }
+            }
+        }
+
+    @Test
+    fun detectDoesNotRunWhenFaceAuthNotSupportedInCurrentPosture() =
+        testScope.runTest {
+            testGatingCheckForDetect {
+                biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(false)
+            }
+        }
+
+    @Test
+    fun detectDoesNotRunWhenCurrentUserInLockdown() =
+        testScope.runTest {
+            testGatingCheckForDetect { biometricSettingsRepository.setIsUserInLockdown(true) }
+        }
+
+    @Test
+    fun detectDoesNotRunWhenBypassIsNotEnabled() =
+        testScope.runTest {
+            runCurrent()
+            verify(bypassController)
+                .registerOnBypassStateChangedListener(bypassStateChangedListener.capture())
+
+            testGatingCheckForDetect {
+                bypassStateChangedListener.value.onBypassStateChanged(false)
+            }
+        }
+
+    @Test
+    fun detectDoesNotRunWhenNonStrongBiometricIsAllowed() =
+        testScope.runTest {
+            testGatingCheckForDetect {
+                biometricSettingsRepository.setIsNonStrongBiometricAllowed(true)
+            }
+        }
+
+    @Test
+    fun detectDoesNotRunIfUdfpsIsRunning() =
+        testScope.runTest {
+            testGatingCheckForDetect {
+                deviceEntryFingerprintAuthRepository.setAvailableFpSensorType(
+                    BiometricType.UNDER_DISPLAY_FINGERPRINT
+                )
+                deviceEntryFingerprintAuthRepository.setIsRunning(true)
+            }
+        }
+
+    private suspend fun TestScope.testGatingCheckForFaceAuth(gatingCheckModifier: () -> Unit) {
+        initCollectors()
+        allPreconditionsToRunFaceAuthAreTrue()
+
+        gatingCheckModifier()
+        runCurrent()
+
+        // gating check doesn't allow face auth to run.
+        assertThat(underTest.canRunFaceAuth.value).isFalse()
+
+        // flip the gating check back on.
+        allPreconditionsToRunFaceAuthAreTrue()
+
+        triggerFaceAuth(false)
+
+        // Flip gating check off
+        gatingCheckModifier()
+        runCurrent()
+
+        // Stops currently running auth
+        assertThat(wasAuthCancelled).isTrue()
+        clearInvocations(faceManager)
+
+        // Try auth again
+        underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+
+        // Auth can't run again
+        faceAuthenticateIsNotCalled()
+    }
+
+    private suspend fun TestScope.testGatingCheckForDetect(gatingCheckModifier: () -> Unit) {
+        initCollectors()
+        allPreconditionsToRunFaceAuthAreTrue()
+
+        // This will stop face auth from running but is required to be false for detect.
+        biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
+        runCurrent()
+
+        assertThat(canFaceAuthRun()).isFalse()
+
+        // Trigger authenticate with detection fallback
+        underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetection = true)
+
+        faceAuthenticateIsNotCalled()
+        faceDetectIsCalled()
+        cancellationSignal.value.setOnCancelListener { wasDetectCancelled = true }
+
+        // Flip gating check
+        gatingCheckModifier()
+        runCurrent()
+
+        // Stops currently running detect
+        assertThat(wasDetectCancelled).isTrue()
+        clearInvocations(faceManager)
+
+        // Try to run detect again
+        underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetection = true)
+
+        // Detect won't run because preconditions are not true anymore.
+        faceDetectIsNotCalled()
+    }
+
+    private suspend fun triggerFaceAuth(fallbackToDetect: Boolean) {
+        assertThat(canFaceAuthRun()).isTrue()
+        underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetect)
+        faceAuthenticateIsCalled()
+        assertThat(authRunning()).isTrue()
+        cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
+    }
+
+    private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
+        biometricSettingsRepository.setFaceEnrolled(true)
+        biometricSettingsRepository.setIsFaceAuthEnabled(true)
+        fakeUserRepository.setUserSwitching(false)
+        deviceEntryFingerprintAuthRepository.setLockedOut(false)
+        trustRepository.setCurrentUserTrusted(false)
+        keyguardRepository.setKeyguardGoingAway(false)
+        keyguardRepository.setWakefulnessModel(
+            WakefulnessModel(
+                WakefulnessState.STARTING_TO_WAKE,
+                true,
+                WakeSleepReason.OTHER,
+                WakeSleepReason.OTHER
+            )
+        )
+        biometricSettingsRepository.setIsNonStrongBiometricAllowed(true)
+        biometricSettingsRepository.setIsUserInLockdown(false)
+        fakeUserRepository.setSelectedUserInfo(primaryUser)
+        biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
+        bouncerRepository.setAlternateVisible(true)
+        runCurrent()
+    }
+
+    private suspend fun TestScope.initCollectors() {
+        authStatus = collectLastValue(underTest.authenticationStatus)
+        detectStatus = collectLastValue(underTest.detectionStatus)
+        authRunning = collectLastValue(underTest.isAuthRunning)
+        lockedOut = collectLastValue(underTest.isLockedOut)
+        canFaceAuthRun = collectLastValue(underTest.canRunFaceAuth)
+        authenticated = collectLastValue(underTest.isAuthenticated)
+        fakeUserRepository.setSelectedUserInfo(primaryUser)
+    }
+
+    private fun successResult() = FaceManager.AuthenticationResult(null, null, primaryUserId, false)
+
+    private fun faceDetectIsCalled() {
+        verify(faceManager)
+            .detectFace(
+                cancellationSignal.capture(),
+                detectionCallback.capture(),
+                eq(FaceAuthenticateOptions.Builder().setUserId(primaryUserId).build())
+            )
+    }
+
+    private fun faceAuthenticateIsCalled() {
+        verify(faceManager)
+            .authenticate(
+                isNull(),
+                cancellationSignal.capture(),
+                authenticationCallback.capture(),
+                isNull(),
+                eq(FaceAuthenticateOptions.Builder().setUserId(primaryUserId).build())
+            )
+    }
+
+    private fun faceAuthenticateIsNotCalled() {
+        verify(faceManager, never())
+            .authenticate(
+                isNull(),
+                any(),
+                any(),
+                isNull(),
+                any(FaceAuthenticateOptions::class.java)
+            )
+    }
+
+    private fun faceDetectIsNotCalled() {
+        verify(faceManager, never())
+            .detectFace(any(), any(), any(FaceAuthenticateOptions::class.java))
+    }
+
+    private fun createFaceSensorProperties(
+        supportsFaceDetection: Boolean
+    ): FaceSensorPropertiesInternal {
+        val componentInfo =
+            listOf(
+                ComponentInfoInternal(
+                    "faceSensor" /* componentId */,
+                    "vendor/model/revision" /* hardwareVersion */,
+                    "1.01" /* firmwareVersion */,
+                    "00000001" /* serialNumber */,
+                    "" /* softwareVersion */
+                )
+            )
+        return FaceSensorPropertiesInternal(
+            0 /* id */,
+            FaceSensorProperties.STRENGTH_STRONG,
+            1 /* maxTemplatesAllowed */,
+            componentInfo,
+            FaceSensorProperties.TYPE_UNKNOWN,
+            supportsFaceDetection /* supportsFaceDetection */,
+            true /* supportsSelfIllumination */,
+            false /* resetLockoutRequiresChallenge */
+        )
+    }
+
+    companion object {
+        const val primaryUserId = 1
+        val keyguardSessionId = fakeInstanceId(10)!!
+        val primaryUser = UserInfo(primaryUserId, "test user", FLAG_PRIMARY)
+
+        val secondaryUser = UserInfo(2, "secondary user", 0)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index 70f766f..e57b044 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT
 import android.hardware.biometrics.BiometricSourceType
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -30,7 +31,6 @@
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -70,11 +70,6 @@
             )
     }
 
-    @After
-    fun tearDown() {
-        //        verify(keyguardUpdateMonitor).removeCallback(updateMonitorCallback.value)
-    }
-
     @Test
     fun isLockedOut_whenFingerprintLockoutStateChanges_emitsNewValue() =
         testScope.runTest {
@@ -129,29 +124,55 @@
     }
 
     @Test
-    fun enabledFingerprintTypeProvidesTheCorrectOutput() =
+    fun enabledFingerprintTypeProvidesTheCorrectOutputForSpfs() =
         testScope.runTest {
             whenever(authController.isSfpsSupported).thenReturn(true)
             whenever(authController.isUdfpsSupported).thenReturn(false)
             whenever(authController.isRearFpsSupported).thenReturn(false)
 
-            assertThat(underTest.availableFpSensorType).isEqualTo(BiometricType.SIDE_FINGERPRINT)
+            val availableFpSensorType = collectLastValue(underTest.availableFpSensorType)
+            assertThat(availableFpSensorType()).isEqualTo(BiometricType.SIDE_FINGERPRINT)
+        }
 
+    @Test
+    fun enabledFingerprintTypeProvidesTheCorrectOutputForUdfps() =
+        testScope.runTest {
             whenever(authController.isSfpsSupported).thenReturn(false)
             whenever(authController.isUdfpsSupported).thenReturn(true)
             whenever(authController.isRearFpsSupported).thenReturn(false)
+            val availableFpSensorType = collectLastValue(underTest.availableFpSensorType)
+            assertThat(availableFpSensorType()).isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT)
+        }
 
-            assertThat(underTest.availableFpSensorType)
-                .isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT)
-
+    @Test
+    fun enabledFingerprintTypeProvidesTheCorrectOutputForRearFps() =
+        testScope.runTest {
             whenever(authController.isSfpsSupported).thenReturn(false)
             whenever(authController.isUdfpsSupported).thenReturn(false)
             whenever(authController.isRearFpsSupported).thenReturn(true)
 
-            assertThat(underTest.availableFpSensorType).isEqualTo(BiometricType.REAR_FINGERPRINT)
+            val availableFpSensorType = collectLastValue(underTest.availableFpSensorType)
 
+            assertThat(availableFpSensorType()).isEqualTo(BiometricType.REAR_FINGERPRINT)
+        }
+
+    @Test
+    fun enabledFingerprintTypeProvidesTheCorrectOutputAfterAllAuthenticatorsAreRegistered() =
+        testScope.runTest {
+            whenever(authController.isSfpsSupported).thenReturn(false)
+            whenever(authController.isUdfpsSupported).thenReturn(false)
             whenever(authController.isRearFpsSupported).thenReturn(false)
+            whenever(authController.areAllFingerprintAuthenticatorsRegistered()).thenReturn(false)
 
-            assertThat(underTest.availableFpSensorType).isNull()
+            val availableFpSensorType = collectLastValue(underTest.availableFpSensorType)
+            runCurrent()
+
+            val callback = ArgumentCaptor.forClass(AuthController.Callback::class.java)
+            verify(authController).addCallback(callback.capture())
+            assertThat(availableFpSensorType()).isNull()
+
+            whenever(authController.isUdfpsSupported).thenReturn(true)
+            callback.value.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT)
+            assertThat(availableFpSensorType()).isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT)
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManagerTest.kt
deleted file mode 100644
index d55370b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManagerTest.kt
+++ /dev/null
@@ -1,427 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.data.repository
-
-import android.app.StatusBarManager.SESSION_KEYGUARD
-import android.content.pm.UserInfo
-import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_CANCELED
-import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT
-import android.hardware.biometrics.ComponentInfoInternal
-import android.hardware.face.FaceAuthenticateOptions
-import android.hardware.face.FaceManager
-import android.hardware.face.FaceSensorProperties
-import android.hardware.face.FaceSensorPropertiesInternal
-import android.os.CancellationSignal
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.InstanceId.fakeInstanceId
-import com.android.internal.logging.UiEventLogger
-import com.android.keyguard.FaceAuthUiEvent
-import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
-import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.FlowValue
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.dump.logcatLogBuffer
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
-import com.android.systemui.log.FaceAuthenticationLogger
-import com.android.systemui.log.SessionTracker
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import java.io.PrintWriter
-import java.io.StringWriter
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.advanceTimeBy
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.isNull
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardFaceAuthManagerTest : SysuiTestCase() {
-    private lateinit var underTest: KeyguardFaceAuthManagerImpl
-
-    @Mock private lateinit var faceManager: FaceManager
-    @Mock private lateinit var bypassController: KeyguardBypassController
-    @Mock private lateinit var sessionTracker: SessionTracker
-    @Mock private lateinit var uiEventLogger: UiEventLogger
-    @Mock private lateinit var dumpManager: DumpManager
-
-    @Captor
-    private lateinit var authenticationCallback: ArgumentCaptor<FaceManager.AuthenticationCallback>
-    @Captor
-    private lateinit var detectionCallback: ArgumentCaptor<FaceManager.FaceDetectionCallback>
-    @Captor private lateinit var cancellationSignal: ArgumentCaptor<CancellationSignal>
-    @Captor
-    private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback>
-    private lateinit var testDispatcher: TestDispatcher
-
-    private lateinit var testScope: TestScope
-    private lateinit var fakeUserRepository: FakeUserRepository
-    private lateinit var authStatus: FlowValue<AuthenticationStatus?>
-    private lateinit var detectStatus: FlowValue<DetectionStatus?>
-    private lateinit var authRunning: FlowValue<Boolean?>
-    private lateinit var lockedOut: FlowValue<Boolean?>
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        fakeUserRepository = FakeUserRepository()
-        fakeUserRepository.setUserInfos(listOf(currentUser))
-        testDispatcher = StandardTestDispatcher()
-        testScope = TestScope(testDispatcher)
-        whenever(sessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(keyguardSessionId)
-        whenever(bypassController.bypassEnabled).thenReturn(true)
-        underTest = createFaceAuthManagerImpl(faceManager)
-    }
-
-    private fun createFaceAuthManagerImpl(
-        fmOverride: FaceManager? = faceManager,
-        bypassControllerOverride: KeyguardBypassController? = bypassController
-    ) =
-        KeyguardFaceAuthManagerImpl(
-            mContext,
-            fmOverride,
-            fakeUserRepository,
-            bypassControllerOverride,
-            testScope.backgroundScope,
-            testDispatcher,
-            sessionTracker,
-            uiEventLogger,
-            FaceAuthenticationLogger(logcatLogBuffer("KeyguardFaceAuthManagerLog")),
-            dumpManager,
-        )
-
-    @Test
-    fun faceAuthRunsAndProvidesAuthStatusUpdates() =
-        testScope.runTest {
-            testSetup(this)
-
-            FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER.extraInfo = 10
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
-            faceAuthenticateIsCalled()
-            uiEventIsLogged(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
-
-            assertThat(authRunning()).isTrue()
-
-            val successResult = successResult()
-            authenticationCallback.value.onAuthenticationSucceeded(successResult)
-
-            assertThat(authStatus()).isEqualTo(SuccessAuthenticationStatus(successResult))
-
-            assertThat(authRunning()).isFalse()
-        }
-
-    private fun uiEventIsLogged(faceAuthUiEvent: FaceAuthUiEvent) {
-        verify(uiEventLogger)
-            .logWithInstanceIdAndPosition(
-                faceAuthUiEvent,
-                0,
-                null,
-                keyguardSessionId,
-                faceAuthUiEvent.extraInfo
-            )
-    }
-
-    @Test
-    fun faceAuthDoesNotRunWhileItIsAlreadyRunning() =
-        testScope.runTest {
-            testSetup(this)
-
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
-            faceAuthenticateIsCalled()
-            clearInvocations(faceManager)
-            clearInvocations(uiEventLogger)
-
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
-            verifyNoMoreInteractions(faceManager)
-            verifyNoMoreInteractions(uiEventLogger)
-        }
-
-    @Test
-    fun faceLockoutStatusIsPropagated() =
-        testScope.runTest {
-            testSetup(this)
-            verify(faceManager).addLockoutResetCallback(faceLockoutResetCallback.capture())
-
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
-            faceAuthenticateIsCalled()
-
-            authenticationCallback.value.onAuthenticationError(
-                FACE_ERROR_LOCKOUT_PERMANENT,
-                "face locked out"
-            )
-
-            assertThat(lockedOut()).isTrue()
-
-            faceLockoutResetCallback.value.onLockoutReset(0)
-            assertThat(lockedOut()).isFalse()
-        }
-
-    @Test
-    fun faceDetectionSupportIsTheCorrectValue() =
-        testScope.runTest {
-            assertThat(createFaceAuthManagerImpl(fmOverride = null).isDetectionSupported).isFalse()
-
-            whenever(faceManager.sensorPropertiesInternal).thenReturn(null)
-            assertThat(createFaceAuthManagerImpl().isDetectionSupported).isFalse()
-
-            whenever(faceManager.sensorPropertiesInternal).thenReturn(listOf())
-            assertThat(createFaceAuthManagerImpl().isDetectionSupported).isFalse()
-
-            whenever(faceManager.sensorPropertiesInternal)
-                .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = false)))
-            assertThat(createFaceAuthManagerImpl().isDetectionSupported).isFalse()
-
-            whenever(faceManager.sensorPropertiesInternal)
-                .thenReturn(
-                    listOf(
-                        createFaceSensorProperties(supportsFaceDetection = false),
-                        createFaceSensorProperties(supportsFaceDetection = true)
-                    )
-                )
-            assertThat(createFaceAuthManagerImpl().isDetectionSupported).isFalse()
-
-            whenever(faceManager.sensorPropertiesInternal)
-                .thenReturn(
-                    listOf(
-                        createFaceSensorProperties(supportsFaceDetection = true),
-                        createFaceSensorProperties(supportsFaceDetection = false)
-                    )
-                )
-            assertThat(createFaceAuthManagerImpl().isDetectionSupported).isTrue()
-        }
-
-    @Test
-    fun cancelStopsFaceAuthentication() =
-        testScope.runTest {
-            testSetup(this)
-
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
-            faceAuthenticateIsCalled()
-
-            var wasAuthCancelled = false
-            cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
-
-            underTest.cancel()
-            assertThat(wasAuthCancelled).isTrue()
-            assertThat(authRunning()).isFalse()
-        }
-
-    @Test
-    fun cancelInvokedWithoutFaceAuthRunningIsANoop() = testScope.runTest { underTest.cancel() }
-
-    @Test
-    fun faceDetectionRunsAndPropagatesDetectionStatus() =
-        testScope.runTest {
-            whenever(faceManager.sensorPropertiesInternal)
-                .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
-            underTest = createFaceAuthManagerImpl()
-            testSetup(this)
-
-            underTest.detect()
-            faceDetectIsCalled()
-
-            detectionCallback.value.onFaceDetected(1, 1, true)
-
-            assertThat(detectStatus()).isEqualTo(DetectionStatus(1, 1, true))
-        }
-
-    @Test
-    fun faceDetectDoesNotRunIfDetectionIsNotSupported() =
-        testScope.runTest {
-            whenever(faceManager.sensorPropertiesInternal)
-                .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = false)))
-            underTest = createFaceAuthManagerImpl()
-            testSetup(this)
-            clearInvocations(faceManager)
-
-            underTest.detect()
-
-            verify(faceManager, never()).detectFace(any(), any(), any())
-        }
-
-    @Test
-    fun faceAuthShouldWaitAndRunIfTriggeredWhileCancelling() =
-        testScope.runTest {
-            testSetup(this)
-
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
-            faceAuthenticateIsCalled()
-
-            // Enter cancelling state
-            underTest.cancel()
-            clearInvocations(faceManager)
-
-            // Auth is while cancelling.
-            underTest.authenticate(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
-            // Auth is not started
-            verifyNoMoreInteractions(faceManager)
-
-            // Auth is done cancelling.
-            authenticationCallback.value.onAuthenticationError(
-                FACE_ERROR_CANCELED,
-                "First auth attempt cancellation completed"
-            )
-            assertThat(authStatus())
-                .isEqualTo(
-                    ErrorAuthenticationStatus(
-                        FACE_ERROR_CANCELED,
-                        "First auth attempt cancellation completed"
-                    )
-                )
-
-            faceAuthenticateIsCalled()
-            uiEventIsLogged(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
-        }
-
-    @Test
-    fun faceAuthAutoCancelsAfterDefaultCancellationTimeout() =
-        testScope.runTest {
-            testSetup(this)
-
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
-            faceAuthenticateIsCalled()
-
-            clearInvocations(faceManager)
-            underTest.cancel()
-            advanceTimeBy(KeyguardFaceAuthManagerImpl.DEFAULT_CANCEL_SIGNAL_TIMEOUT + 1)
-
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
-            faceAuthenticateIsCalled()
-        }
-
-    @Test
-    fun faceHelpMessagesAreIgnoredBasedOnConfig() =
-        testScope.runTest {
-            overrideResource(
-                R.array.config_face_acquire_device_entry_ignorelist,
-                intArrayOf(10, 11)
-            )
-            underTest = createFaceAuthManagerImpl()
-            testSetup(this)
-
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
-            faceAuthenticateIsCalled()
-
-            authenticationCallback.value.onAuthenticationHelp(9, "help msg")
-            authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg")
-            authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg")
-
-            assertThat(authStatus()).isEqualTo(HelpAuthenticationStatus(9, "help msg"))
-        }
-
-    @Test
-    fun dumpDoesNotErrorOutWhenFaceManagerOrBypassControllerIsNull() =
-        testScope.runTest {
-            fakeUserRepository.setSelectedUserInfo(currentUser)
-            underTest.dump(PrintWriter(StringWriter()), emptyArray())
-
-            underTest =
-                createFaceAuthManagerImpl(fmOverride = null, bypassControllerOverride = null)
-            fakeUserRepository.setSelectedUserInfo(currentUser)
-
-            underTest.dump(PrintWriter(StringWriter()), emptyArray())
-        }
-
-    private suspend fun testSetup(testScope: TestScope) {
-        with(testScope) {
-            authStatus = collectLastValue(underTest.authenticationStatus)
-            detectStatus = collectLastValue(underTest.detectionStatus)
-            authRunning = collectLastValue(underTest.isAuthRunning)
-            lockedOut = collectLastValue(underTest.isLockedOut)
-            fakeUserRepository.setSelectedUserInfo(currentUser)
-        }
-    }
-
-    private fun successResult() = FaceManager.AuthenticationResult(null, null, currentUserId, false)
-
-    private fun faceDetectIsCalled() {
-        verify(faceManager)
-            .detectFace(
-                cancellationSignal.capture(),
-                detectionCallback.capture(),
-                eq(FaceAuthenticateOptions.Builder().setUserId(currentUserId).build())
-            )
-    }
-
-    private fun faceAuthenticateIsCalled() {
-        verify(faceManager)
-            .authenticate(
-                isNull(),
-                cancellationSignal.capture(),
-                authenticationCallback.capture(),
-                isNull(),
-                eq(FaceAuthenticateOptions.Builder().setUserId(currentUserId).build())
-            )
-    }
-
-    private fun createFaceSensorProperties(
-        supportsFaceDetection: Boolean
-    ): FaceSensorPropertiesInternal {
-        val componentInfo =
-            listOf(
-                ComponentInfoInternal(
-                    "faceSensor" /* componentId */,
-                    "vendor/model/revision" /* hardwareVersion */,
-                    "1.01" /* firmwareVersion */,
-                    "00000001" /* serialNumber */,
-                    "" /* softwareVersion */
-                )
-            )
-        return FaceSensorPropertiesInternal(
-            0 /* id */,
-            FaceSensorProperties.STRENGTH_STRONG,
-            1 /* maxTemplatesAllowed */,
-            componentInfo,
-            FaceSensorProperties.TYPE_UNKNOWN,
-            supportsFaceDetection /* supportsFaceDetection */,
-            true /* supportsSelfIllumination */,
-            false /* resetLockoutRequiresChallenge */
-        )
-    }
-
-    companion object {
-        const val currentUserId = 1
-        val keyguardSessionId = fakeInstanceId(10)!!
-        val currentUser = UserInfo(currentUserId, "test user", 0)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 7f30162..68d694a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -18,18 +18,16 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.app.StatusBarManager
-import android.content.Context
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
-import com.android.systemui.settings.DisplayTracker
-import com.android.systemui.statusbar.CommandQueue
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.onCompletion
 import kotlinx.coroutines.test.TestScope
@@ -38,7 +36,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -56,7 +53,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
-        commandQueue = FakeCommandQueue(mock(Context::class.java), mock(DisplayTracker::class.java))
+        commandQueue = FakeCommandQueue()
         testScope = TestScope()
         repository = FakeKeyguardRepository()
         bouncerRepository = FakeKeyguardBouncerRepository()
@@ -174,22 +171,3 @@
             assertThat(secureCameraActive()).isFalse()
         }
 }
-
-class FakeCommandQueue(val context: Context, val displayTracker: DisplayTracker) :
-    CommandQueue(context, displayTracker) {
-    private val callbacks = mutableListOf<Callbacks>()
-
-    override fun addCallback(callback: Callbacks) {
-        callbacks.add(callback)
-    }
-
-    override fun removeCallback(callback: Callbacks) {
-        callbacks.remove(callback)
-    }
-
-    fun doForEachCallback(func: (callback: Callbacks) -> Unit) {
-        callbacks.forEach { func(it) }
-    }
-
-    fun callbackCount(): Int = callbacks.size
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
index aa54a1c..447b333 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -37,6 +38,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -64,6 +66,8 @@
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock
     private KeyguardStateController mKeyguardStateController;
+    @Mock
+    private UiEventLogger mUiEventLogger;
 
     @Captor
     ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
@@ -87,7 +91,8 @@
                 mStatusBarService,
                 mAuthController,
                 mKeyguardUpdateMonitor,
-                mKeyguardStateController
+                mKeyguardStateController,
+                mUiEventLogger
         );
     }
 
@@ -238,6 +243,62 @@
                 eq(SESSION_KEYGUARD), any(InstanceId.class));
     }
 
+    @Test
+    public void uiEventLoggedOnEndSessionWhenDeviceStartsSleeping() throws RemoteException {
+        // GIVEN session tracker start
+        mSessionTracker.start();
+        captureKeyguardUpdateMonitorCallback();
+        captureKeyguardStateControllerCallback();
+
+        // GIVEN keyguard becomes visible (ie: from lockdown), so there's a valid keyguard
+        // session running
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        mKeyguardStateCallback.onKeyguardShowingChanged();
+
+        // WHEN device starts going to sleep
+        mKeyguardUpdateMonitorCallback.onStartedGoingToSleep(0);
+
+        // THEN UI event is logged
+        verify(mUiEventLogger).log(
+                eq(SessionTracker.SessionUiEvent.KEYGUARD_SESSION_END_GOING_TO_SLEEP),
+                any(InstanceId.class));
+    }
+
+    @Test
+    public void noUiEventLoggedOnEndSessionWhenDeviceStartsSleepingWithoutStartSession()
+            throws RemoteException {
+        // GIVEN session tracker start without any valid sessions
+        mSessionTracker.start();
+        captureKeyguardUpdateMonitorCallback();
+
+        // WHEN device starts going to sleep when there was no started sessions
+        mKeyguardUpdateMonitorCallback.onStartedGoingToSleep(0);
+
+        // THEN UI event is never logged
+        verify(mUiEventLogger, never()).log(
+                eq(SessionTracker.SessionUiEvent.KEYGUARD_SESSION_END_GOING_TO_SLEEP),
+                any(InstanceId.class));
+    }
+
+    @Test
+    public void uiEventLoggedOnEndSessionWhenKeyguardGoingAway() throws RemoteException {
+        // GIVEN session tracker started w/o any sessions
+        mSessionTracker.start();
+        captureKeyguardUpdateMonitorCallback();
+        captureKeyguardStateControllerCallback();
+
+        // WHEN keyguard was showing and now it's not
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        mKeyguardStateCallback.onKeyguardShowingChanged();
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+        mKeyguardStateCallback.onKeyguardShowingChanged();
+
+        // THEN UI event is logged
+        verify(mUiEventLogger).log(
+                eq(SessionTracker.SessionUiEvent.KEYGUARD_SESSION_END_KEYGUARD_GOING_AWAY),
+                any(InstanceId.class));
+    }
+
     void captureKeyguardUpdateMonitorCallback() {
         verify(mKeyguardUpdateMonitor).registerCallback(
                 mKeyguardUpdateMonitorCallbackCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
index acde887..19f9960 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -31,6 +33,8 @@
 import com.android.systemui.multishade.data.repository.MultiShadeRepository
 import com.android.systemui.multishade.shared.model.ProxiedInputModel
 import com.android.systemui.multishade.shared.model.ShadeId
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -42,6 +46,10 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -57,9 +65,11 @@
     private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop
     private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
     private lateinit var falsingManager: FalsingManagerFake
+    @Mock private lateinit var shadeController: ShadeController
 
     @Before
     fun setUp() {
+        MockitoAnnotations.initMocks(this)
         testScope = TestScope()
         motionEvents = mutableSetOf()
 
@@ -75,18 +85,23 @@
                 repository = repository,
                 inputProxy = inputProxy,
             )
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.DUAL_SHADE, true)
         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
         falsingManager = FalsingManagerFake()
+
         underTest =
             MultiShadeMotionEventInteractor(
                 applicationContext = context,
                 applicationScope = testScope.backgroundScope,
                 multiShadeInteractor = interactor,
+                featureFlags = featureFlags,
                 keyguardTransitionInteractor =
                     KeyguardTransitionInteractor(
                         repository = keyguardTransitionRepository,
                     ),
                 falsingManager = falsingManager,
+                shadeController = shadeController,
             )
     }
 
@@ -96,6 +111,39 @@
     }
 
     @Test
+    fun listenForIsAnyShadeExpanded_expanded_makesWindowViewVisible() =
+        testScope.runTest {
+            whenever(shadeController.isKeyguard).thenReturn(false)
+            repository.setExpansion(ShadeId.LEFT, 0.1f)
+            val expanded by collectLastValue(interactor.isAnyShadeExpanded)
+            assertThat(expanded).isTrue()
+
+            verify(shadeController).makeExpandedVisible(anyBoolean())
+        }
+
+    @Test
+    fun listenForIsAnyShadeExpanded_collapsed_makesWindowViewInvisible() =
+        testScope.runTest {
+            whenever(shadeController.isKeyguard).thenReturn(false)
+            repository.setForceCollapseAll(true)
+            val expanded by collectLastValue(interactor.isAnyShadeExpanded)
+            assertThat(expanded).isFalse()
+
+            verify(shadeController).makeExpandedInvisible()
+        }
+
+    @Test
+    fun listenForIsAnyShadeExpanded_collapsedOnKeyguard_makesWindowViewVisible() =
+        testScope.runTest {
+            whenever(shadeController.isKeyguard).thenReturn(true)
+            repository.setForceCollapseAll(true)
+            val expanded by collectLastValue(interactor.isAnyShadeExpanded)
+            assertThat(expanded).isFalse()
+
+            verify(shadeController).makeExpandedVisible(anyBoolean())
+        }
+
+    @Test
     fun shouldIntercept_initialDown_returnsFalse() =
         testScope.runTest {
             assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index fbe089a..ba29ca5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -369,6 +369,39 @@
 
         verifyZeroInteractions(context, bubbles, eventLogger)
     }
+
+    @Test
+    fun showNoteTask_keyboardShortcut_shouldStartActivity() {
+        val expectedInfo =
+                NOTE_TASK_INFO.copy(
+                        entryPoint = NoteTaskEntryPoint.KEYBOARD_SHORTCUT,
+                        isKeyguardLocked = true,
+                )
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
+        whenever(resolver.resolveInfo(any(), any())).thenReturn(expectedInfo)
+
+        createNoteTaskController()
+                .showNoteTask(
+                        entryPoint = expectedInfo.entryPoint!!,
+                )
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val userCaptor = argumentCaptor<UserHandle>()
+        verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
+            assertThat(intent.`package`).isEqualTo(NOTE_TASK_PACKAGE_NAME)
+            assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
+            assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
+                    .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
+            assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
+                    .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
+            assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, true)).isFalse()
+        }
+        assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
+        verify(eventLogger).logNoteTaskOpened(expectedInfo)
+        verifyZeroInteractions(bubbles)
+    }
     // endregion
 
     // region setNoteTaskShortcutEnabled
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index cd67e8d..ec4daee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -98,14 +98,24 @@
     // region handleSystemKey
     @Test
     fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() {
-        createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL)
+        createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent(KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL))
 
         verify(controller).showNoteTask(entryPoint = NoteTaskEntryPoint.TAIL_BUTTON)
     }
 
     @Test
+    fun handleSystemKey_receiveKeyboardShortcut_shouldShowNoteTask() {
+        createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_N, 0, KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON))
+
+        verify(controller).showNoteTask(entryPoint = NoteTaskEntryPoint.KEYBOARD_SHORTCUT)
+    }
+    
+    @Test
     fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
-        createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
+        createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent(KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_UNKNOWN))
 
         verifyZeroInteractions(controller)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
index 6f54f62..f5a3bec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -111,8 +111,6 @@
         MockitoAnnotations.initMocks(this);
 
         mDeviceConfigProxyFake = new DeviceConfigProxyFake();
-        mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED, "true", false);
         mSystemClock = new FakeSystemClock();
         mMainExecutor = new FakeExecutor(mSystemClock);
         mBackgroundExecutor = new FakeExecutor(mSystemClock);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 46af89e..9ca7a85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -40,6 +40,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
 import com.android.systemui.settings.UserTracker;
 
 import org.junit.After;
@@ -64,6 +65,8 @@
     private QSHost mQSHost;
     @Mock
     private Context mMockContext;
+    @Mock
+    private CustomTileAddedRepository mCustomTileAddedRepository;
 
     private HandlerThread mThread;
     private Handler mHandler;
@@ -86,8 +89,9 @@
 
         mComponentName = new ComponentName(mContext, TileServiceManagerTest.class);
         when(mTileLifecycle.getComponent()).thenReturn(mComponentName);
+
         mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker,
-                mTileLifecycle);
+                mCustomTileAddedRepository, mTileLifecycle);
     }
 
     @After
@@ -98,28 +102,34 @@
 
     @Test
     public void testSetTileAddedIfNotAdded() {
-        when(mQSHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
+        when(mCustomTileAddedRepository.isTileAdded(eq(mComponentName), anyInt()))
+                .thenReturn(false);
         mTileServiceManager.startLifecycleManagerAndAddTile();
 
-        verify(mQSHost).setTileAdded(mComponentName, mUserTracker.getUserId(), true);
+        verify(mCustomTileAddedRepository)
+                .setTileAdded(mComponentName, mUserTracker.getUserId(), true);
     }
 
     @Test
     public void testNotSetTileAddedIfAdded() {
-        when(mQSHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(true);
+        when(mCustomTileAddedRepository.isTileAdded(eq(mComponentName), anyInt()))
+                .thenReturn(true);
         mTileServiceManager.startLifecycleManagerAndAddTile();
 
-        verify(mQSHost, never()).setTileAdded(eq(mComponentName), anyInt(), eq(true));
+        verify(mCustomTileAddedRepository, never())
+                .setTileAdded(eq(mComponentName), anyInt(), eq(true));
     }
 
     @Test
     public void testSetTileAddedCorrectUser() {
         int user = 10;
         when(mUserTracker.getUserId()).thenReturn(user);
-        when(mQSHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
+        when(mCustomTileAddedRepository.isTileAdded(eq(mComponentName), anyInt()))
+                .thenReturn(false);
         mTileServiceManager.startLifecycleManagerAndAddTile();
 
-        verify(mQSHost).setTileAdded(mComponentName, user, true);
+        verify(mCustomTileAddedRepository)
+                .setTileAdded(mComponentName, user, true);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index fb93367..12b5656 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -42,6 +42,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
@@ -98,6 +99,8 @@
     private PanelInteractor mPanelInteractor;
     @Captor
     private ArgumentCaptor<CommandQueue.Callbacks> mCallbacksArgumentCaptor;
+    @Mock
+    private CustomTileAddedRepository mCustomTileAddedRepository;
 
     @Before
     public void setUp() throws Exception {
@@ -115,7 +118,7 @@
 
         mTileService = new TestTileServices(mQSHost, provider, mBroadcastDispatcher,
                 mUserTracker, mKeyguardStateController, mCommandQueue, mStatusBarIconController,
-                mPanelInteractor);
+                mPanelInteractor, mCustomTileAddedRepository);
     }
 
     @After
@@ -293,9 +296,11 @@
         TestTileServices(QSHost host, Provider<Handler> handlerProvider,
                 BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
                 KeyguardStateController keyguardStateController, CommandQueue commandQueue,
-                StatusBarIconController statusBarIconController, PanelInteractor panelInteractor) {
+                StatusBarIconController statusBarIconController, PanelInteractor panelInteractor,
+                CustomTileAddedRepository customTileAddedRepository) {
             super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController,
-                    commandQueue, statusBarIconController, panelInteractor);
+                    commandQueue, statusBarIconController, panelInteractor,
+                    customTileAddedRepository);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 0b9fbd9..59f0d96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -259,7 +259,6 @@
         val securityController = FakeSecurityController()
         val fgsManagerController =
             FakeFgsManagerController(
-                isAvailable = true,
                 showFooterDot = false,
                 numRunningPackages = 0,
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
new file mode 100644
index 0000000..d7ab903
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.content.ComponentName
+import android.content.SharedPreferences
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.util.FakeSharedPreferences
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CustomTileAddedSharedPreferencesRepositoryTest : SysuiTestCase() {
+
+    private lateinit var underTest: CustomTileAddedSharedPrefsRepository
+
+    @Test
+    fun setTileAdded_inSharedPreferences() {
+        val userId = 0
+        val sharedPrefs = FakeSharedPreferences()
+        val userFileManager = FakeUserFileManager(mapOf(userId to sharedPrefs))
+
+        underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
+
+        underTest.setTileAdded(TEST_COMPONENT, userId, added = true)
+        assertThat(sharedPrefs.getForComponentName(TEST_COMPONENT)).isTrue()
+
+        underTest.setTileAdded(TEST_COMPONENT, userId, added = false)
+        assertThat(sharedPrefs.getForComponentName(TEST_COMPONENT)).isFalse()
+    }
+
+    @Test
+    fun setTileAdded_differentComponents() {
+        val userId = 0
+        val sharedPrefs = FakeSharedPreferences()
+        val userFileManager = FakeUserFileManager(mapOf(userId to sharedPrefs))
+
+        underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
+
+        underTest.setTileAdded(TEST_COMPONENT, userId, added = true)
+
+        assertThat(sharedPrefs.getForComponentName(TEST_COMPONENT)).isTrue()
+        assertThat(sharedPrefs.getForComponentName(OTHER_TEST_COMPONENT)).isFalse()
+    }
+
+    @Test
+    fun setTileAdded_differentUsers() {
+        val sharedPrefs0 = FakeSharedPreferences()
+        val sharedPrefs1 = FakeSharedPreferences()
+        val userFileManager = FakeUserFileManager(mapOf(0 to sharedPrefs0, 1 to sharedPrefs1))
+
+        underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
+
+        underTest.setTileAdded(TEST_COMPONENT, userId = 1, added = true)
+
+        assertThat(sharedPrefs0.getForComponentName(TEST_COMPONENT)).isFalse()
+        assertThat(sharedPrefs1.getForComponentName(TEST_COMPONENT)).isTrue()
+    }
+
+    @Test
+    fun isTileAdded_fromSharedPreferences() {
+        val userId = 0
+        val sharedPrefs = FakeSharedPreferences()
+        val userFileManager = FakeUserFileManager(mapOf(userId to sharedPrefs))
+
+        underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
+
+        assertThat(underTest.isTileAdded(TEST_COMPONENT, userId)).isFalse()
+
+        sharedPrefs.setForComponentName(TEST_COMPONENT, true)
+        assertThat(underTest.isTileAdded(TEST_COMPONENT, userId)).isTrue()
+
+        sharedPrefs.setForComponentName(TEST_COMPONENT, false)
+        assertThat(underTest.isTileAdded(TEST_COMPONENT, userId)).isFalse()
+    }
+
+    @Test
+    fun isTileAdded_differentComponents() {
+        val userId = 0
+        val sharedPrefs = FakeSharedPreferences()
+        val userFileManager = FakeUserFileManager(mapOf(userId to sharedPrefs))
+
+        underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
+
+        sharedPrefs.setForComponentName(OTHER_TEST_COMPONENT, true)
+
+        assertThat(underTest.isTileAdded(TEST_COMPONENT, userId)).isFalse()
+        assertThat(underTest.isTileAdded(OTHER_TEST_COMPONENT, userId)).isTrue()
+    }
+
+    @Test
+    fun isTileAdded_differentUsers() {
+        val sharedPrefs0 = FakeSharedPreferences()
+        val sharedPrefs1 = FakeSharedPreferences()
+        val userFileManager = FakeUserFileManager(mapOf(0 to sharedPrefs0, 1 to sharedPrefs1))
+
+        underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
+
+        sharedPrefs1.setForComponentName(TEST_COMPONENT, true)
+
+        assertThat(underTest.isTileAdded(TEST_COMPONENT, userId = 0)).isFalse()
+        assertThat(underTest.isTileAdded(TEST_COMPONENT, userId = 1)).isTrue()
+    }
+
+    private fun SharedPreferences.getForComponentName(componentName: ComponentName): Boolean {
+        return getBoolean(componentName.flattenToString(), false)
+    }
+
+    private fun SharedPreferences.setForComponentName(
+        componentName: ComponentName,
+        value: Boolean
+    ) {
+        edit().putBoolean(componentName.flattenToString(), value).commit()
+    }
+
+    companion object {
+        private val TEST_COMPONENT = ComponentName("pkg", "cls")
+        private val OTHER_TEST_COMPONENT = ComponentName("pkg", "other")
+    }
+}
+
+private const val FILE_NAME = "tiles_prefs"
+
+private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
+    UserFileManager {
+    override fun getFile(fileName: String, userId: Int): File {
+        throw UnsupportedOperationException()
+    }
+
+    override fun getSharedPreferences(fileName: String, mode: Int, userId: Int): SharedPreferences {
+        if (fileName != FILE_NAME) {
+            throw IllegalArgumentException("Preference files must be $FILE_NAME")
+        }
+        return sharedPrefs.getValue(userId)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index d3ec1dd..28aeba4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -317,6 +317,26 @@
     }
 
     @Test
+    fun testDisableByPolicyThenRemoved_changesColor() {
+        val stateActive = QSTile.State()
+        stateActive.state = Tile.STATE_ACTIVE
+
+        val stateDisabledByPolicy = stateActive.copy()
+        stateDisabledByPolicy.disabledByPolicy = true
+
+        tileView.changeState(stateActive)
+        val activeColors = tileView.getCurrentColors()
+
+        tileView.changeState(stateDisabledByPolicy)
+        // It has unavailable colors
+        assertThat(tileView.getCurrentColors()).isNotEqualTo(activeColors)
+
+        // When we get back to not disabled by policy tile, it should go back to active colors
+        tileView.changeState(stateActive)
+        assertThat(tileView.getCurrentColors()).containsExactlyElementsIn(activeColors)
+    }
+
+    @Test
     fun testDisabledByPolicy_secondaryLabelText() {
         val testA11yLabel = "TEST_LABEL"
         context.orCreateTestableResources
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index eb7b481..8cb5d31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -19,6 +19,7 @@
 import android.content.ComponentName
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
+import android.os.PowerManager
 import android.testing.AndroidTestingRunner
 import android.testing.TestableContext
 import android.testing.TestableLooper
@@ -30,6 +31,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
 import com.android.systemui.keyguard.ScreenLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.model.SysUiState
 import com.android.systemui.navigationbar.NavigationBarController
 import com.android.systemui.navigationbar.NavigationModeController
@@ -37,16 +39,17 @@
 import com.android.systemui.settings.FakeDisplayTracker
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.recents.IOverviewProxy
-import com.android.systemui.shared.system.QuickStepContract.SCREEN_STATE_OFF
-import com.android.systemui.shared.system.QuickStepContract.SCREEN_STATE_ON
-import com.android.systemui.shared.system.QuickStepContract.SCREEN_STATE_TURNING_OFF
-import com.android.systemui.shared.system.QuickStepContract.SCREEN_STATE_TURNING_ON
-import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_STATE_MASK
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK
+import com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_ASLEEP
+import com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_AWAKE
+import com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_GOING_TO_SLEEP
+import com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_WAKING
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.android.wm.shell.sysui.ShellInterface
 import com.google.common.util.concurrent.MoreExecutors
 import dagger.Lazy
@@ -60,6 +63,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.intThat
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
@@ -75,8 +79,11 @@
     private lateinit var subject: OverviewProxyService
     private val dumpManager = DumpManager()
     private val displayTracker = FakeDisplayTracker(mContext)
+    private val fakeSystemClock = FakeSystemClock()
     private val sysUiState = SysUiState(displayTracker)
     private val screenLifecycle = ScreenLifecycle(dumpManager)
+    private val wakefulnessLifecycle =
+        WakefulnessLifecycle(mContext, null, fakeSystemClock, dumpManager)
 
     @Mock private lateinit var overviewProxy: IOverviewProxy.Stub
     @Mock private lateinit var packageManager: PackageManager
@@ -130,6 +137,7 @@
                 sysUiState,
                 userTracker,
                 screenLifecycle,
+                wakefulnessLifecycle,
                 uiEventLogger,
                 displayTracker,
                 sysuiUnlockAnimationController,
@@ -145,42 +153,48 @@
     }
 
     @Test
-    fun `ScreenLifecycle - screenTurnedOn triggers SysUI state flag changes `() {
-        screenLifecycle.dispatchScreenTurnedOn()
+    fun `WakefulnessLifecycle - dispatchFinishedWakingUp sets SysUI flag to AWAKE`() {
+        // WakefulnessLifecycle is initialized to AWAKE initially, and won't emit a noop.
+        wakefulnessLifecycle.dispatchFinishedGoingToSleep()
+        clearInvocations(overviewProxy)
+
+        wakefulnessLifecycle.dispatchFinishedWakingUp()
 
         verify(overviewProxy)
             .onSystemUiStateChanged(
-                intThat { it and SYSUI_STATE_SCREEN_STATE_MASK == SCREEN_STATE_ON }
+                intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE }
             )
     }
 
     @Test
-    fun `ScreenLifecycle - screenTurningOn triggers SysUI state flag changes `() {
-        screenLifecycle.dispatchScreenTurningOn()
+    fun `WakefulnessLifecycle - dispatchStartedWakingUp sets SysUI flag to WAKING`() {
+        wakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN)
 
         verify(overviewProxy)
             .onSystemUiStateChanged(
-                intThat { it and SYSUI_STATE_SCREEN_STATE_MASK == SCREEN_STATE_TURNING_ON }
+                intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING }
             )
     }
 
     @Test
-    fun `ScreenLifecycle - screenTurnedOff triggers SysUI state flag changes `() {
-        screenLifecycle.dispatchScreenTurnedOff()
+    fun `WakefulnessLifecycle - dispatchFinishedGoingToSleep sets SysUI flag to ASLEEP`() {
+        wakefulnessLifecycle.dispatchFinishedGoingToSleep()
 
         verify(overviewProxy)
             .onSystemUiStateChanged(
-                intThat { it and SYSUI_STATE_SCREEN_STATE_MASK == SCREEN_STATE_OFF }
+                intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP }
             )
     }
 
     @Test
-    fun `ScreenLifecycle - screenTurningOff triggers SysUI state flag changes `() {
-        screenLifecycle.dispatchScreenTurningOff()
+    fun `WakefulnessLifecycle - dispatchStartedGoingToSleep sets SysUI flag to GOING_TO_SLEEP`() {
+        wakefulnessLifecycle.dispatchStartedGoingToSleep(
+            PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON
+        )
 
         verify(overviewProxy)
             .onSystemUiStateChanged(
-                intThat { it and SYSUI_STATE_SCREEN_STATE_MASK == SCREEN_STATE_TURNING_OFF }
+                intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP }
             )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 47d88a5..77f7426 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -23,28 +23,21 @@
 import android.graphics.Bitmap
 import android.graphics.Bitmap.Config.HARDWARE
 import android.graphics.ColorSpace
-import android.graphics.Insets
-import android.graphics.Rect
 import android.hardware.HardwareBuffer
 import android.os.UserHandle
 import android.os.UserManager
 import android.testing.AndroidTestingRunner
 import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
-import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW
 import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
-import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.internal.util.ScreenshotRequest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.SCREENSHOT_METADATA_REFACTOR
 import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
 import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER
-import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW
 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
 import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argThat
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -61,9 +54,6 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
 
-private const val USER_ID = 1
-private const val TASK_ID = 11
-
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class TakeScreenshotServiceTest : SysuiTestCase() {
@@ -123,9 +113,6 @@
             .whenever(requestProcessor)
             .processAsync(/* screenshot= */ any(ScreenshotData::class.java), /* callback= */ any())
 
-        // Flipped in selected test cases
-        flags.set(SCREENSHOT_METADATA_REFACTOR, false)
-
         service.attach(
             mContext,
             /* thread = */ null,
@@ -158,39 +145,6 @@
         service.handleRequest(request, { /* onSaved */}, callback)
 
         verify(controller, times(1))
-            .takeScreenshotFullscreen(
-                eq(topComponent),
-                /* onSavedListener = */ any(),
-                /* requestCallback = */ any()
-            )
-
-        assertEquals("Expected one UiEvent", 1, eventLogger.numLogs())
-        val logEvent = eventLogger.get(0)
-
-        assertEquals(
-            "Expected SCREENSHOT_REQUESTED UiEvent",
-            logEvent.eventId,
-            SCREENSHOT_REQUESTED_KEY_OTHER.id
-        )
-        assertEquals(
-            "Expected supplied package name",
-            topComponent.packageName,
-            eventLogger.get(0).packageName
-        )
-    }
-
-    @Test
-    fun takeScreenshotFullscreen_screenshotDataEnabled() {
-        flags.set(SCREENSHOT_METADATA_REFACTOR, true)
-
-        val request =
-            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
-                .setTopComponent(topComponent)
-                .build()
-
-        service.handleRequest(request, { /* onSaved */}, callback)
-
-        verify(controller, times(1))
             .handleScreenshot(
                 eq(ScreenshotData.fromRequest(request)),
                 /* onSavedListener = */ any(),
@@ -213,53 +167,7 @@
     }
 
     @Test
-    fun takeScreenshotProvidedImage() {
-        val bounds = Rect(50, 50, 150, 150)
-        val bitmap = makeHardwareBitmap(100, 100)
-
-        val request =
-            ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW)
-                .setTopComponent(topComponent)
-                .setTaskId(TASK_ID)
-                .setUserId(USER_ID)
-                .setBitmap(bitmap)
-                .setBoundsOnScreen(bounds)
-                .setInsets(Insets.NONE)
-                .build()
-
-        service.handleRequest(request, { /* onSaved */}, callback)
-
-        verify(controller, times(1))
-            .handleImageAsScreenshot(
-                argThat { b -> b.equalsHardwareBitmap(bitmap) },
-                eq(bounds),
-                eq(Insets.NONE),
-                eq(TASK_ID),
-                eq(USER_ID),
-                eq(topComponent),
-                /* onSavedListener = */ any(),
-                /* requestCallback = */ any()
-            )
-
-        assertEquals("Expected one UiEvent", 1, eventLogger.numLogs())
-        val logEvent = eventLogger.get(0)
-
-        assertEquals(
-            "Expected SCREENSHOT_REQUESTED_* UiEvent",
-            logEvent.eventId,
-            SCREENSHOT_REQUESTED_OVERVIEW.id
-        )
-        assertEquals(
-            "Expected supplied package name",
-            topComponent.packageName,
-            eventLogger.get(0).packageName
-        )
-    }
-
-    @Test
     fun takeScreenshotFullscreen_userLocked() {
-        flags.set(SCREENSHOT_METADATA_REFACTOR, true)
-
         whenever(userManager.isUserUnlocked).thenReturn(false)
 
         val request =
@@ -300,8 +208,6 @@
 
     @Test
     fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
-        flags.set(SCREENSHOT_METADATA_REFACTOR, true)
-
         whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
             .thenReturn(true)
 
@@ -350,143 +256,7 @@
     }
 
     @Test
-    fun takeScreenshotFullscreen_userLocked_metadataDisabled() {
-        flags.set(SCREENSHOT_METADATA_REFACTOR, false)
-        whenever(userManager.isUserUnlocked).thenReturn(false)
-
-        val request =
-            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
-                .setTopComponent(topComponent)
-                .build()
-
-        service.handleRequest(request, { /* onSaved */}, callback)
-
-        verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
-        verify(callback, times(1)).reportError()
-        verifyZeroInteractions(controller)
-
-        assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
-        val requestEvent = eventLogger.get(0)
-        assertEquals(
-            "Expected SCREENSHOT_REQUESTED_* UiEvent",
-            SCREENSHOT_REQUESTED_KEY_OTHER.id,
-            requestEvent.eventId
-        )
-        assertEquals(
-            "Expected supplied package name",
-            topComponent.packageName,
-            requestEvent.packageName
-        )
-        val failureEvent = eventLogger.get(1)
-        assertEquals(
-            "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
-            SCREENSHOT_CAPTURE_FAILED.id,
-            failureEvent.eventId
-        )
-        assertEquals(
-            "Expected supplied package name",
-            topComponent.packageName,
-            failureEvent.packageName
-        )
-    }
-
-    @Test
-    fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers_metadataDisabled() {
-        flags.set(SCREENSHOT_METADATA_REFACTOR, false)
-
-        whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
-            .thenReturn(true)
-
-        whenever(
-                devicePolicyResourcesManager.getString(
-                    eq(SCREENSHOT_BLOCKED_BY_ADMIN),
-                    /* Supplier<String> */
-                    any(),
-                )
-            )
-            .thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
-
-        val request =
-            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
-                .setTopComponent(topComponent)
-                .build()
-
-        service.handleRequest(request, { /* onSaved */}, callback)
-
-        // error shown: Toast.makeText(...).show(), untestable
-        verify(callback, times(1)).reportError()
-        verifyZeroInteractions(controller)
-        assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
-        val requestEvent = eventLogger.get(0)
-        assertEquals(
-            "Expected SCREENSHOT_REQUESTED_* UiEvent",
-            SCREENSHOT_REQUESTED_KEY_OTHER.id,
-            requestEvent.eventId
-        )
-        assertEquals(
-            "Expected supplied package name",
-            topComponent.packageName,
-            requestEvent.packageName
-        )
-        val failureEvent = eventLogger.get(1)
-        assertEquals(
-            "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
-            SCREENSHOT_CAPTURE_FAILED.id,
-            failureEvent.eventId
-        )
-        assertEquals(
-            "Expected supplied package name",
-            topComponent.packageName,
-            failureEvent.packageName
-        )
-    }
-
-    @Test
-    fun takeScreenshot_workProfile_nullBitmap_metadataDisabled() {
-        flags.set(SCREENSHOT_METADATA_REFACTOR, false)
-
-        val request =
-            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
-                .setTopComponent(topComponent)
-                .build()
-
-        doThrow(IllegalStateException::class.java)
-            .whenever(requestProcessor)
-            .processAsync(any(ScreenshotRequest::class.java), any())
-
-        service.handleRequest(request, { /* onSaved */}, callback)
-
-        verify(callback, times(1)).reportError()
-        verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
-        verifyZeroInteractions(controller)
-        assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
-        val requestEvent = eventLogger.get(0)
-        assertEquals(
-            "Expected SCREENSHOT_REQUESTED_* UiEvent",
-            SCREENSHOT_REQUESTED_KEY_OTHER.id,
-            requestEvent.eventId
-        )
-        assertEquals(
-            "Expected supplied package name",
-            topComponent.packageName,
-            requestEvent.packageName
-        )
-        val failureEvent = eventLogger.get(1)
-        assertEquals(
-            "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
-            SCREENSHOT_CAPTURE_FAILED.id,
-            failureEvent.eventId
-        )
-        assertEquals(
-            "Expected supplied package name",
-            topComponent.packageName,
-            failureEvent.packageName
-        )
-    }
-    @Test
     fun takeScreenshot_workProfile_nullBitmap() {
-        flags.set(SCREENSHOT_METADATA_REFACTOR, true)
-
         val request =
             ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
                 .setTopComponent(topComponent)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 1bd13aa..7b37ea0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -103,6 +103,7 @@
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
@@ -286,6 +287,7 @@
     @Mock protected GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
 
     @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    @Mock protected MultiShadeInteractor mMultiShadeInteractor;
     @Mock protected KeyguardLongPressViewModel mKeyuardLongPressViewModel;
     @Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock protected MotionEvent mDownMotionEvent;
@@ -570,6 +572,7 @@
                 mLockscreenToOccludedTransitionViewModel,
                 mMainDispatcher,
                 mKeyguardTransitionInteractor,
+                () -> mMultiShadeInteractor,
                 mDumpManager,
                 mKeyuardLongPressViewModel,
                 mKeyguardInteractor);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 2a10823..bc8ab1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -86,6 +86,7 @@
     @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
     @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
     @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
+    @Mock private lateinit var shadeController: ShadeController
     @Mock private lateinit var ambientState: AmbientState
     @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
     @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@@ -173,11 +174,13 @@
                         applicationContext = context,
                         applicationScope = testScope.backgroundScope,
                         multiShadeInteractor = multiShadeInteractor,
+                        featureFlags = featureFlags,
                         keyguardTransitionInteractor =
                             KeyguardTransitionInteractor(
                                 repository = FakeKeyguardTransitionRepository(),
                             ),
                         falsingManager = FalsingManagerFake(),
+                        shadeController = shadeController,
                     )
                 },
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 86660a4..56385b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -185,11 +185,13 @@
                         applicationContext = context,
                         applicationScope = testScope.backgroundScope,
                         multiShadeInteractor = multiShadeInteractor,
+                        featureFlags = featureFlags,
                         keyguardTransitionInteractor =
                             KeyguardTransitionInteractor(
                                 repository = FakeKeyguardTransitionRepository(),
                             ),
                         falsingManager = FalsingManagerFake(),
+                        shadeController = shadeController,
                     )
                 },
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 374aae1..78f5bf2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -122,7 +122,7 @@
             isEnabled = true,
             handleAllUsers = true,
             defaultClockProvider = fakeDefaultProvider,
-            keepAllLoaded = true,
+            keepAllLoaded = false,
             subTag = "Test",
         ) {
             override fun querySettings() { }
@@ -154,8 +154,8 @@
         pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle)
         val list = registry.getClocks()
         assertEquals(
-            list,
-            listOf(
+            list.toSet(),
+            setOf(
                 ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
                 ClockMetadata("clock_1", "clock 1"),
                 ClockMetadata("clock_2", "clock 2"),
@@ -187,8 +187,8 @@
         pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2)
         val list = registry.getClocks()
         assertEquals(
-            list,
-            listOf(
+            list.toSet(),
+            setOf(
                 ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
                 ClockMetadata("clock_1", "clock 1"),
                 ClockMetadata("clock_2", "clock 2")
@@ -293,6 +293,53 @@
         assertEquals(4, listChangeCallCount)
     }
 
+    @Test
+    fun pluginAddRemove_concurrentModification() {
+        val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
+        val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
+        val mockPluginLifecycle3 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
+        val mockPluginLifecycle4 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
+        val plugin1 = FakeClockPlugin().addClock("clock_1", "clock 1")
+        val plugin2 = FakeClockPlugin().addClock("clock_2", "clock 2")
+        val plugin3 = FakeClockPlugin().addClock("clock_3", "clock 3")
+        val plugin4 = FakeClockPlugin().addClock("clock_4", "clock 4")
+        whenever(mockPluginLifecycle1.isLoaded).thenReturn(true)
+        whenever(mockPluginLifecycle2.isLoaded).thenReturn(true)
+        whenever(mockPluginLifecycle3.isLoaded).thenReturn(true)
+        whenever(mockPluginLifecycle4.isLoaded).thenReturn(true)
+
+        // Set the current clock to the final clock to load
+        registry.applySettings(ClockSettings("clock_4", null))
+        scheduler.runCurrent()
+
+        // When ClockRegistry attempts to unload a plugin, we at that point decide to load and
+        // unload other plugins. This causes ClockRegistry to modify the list of available clock
+        // plugins while it is being iterated over. In production this happens as a result of a
+        // thread race, instead of synchronously like it does here.
+        whenever(mockPluginLifecycle2.unloadPlugin()).then {
+            pluginListener.onPluginDetached(mockPluginLifecycle1)
+            pluginListener.onPluginLoaded(plugin4, mockContext, mockPluginLifecycle4)
+        }
+
+        // Load initial plugins
+        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1)
+        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2)
+        pluginListener.onPluginLoaded(plugin3, mockContext, mockPluginLifecycle3)
+
+        // Repeatedly verify the loaded providers to get final state
+        registry.verifyLoadedProviders()
+        scheduler.runCurrent()
+        registry.verifyLoadedProviders()
+        scheduler.runCurrent()
+
+        // Verify all plugins were correctly loaded into the registry
+        assertEquals(registry.getClocks().toSet(), setOf(
+            ClockMetadata("DEFAULT", "Default Clock"),
+            ClockMetadata("clock_2", "clock 2"),
+            ClockMetadata("clock_3", "clock 3"),
+            ClockMetadata("clock_4", "clock 4")
+        ))
+    }
 
     @Test
     fun jsonDeserialization_gotExpectedObject() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index f581154..f4cd383 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -33,6 +33,7 @@
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.os.Bundle;
+import android.view.KeyEvent;
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
@@ -397,9 +398,10 @@
 
     @Test
     public void testHandleSysKey() {
-        mCommandQueue.handleSystemKey(1);
+        KeyEvent testEvent = new KeyEvent(1, 1);
+        mCommandQueue.handleSystemKey(testEvent);
         waitForIdleSync();
-        verify(mCallbacks).handleSystemKey(eq(1));
+        verify(mCallbacks).handleSystemKey(eq(testEvent));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
index 39ea46a..e2aef31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
@@ -126,7 +126,7 @@
         }
 
         fun assertLastProgress(progress: Float) {
-            waitForCondition { progress == progressHistory.last() }
+            waitForCondition { progress == progressHistory.lastOrNull() }
         }
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index d8b3270..65735f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -21,7 +21,6 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.flowOf
 
 class FakeBiometricSettingsRepository : BiometricSettingsRepository {
 
@@ -39,12 +38,17 @@
     private val _isStrongBiometricAllowed = MutableStateFlow(false)
     override val isStrongBiometricAllowed = _isStrongBiometricAllowed.asStateFlow()
 
+    private val _isNonStrongBiometricAllowed = MutableStateFlow(false)
+    override val isNonStrongBiometricAllowed: StateFlow<Boolean>
+        get() = _isNonStrongBiometricAllowed
+
     private val _isFingerprintEnabledByDevicePolicy = MutableStateFlow(false)
     override val isFingerprintEnabledByDevicePolicy =
         _isFingerprintEnabledByDevicePolicy.asStateFlow()
 
+    private val _isFaceAuthSupportedInCurrentPosture = MutableStateFlow(false)
     override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
-        get() = flowOf(true)
+        get() = _isFaceAuthSupportedInCurrentPosture
 
     private val _isCurrentUserInLockdown = MutableStateFlow(false)
     override val isCurrentUserInLockdown: Flow<Boolean>
@@ -66,7 +70,19 @@
         _isFaceEnrolled.value = isFaceEnrolled
     }
 
+    fun setIsFaceAuthSupportedInCurrentPosture(value: Boolean) {
+        _isFaceAuthSupportedInCurrentPosture.value = value
+    }
+
     fun setIsFaceAuthEnabled(enabled: Boolean) {
         _isFaceAuthEnabled.value = enabled
     }
+
+    fun setIsUserInLockdown(value: Boolean) {
+        _isCurrentUserInLockdown.value = value
+    }
+
+    fun setIsNonStrongBiometricAllowed(value: Boolean) {
+        _isNonStrongBiometricAllowed.value = value
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt
new file mode 100644
index 0000000..fe94117
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.systemui.keyguard.data.repository
+
+import android.content.Context
+import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.statusbar.CommandQueue
+import org.mockito.Mockito.mock
+
+class FakeCommandQueue : CommandQueue(mock(Context::class.java), mock(DisplayTracker::class.java)) {
+    private val callbacks = mutableListOf<Callbacks>()
+
+    override fun addCallback(callback: Callbacks) {
+        callbacks.add(callback)
+    }
+
+    override fun removeCallback(callback: Callbacks) {
+        callbacks.remove(callback)
+    }
+
+    fun doForEachCallback(func: (callback: Callbacks) -> Unit) {
+        callbacks.forEach { func(it) }
+    }
+
+    fun callbackCount(): Int = callbacks.size
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
index 00b1a40..4bfd3d6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
@@ -30,10 +30,19 @@
     override val isRunning: Flow<Boolean>
         get() = _isRunning
 
-    override val availableFpSensorType: BiometricType?
-        get() = null
+    private var fpSensorType = MutableStateFlow<BiometricType?>(null)
+    override val availableFpSensorType: Flow<BiometricType?>
+        get() = fpSensorType
 
     fun setLockedOut(lockedOut: Boolean) {
         _isLockedOut.value = lockedOut
     }
+
+    fun setIsRunning(value: Boolean) {
+        _isRunning.value = value
+    }
+
+    fun setAvailableFpSensorType(value: BiometricType?) {
+        fpSensorType.value = value
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
index 1dda472..8a6d2aa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
@@ -48,8 +48,6 @@
     override val showMessage = _showMessage.asStateFlow()
     private val _resourceUpdateRequests = MutableStateFlow(false)
     override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
-    override val bouncerPromptReason = 0
-    override val bouncerErrorMessage: CharSequence? = null
     private val _isAlternateBouncerVisible = MutableStateFlow(false)
     override val alternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
     override var lastAlternateBouncerVisibleTime: Long = 0L
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 194ed02..d411590 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -129,6 +129,10 @@
         _isKeyguardShowing.value = isShowing
     }
 
+    fun setKeyguardGoingAway(isGoingAway: Boolean) {
+        _isKeyguardGoingAway.value = isGoingAway
+    }
+
     fun setKeyguardOccluded(isOccluded: Boolean) {
         _isKeyguardOccluded.value = isOccluded
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
new file mode 100644
index 0000000..6690de8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.systemui.keyguard.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeTrustRepository : TrustRepository {
+    private val _isCurrentUserTrusted = MutableStateFlow(false)
+    override val isCurrentUserTrusted: Flow<Boolean>
+        get() = _isCurrentUserTrusted
+
+    fun setCurrentUserTrusted(trust: Boolean) {
+        _isCurrentUserTrusted.value = trust
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
index ced7955..9ff7dd5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
@@ -23,11 +23,9 @@
 
 /** A fake [FgsManagerController] to be used in tests. */
 class FakeFgsManagerController(
-    isAvailable: Boolean = true,
     showFooterDot: Boolean = false,
     numRunningPackages: Int = 0,
 ) : FgsManagerController {
-    override val isAvailable: MutableStateFlow<Boolean> = MutableStateFlow(isAvailable)
 
     override var numRunningPackages = numRunningPackages
         set(value) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 53bb340..fbc2381 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -100,4 +100,8 @@
     fun setGuestUserAutoCreated(value: Boolean) {
         _isGuestUserAutoCreated = value
     }
+
+    fun setUserSwitching(value: Boolean) {
+        _userSwitchingInProgress.value = value
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index e159f18..7463061 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -271,6 +271,14 @@
          */
         void onClientChangeLocked(boolean serviceInfoChanged);
 
+        /**
+         * Called back to notify the system the proxy client for a device has changed.
+         *
+         * Changes include if the proxy is unregistered, if its service info list has changed, or if
+         * its focus appearance has changed.
+         */
+        void onProxyChanged(int deviceId);
+
         int getCurrentUserIdLocked();
 
         Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
@@ -315,8 +323,6 @@
 
         void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc);
 
-        void setCurrentUserFocusAppearance(int strokeWidth, int color);
-
     }
 
     public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 8473ab7..ae4e531 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -25,6 +25,9 @@
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_USER_BROADCAST_RECEIVER;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
+import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
+import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
@@ -189,7 +192,7 @@
         AccessibilityUserState.ServiceInfoChangeListener,
         AccessibilityWindowManager.AccessibilityEventSender,
         AccessibilitySecurityPolicy.AccessibilityUserManager,
-        SystemActionPerformer.SystemActionsChangedListener {
+        SystemActionPerformer.SystemActionsChangedListener, ProxyManager.SystemSupport{
 
     private static final boolean DEBUG = false;
 
@@ -471,7 +474,8 @@
                 new MagnificationScaleProvider(mContext));
         mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
         mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
-        mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext);
+        mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext, mMainHandler,
+                mUiAutomationManager, this);
         mFlashNotificationsController = new FlashNotificationsController(mContext);
         init();
     }
@@ -876,6 +880,19 @@
         };
         mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, mMainHandler,
                 Context.RECEIVER_EXPORTED);
+
+        final BroadcastReceiver virtualDeviceReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int deviceId = intent.getIntExtra(
+                        EXTRA_VIRTUAL_DEVICE_ID, DEVICE_ID_DEFAULT);
+                mProxyManager.clearConnections(deviceId);
+            }
+        };
+
+        final IntentFilter virtualDeviceFilter = new IntentFilter(ACTION_VIRTUAL_DEVICE_REMOVED);
+        mContext.registerReceiver(virtualDeviceReceiver, virtualDeviceFilter,
+                Context.RECEIVER_NOT_EXPORTED);
     }
 
     /**
@@ -954,21 +971,42 @@
             final int resolvedUserId = mSecurityPolicy
                     .resolveCallingUserIdEnforcingPermissionsLocked(userId);
 
+            AccessibilityUserState userState = getUserStateLocked(resolvedUserId);
+            // Support a process moving from the default device to a single virtual
+            // device.
+            final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+                    Binder.getCallingUid());
+            Client client = new Client(callback, Binder.getCallingUid(), userState, deviceId);
             // If the client is from a process that runs across users such as
             // the system UI or the system we add it to the global state that
             // is shared across users.
-            AccessibilityUserState userState = getUserStateLocked(resolvedUserId);
-            Client client = new Client(callback, Binder.getCallingUid(), userState);
             if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) {
+                if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                    if (DEBUG) {
+                        Slog.v(LOG_TAG, "Added global client for proxy-ed pid: "
+                                + Binder.getCallingPid() + " for device id " + deviceId
+                                + " with package names " + Arrays.toString(client.mPackageNames));
+                    }
+                    return IntPair.of(mProxyManager.getStateLocked(deviceId,
+                                    mUiAutomationManager.isUiAutomationRunningLocked()),
+                            client.mLastSentRelevantEventTypes);
+                }
                 mGlobalClients.register(callback, client);
                 if (DEBUG) {
                     Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
                 }
-                return IntPair.of(
-                        combineUserStateAndProxyState(getClientStateLocked(userState),
-                                mProxyManager.getStateLocked()),
-                        client.mLastSentRelevantEventTypes);
             } else {
+                // If the display belongs to a proxy connections
+                if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                    if (DEBUG) {
+                        Slog.v(LOG_TAG, "Added user client for proxy-ed pid: "
+                                + Binder.getCallingPid() + " for device id " + deviceId
+                                + " with package names " + Arrays.toString(client.mPackageNames));
+                    }
+                    return IntPair.of(mProxyManager.getStateLocked(deviceId,
+                                    mUiAutomationManager.isUiAutomationRunningLocked()),
+                            client.mLastSentRelevantEventTypes);
+                }
                 userState.mUserClients.register(callback, client);
                 // If this client is not for the current user we do not
                 // return a state since it is not for the foreground user.
@@ -977,12 +1015,10 @@
                     Slog.i(LOG_TAG, "Added user client for pid:" + Binder.getCallingPid()
                             + " and userId:" + mCurrentUserId);
                 }
-                return IntPair.of(
-                        (resolvedUserId == mCurrentUserId) ? combineUserStateAndProxyState(
-                                getClientStateLocked(userState), mProxyManager.getStateLocked())
-                                : 0,
-                        client.mLastSentRelevantEventTypes);
             }
+            return IntPair.of(
+                    (resolvedUserId == mCurrentUserId) ? getClientStateLocked(userState) : 0,
+                    client.mLastSentRelevantEventTypes);
         }
     }
 
@@ -1108,7 +1144,7 @@
     }
 
     private void dispatchAccessibilityEventLocked(AccessibilityEvent event) {
-        if (mProxyManager.isProxyed(event.getDisplayId())) {
+        if (mProxyManager.isProxyedDisplay(event.getDisplayId())) {
             mProxyManager.sendAccessibilityEventLocked(event);
         } else {
             notifyAccessibilityServicesDelayedLocked(event, false);
@@ -1174,6 +1210,12 @@
         final int resolvedUserId;
         final List<AccessibilityServiceInfo> serviceInfos;
         synchronized (mLock) {
+            final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+                    Binder.getCallingUid());
+            if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                return mProxyManager.getInstalledAndEnabledServiceInfosLocked(
+                        AccessibilityServiceInfo.FEEDBACK_ALL_MASK, deviceId);
+            }
             // We treat calls from a profile as if made by its parent as profiles
             // share the accessibility state of the parent. The call below
             // performs the current profile parent resolution.
@@ -1209,6 +1251,12 @@
         }
 
         synchronized (mLock) {
+            final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+                    Binder.getCallingUid());
+            if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                return mProxyManager.getInstalledAndEnabledServiceInfosLocked(feedbackType,
+                        deviceId);
+            }
             // We treat calls from a profile as if made by its parent as profiles
             // share the accessibility state of the parent. The call below
             // performs the current profile parent resolution.
@@ -1253,19 +1301,25 @@
             if (resolvedUserId != mCurrentUserId) {
                 return;
             }
-            List<AccessibilityServiceConnection> services =
-                    getUserStateLocked(resolvedUserId).mBoundServices;
-            int numServices = services.size() + mProxyManager.getNumProxysLocked();
-            interfacesToInterrupt = new ArrayList<>(numServices);
-            for (int i = 0; i < services.size(); i++) {
-                AccessibilityServiceConnection service = services.get(i);
-                IBinder a11yServiceBinder = service.mService;
-                IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
-                if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) {
-                    interfacesToInterrupt.add(a11yServiceInterface);
+
+            final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+                    Binder.getCallingUid());
+            if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                interfacesToInterrupt = new ArrayList<>();
+                mProxyManager.addServiceInterfacesLocked(interfacesToInterrupt, deviceId);
+            } else {
+                List<AccessibilityServiceConnection> services =
+                        getUserStateLocked(resolvedUserId).mBoundServices;
+                interfacesToInterrupt = new ArrayList<>(services.size());
+                for (int i = 0; i < services.size(); i++) {
+                    AccessibilityServiceConnection service = services.get(i);
+                    IBinder a11yServiceBinder = service.mService;
+                    IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
+                    if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) {
+                        interfacesToInterrupt.add(a11yServiceInterface);
+                    }
                 }
             }
-            mProxyManager.addServiceInterfacesLocked(interfacesToInterrupt);
         }
         for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) {
             try {
@@ -1841,7 +1895,8 @@
         return result;
     }
 
-    private void notifyClearAccessibilityCacheLocked() {
+    @Override
+    public void notifyClearAccessibilityCacheLocked() {
         AccessibilityUserState state = getCurrentUserStateLocked();
         for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
             AccessibilityServiceConnection service = state.mBoundServices.get(i);
@@ -2045,18 +2100,15 @@
         mMainHandler.post(() -> {
             broadcastToClients(userState, ignoreRemoteException(client -> {
                 int relevantEventTypes;
-                boolean changed = false;
                 synchronized (mLock) {
                     relevantEventTypes = computeRelevantEventTypesLocked(userState, client);
-
-                    if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
-                        client.mLastSentRelevantEventTypes = relevantEventTypes;
-                        changed = true;
+                    if (!mProxyManager.isProxyedDeviceId(client.mDeviceId)) {
+                        if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
+                            client.mLastSentRelevantEventTypes = relevantEventTypes;
+                            client.mCallback.setRelevantEventTypes(relevantEventTypes);
+                        }
                     }
                 }
-                if (changed) {
-                    client.mCallback.setRelevantEventTypes(relevantEventTypes);
-                }
             }));
         });
     }
@@ -2076,7 +2128,6 @@
                 mUiAutomationManager.getServiceInfo(), client)
                 ? mUiAutomationManager.getRelevantEventTypes()
                 : 0;
-        relevantEventTypes |= mProxyManager.getRelevantEventTypesLocked();
         return relevantEventTypes;
     }
 
@@ -2130,7 +2181,7 @@
         }
     }
 
-    private static boolean isClientInPackageAllowlist(
+    static boolean isClientInPackageAllowlist(
             @Nullable AccessibilityServiceInfo serviceInfo, Client client) {
         if (serviceInfo == null) return false;
 
@@ -2323,24 +2374,20 @@
         updateAccessibilityEnabledSettingLocked(userState);
     }
 
-    private int combineUserStateAndProxyState(int userState, int proxyState) {
-        return userState | proxyState;
+    void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
+        scheduleUpdateClientsIfNeededLocked(userState, false);
     }
 
-    void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
+    void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState,
+            boolean forceUpdate) {
         final int clientState = getClientStateLocked(userState);
-        final int proxyState = mProxyManager.getStateLocked();
-        if ((userState.getLastSentClientStateLocked() != clientState
-                || mProxyManager.getLastSentStateLocked() != proxyState)
+        if (((userState.getLastSentClientStateLocked() != clientState || forceUpdate))
                 && (mGlobalClients.getRegisteredCallbackCount() > 0
                 || userState.mUserClients.getRegisteredCallbackCount() > 0)) {
             userState.setLastSentClientStateLocked(clientState);
-            mProxyManager.setLastStateLocked(proxyState);
-            // Send both the user and proxy state to the app for now.
-            // TODO(b/250929565): Send proxy state to proxy clients
             mMainHandler.sendMessage(obtainMessage(
                     AccessibilityManagerService::sendStateToAllClients,
-                    this, combineUserStateAndProxyState(clientState, proxyState),
+                    this, clientState,
                     userState.mUserId));
         }
     }
@@ -2360,8 +2407,13 @@
             mTraceManager.logTrace(LOG_TAG + ".sendStateToClients",
                     FLAGS_ACCESSIBILITY_MANAGER_CLIENT, "clientState=" + clientState);
         }
-        clients.broadcast(ignoreRemoteException(
-                client -> client.setState(clientState)));
+        clients.broadcastForEachCookie(ignoreRemoteException(
+                client -> {
+                    Client managerClient = ((Client) client);
+                    if (!mProxyManager.isProxyedDeviceId(managerClient.mDeviceId)) {
+                        managerClient.mCallback.setState(clientState);
+                    }
+                }));
     }
 
     private void scheduleNotifyClientsOfServicesStateChangeLocked(
@@ -2384,8 +2436,14 @@
             mTraceManager.logTrace(LOG_TAG + ".notifyClientsOfServicesStateChange",
                     FLAGS_ACCESSIBILITY_MANAGER_CLIENT, "uiTimeout=" + uiTimeout);
         }
-        clients.broadcast(ignoreRemoteException(
-                client -> client.notifyServicesStateChanged(uiTimeout)));
+
+        clients.broadcastForEachCookie(ignoreRemoteException(
+                client -> {
+                    Client managerClient = ((Client) client);
+                    if (!mProxyManager.isProxyedDeviceId(managerClient.mDeviceId)) {
+                        managerClient.mCallback.notifyServicesStateChanged(uiTimeout);
+                    }
+                }));
     }
 
     private void scheduleUpdateInputFilter(AccessibilityUserState userState) {
@@ -2458,7 +2516,6 @@
                     }
                     inputFilter = mInputFilter;
                     setInputFilter = true;
-                    mProxyManager.setAccessibilityInputFilter(mInputFilter);
                 }
                 mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags);
                 mInputFilter.setCombinedGenericMotionEventSources(
@@ -2491,6 +2548,7 @@
                         "inputFilter=" + inputFilter);
             }
             mWindowManagerService.setInputFilter(inputFilter);
+            mProxyManager.setAccessibilityInputFilter(inputFilter);
         }
     }
 
@@ -2555,6 +2613,20 @@
      * @param userState the new user state
      */
     private void onUserStateChangedLocked(AccessibilityUserState userState) {
+        onUserStateChangedLocked(userState, false);
+    }
+
+    /**
+     * Called when any property of the user state has changed.
+     *
+     * @param userState the new user state
+     * @param forceUpdate whether to force an update of the app Clients.
+     */
+    private void onUserStateChangedLocked(AccessibilityUserState userState, boolean forceUpdate) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "onUserStateChangedLocked for user " + userState.mUserId + " with "
+                    + "forceUpdate: " + forceUpdate);
+        }
         // TODO: Remove this hack
         mInitialized = true;
         updateLegacyCapabilitiesLocked(userState);
@@ -2567,7 +2639,7 @@
         scheduleUpdateFingerprintGestureHandling(userState);
         scheduleUpdateInputFilter(userState);
         updateRelevantEventsLocked(userState);
-        scheduleUpdateClientsIfNeededLocked(userState);
+        scheduleUpdateClientsIfNeededLocked(userState, forceUpdate);
         updateAccessibilityShortcutKeyTargetsLocked(userState);
         updateAccessibilityButtonTargetsLocked(userState);
         // Update the capabilities before the mode because we will check the current mode is
@@ -2614,7 +2686,7 @@
             if (display != null) {
                 if (observingWindows) {
                     mA11yWindowManager.startTrackingWindows(display.getDisplayId(),
-                            mProxyManager.isProxyed(display.getDisplayId()));
+                            mProxyManager.isProxyedDisplay(display.getDisplayId()));
                 } else {
                     mA11yWindowManager.stopTrackingWindows(display.getDisplayId());
                 }
@@ -2881,6 +2953,8 @@
                 mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, 0,
                 userState.mUserId);
+
+        mProxyManager.updateTimeoutsIfNeeded(nonInteractiveUiTimeout, interactiveUiTimeout);
         if (nonInteractiveUiTimeout != userState.getUserNonInteractiveUiTimeoutLocked()
                 || interactiveUiTimeout != userState.getUserInteractiveUiTimeoutLocked()) {
             userState.setUserNonInteractiveUiTimeoutLocked(nonInteractiveUiTimeout);
@@ -3646,8 +3720,14 @@
         }
 
         synchronized(mLock) {
-            final AccessibilityUserState userState = getCurrentUserStateLocked();
-            return getRecommendedTimeoutMillisLocked(userState);
+            final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+                    Binder.getCallingUid());
+            if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                return mProxyManager.getRecommendedTimeoutMillisLocked(deviceId);
+            } else {
+                final AccessibilityUserState userState = getCurrentUserStateLocked();
+                return getRecommendedTimeoutMillisLocked(userState);
+            }
         }
     }
 
@@ -3726,6 +3806,11 @@
             mTraceManager.logTrace(LOG_TAG + ".getFocusStrokeWidth", FLAGS_ACCESSIBILITY_MANAGER);
         }
         synchronized (mLock) {
+            final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+                    Binder.getCallingUid());
+            if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                return mProxyManager.getFocusStrokeWidthLocked(deviceId);
+            }
             final AccessibilityUserState userState = getCurrentUserStateLocked();
 
             return userState.getFocusStrokeWidthLocked();
@@ -3742,6 +3827,11 @@
             mTraceManager.logTrace(LOG_TAG + ".getFocusColor", FLAGS_ACCESSIBILITY_MANAGER);
         }
         synchronized (mLock) {
+            final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+                    Binder.getCallingUid());
+            if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                return mProxyManager.getFocusColorLocked(deviceId);
+            }
             final AccessibilityUserState userState = getCurrentUserStateLocked();
 
             return userState.getFocusColorLocked();
@@ -3828,9 +3918,9 @@
             throw new IllegalArgumentException("The display " + displayId + " does not exist or is"
                     + " not tracked by accessibility.");
         }
-        if (mProxyManager.isProxyed(displayId)) {
+        if (mProxyManager.isProxyedDisplay(displayId)) {
             throw new IllegalArgumentException("The display " + displayId + " is already being"
-                    + "proxy-ed");
+                    + " proxy-ed");
         }
 
         final long identity = Binder.clearCallingIdentity();
@@ -3861,7 +3951,7 @@
     }
 
     boolean isDisplayProxyed(int displayId) {
-        return mProxyManager.isProxyed(displayId);
+        return mProxyManager.isProxyedDisplay(displayId);
     }
 
     @Override
@@ -4009,13 +4099,71 @@
 
     @Override
     public void onClientChangeLocked(boolean serviceInfoChanged) {
+        onClientChangeLocked(serviceInfoChanged, false);
+    }
+
+    /**
+     * Called when the state of a service or proxy has changed
+     * @param serviceInfoChanged if the service info has changed
+     * @param forceUpdate whether to force an update of state for app clients
+     */
+    public void onClientChangeLocked(boolean serviceInfoChanged, boolean forceUpdate) {
         AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
-        onUserStateChangedLocked(userState);
+        onUserStateChangedLocked(userState, forceUpdate);
         if (serviceInfoChanged) {
             scheduleNotifyClientsOfServicesStateChangeLocked(userState);
         }
     }
 
+
+    @Override
+    public void onProxyChanged(int deviceId) {
+        mProxyManager.onProxyChanged(deviceId);
+    }
+
+    /**
+     * Removes the device from tracking. This will reset any AccessibilityManagerClients to be
+     * associated with the default user id.
+     */
+    @Override
+    public void removeDeviceIdLocked(int deviceId) {
+        resetClientsLocked(deviceId, getCurrentUserStateLocked().mUserClients);
+        resetClientsLocked(deviceId, mGlobalClients);
+        // Force an update of A11yManagers if the state was previously a proxy state and needs to be
+        // returned to the default device state.
+        onClientChangeLocked(true, true);
+    }
+
+    private void resetClientsLocked(int deviceId,
+            RemoteCallbackList<IAccessibilityManagerClient> clients) {
+        if (clients == null || clients.getRegisteredCallbackCount() == 0) {
+            return;
+        }
+        synchronized (mLock) {
+            for (int i = 0; i < clients.getRegisteredCallbackCount(); i++) {
+                final Client appClient = ((Client) clients.getRegisteredCallbackCookie(i));
+                if (appClient.mDeviceId == deviceId) {
+                    appClient.mDeviceId = DEVICE_ID_DEFAULT;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void updateWindowsForAccessibilityCallbackLocked() {
+        updateWindowsForAccessibilityCallbackLocked(getUserStateLocked(mCurrentUserId));
+    }
+
+    @Override
+    public RemoteCallbackList<IAccessibilityManagerClient> getGlobalClientsLocked() {
+        return mGlobalClients;
+    }
+
+    @Override
+    public RemoteCallbackList<IAccessibilityManagerClient> getCurrentUserClientsLocked() {
+        return getCurrentUserState().mUserClients;
+    }
+
     @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ShellCallback callback,
@@ -4327,13 +4475,22 @@
         final IAccessibilityManagerClient mCallback;
         final String[] mPackageNames;
         int mLastSentRelevantEventTypes;
+        int mUid;
+        int mDeviceId = DEVICE_ID_DEFAULT;
 
         private Client(IAccessibilityManagerClient callback, int clientUid,
-                AccessibilityUserState userState) {
+                AccessibilityUserState userState, int deviceId) {
             mCallback = callback;
             mPackageNames = mPackageManager.getPackagesForUid(clientUid);
+            mUid = clientUid;
+            mDeviceId = deviceId;
             synchronized (mLock) {
-                mLastSentRelevantEventTypes = computeRelevantEventTypesLocked(userState, this);
+                if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                    mLastSentRelevantEventTypes =
+                            mProxyManager.computeRelevantEventTypesLocked(this);
+                } else {
+                    mLastSentRelevantEventTypes = computeRelevantEventTypesLocked(userState, this);
+                }
             }
         }
     }
@@ -4819,8 +4976,10 @@
         }
         mMainHandler.post(() -> {
             broadcastToClients(userState, ignoreRemoteException(client -> {
-                client.mCallback.setFocusAppearance(userState.getFocusStrokeWidthLocked(),
-                        userState.getFocusColorLocked());
+                if (!mProxyManager.isProxyedDeviceId(client.mDeviceId)) {
+                    client.mCallback.setFocusAppearance(userState.getFocusStrokeWidthLocked(),
+                            userState.getFocusColorLocked());
+                }
             }));
         });
 
@@ -5055,11 +5214,4 @@
         transaction.apply();
         transaction.close();
     }
-
-    @Override
-    public void setCurrentUserFocusAppearance(int strokeWidth, int color) {
-        synchronized (mLock) {
-            getCurrentUserStateLocked().setFocusAppearanceLocked(strokeWidth, color);
-        }
-    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index b19a502..ab01fc3 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -67,6 +67,7 @@
 public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection {
     private static final String LOG_TAG = "ProxyAccessibilityServiceConnection";
 
+    private int mDeviceId;
     private int mDisplayId;
     private List<AccessibilityServiceInfo> mInstalledAndEnabledServices;
 
@@ -75,6 +76,9 @@
     /** The color of the focus rectangle */
     private int mFocusColor;
 
+    private int mInteractiveTimeout;
+    private int mNonInteractiveTimeout;
+
     ProxyAccessibilityServiceConnection(
             Context context,
             ComponentName componentName,
@@ -83,7 +87,7 @@
             AccessibilitySecurityPolicy securityPolicy,
             SystemSupport systemSupport, AccessibilityTrace trace,
             WindowManagerInternal windowManagerInternal,
-            AccessibilityWindowManager awm, int displayId) {
+            AccessibilityWindowManager awm, int displayId, int deviceId) {
         super(/* userState= */null, context, componentName, accessibilityServiceInfo, id,
                 mainHandler, lock, securityPolicy, systemSupport, trace, windowManagerInternal,
                 /* systemActionPerformer= */ null, awm, /* activityTaskManagerService= */ null);
@@ -93,6 +97,14 @@
                 R.dimen.accessibility_focus_highlight_stroke_width);
         mFocusColor = mContext.getResources().getColor(
                 R.color.accessibility_focus_highlight_color);
+        mDeviceId = deviceId;
+    }
+
+    int getDisplayId() {
+        return mDisplayId;
+    }
+    int getDeviceId() {
+        return mDeviceId;
     }
 
     /**
@@ -155,6 +167,8 @@
                 proxyInfo.setAccessibilityTool(isAccessibilityTool);
                 proxyInfo.setInteractiveUiTimeoutMillis(interactiveUiTimeout);
                 proxyInfo.setNonInteractiveUiTimeoutMillis(nonInteractiveUiTimeout);
+                mInteractiveTimeout = interactiveUiTimeout;
+                mNonInteractiveTimeout = nonInteractiveUiTimeout;
 
                 // If any one service info doesn't set package names, i.e. if it's interested in all
                 // apps, the proxy shouldn't filter by package name even if some infos specify this.
@@ -167,7 +181,7 @@
                 // Update connection with mAccessibilityServiceInfo values.
                 setDynamicallyConfigurableProperties(proxyInfo);
                 // Notify manager service.
-                mSystemSupport.onClientChangeLocked(true);
+                mSystemSupport.onProxyChanged(mDeviceId);
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -235,12 +249,7 @@
 
             mFocusStrokeWidth = strokeWidth;
             mFocusColor = color;
-            // Sets the appearance data in the A11yUserState for now, since the A11yManagers are not
-            // separated.
-            // TODO(254545943): Separate proxy and non-proxy states so the focus appearance on the
-            // phone is not affected by the appearance of a proxy-ed app.
-            mSystemSupport.setCurrentUserFocusAppearance(mFocusStrokeWidth, mFocusColor);
-            mSystemSupport.onClientChangeLocked(false);
+            mSystemSupport.onProxyChanged(mDeviceId);
         }
     }
 
@@ -572,17 +581,51 @@
         throw new UnsupportedOperationException("setAnimationScale is not supported");
     }
 
+    public int getInteractiveTimeout() {
+        return mInteractiveTimeout;
+    }
+
+    public int getNonInteractiveTimeout() {
+        return mNonInteractiveTimeout;
+    }
+
+    /**
+     * Returns true if a timeout was updated.
+     */
+    public boolean updateTimeouts(int nonInteractiveUiTimeout, int interactiveUiTimeout) {
+        final int newInteractiveUiTimeout = interactiveUiTimeout != 0
+                ? interactiveUiTimeout
+                : mAccessibilityServiceInfo.getInteractiveUiTimeoutMillis();
+        final int newNonInteractiveUiTimeout = nonInteractiveUiTimeout != 0
+                ? nonInteractiveUiTimeout
+                : mAccessibilityServiceInfo.getNonInteractiveUiTimeoutMillis();
+        boolean updated = false;
+
+        if (mInteractiveTimeout != newInteractiveUiTimeout) {
+            mInteractiveTimeout =  newInteractiveUiTimeout;
+            updated = true;
+        }
+        if (mNonInteractiveTimeout != newNonInteractiveUiTimeout) {
+            mNonInteractiveTimeout = newNonInteractiveUiTimeout;
+            updated = true;
+        }
+        return updated;
+    }
+
     @Override
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
         synchronized (mLock) {
             pw.append("Proxy[displayId=" + mDisplayId);
+            pw.append(", deviceId=" + mDeviceId);
             pw.append(", feedbackType"
                     + AccessibilityServiceInfo.feedbackTypeToString(mFeedbackType));
             pw.append(", capabilities=" + mAccessibilityServiceInfo.getCapabilities());
             pw.append(", eventTypes="
                     + AccessibilityEvent.eventTypeToString(mEventTypes));
             pw.append(", notificationTimeout=" + mNotificationTimeout);
+            pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveTimeout));
+            pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveTimeout));
             pw.append(", focusStrokeWidth=").append(String.valueOf(mFocusStrokeWidth));
             pw.append(", focusColor=").append(String.valueOf(mFocusColor));
             pw.append(", installedAndEnabledServiceCount=").append(String.valueOf(
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index e258de1..d417197 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -14,26 +14,45 @@
  * limitations under the License.
  */
 package com.android.server.accessibility;
+
+import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.content.Context.DEVICE_ID_INVALID;
+
+import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
+
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
+import android.annotation.NonNull;
+import android.companion.virtual.VirtualDeviceManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.view.Display;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManagerClient;
 
+import com.android.internal.util.IntPair;
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * Manages proxy connections.
@@ -53,26 +72,72 @@
     static final String PROXY_COMPONENT_PACKAGE_NAME = "ProxyPackage";
     static final String PROXY_COMPONENT_CLASS_NAME = "ProxyClass";
 
+    // AMS#mLock
     private final Object mLock;
 
     private final Context mContext;
+    private final Handler mMainHandler;
 
-    // Used to determine if we should notify AccessibilityManager clients of updates.
-    // TODO(254545943): Separate this so each display id has its own state. Currently there is no
-    // way to identify from AccessibilityManager which proxy state should be returned.
-    private int mLastState = -1;
+    private final UiAutomationManager mUiAutomationManager;
 
-    private SparseArray<ProxyAccessibilityServiceConnection> mProxyA11yServiceConnections =
+    // Device Id -> state. Used to determine if we should notify AccessibilityManager clients of
+    // updates.
+    private final SparseIntArray mLastStates = new SparseIntArray();
+
+    // Each display id entry in a SparseArray represents a proxy a11y user.
+    private final SparseArray<ProxyAccessibilityServiceConnection> mProxyA11yServiceConnections =
             new SparseArray<>();
 
-    private AccessibilityWindowManager mA11yWindowManager;
+    private final AccessibilityWindowManager mA11yWindowManager;
 
     private AccessibilityInputFilter mA11yInputFilter;
 
-    ProxyManager(Object lock, AccessibilityWindowManager awm, Context context) {
+    private VirtualDeviceManagerInternal mLocalVdm;
+
+    private final SystemSupport mSystemSupport;
+
+    /**
+     * Callbacks into AccessibilityManagerService.
+     */
+    public interface SystemSupport {
+        /**
+         * Removes the device id from tracking.
+         */
+        void removeDeviceIdLocked(int deviceId);
+
+        /**
+         * Updates the windows tracking for the current user.
+         */
+        void updateWindowsForAccessibilityCallbackLocked();
+
+        /**
+         * Clears all caches.
+         */
+        void notifyClearAccessibilityCacheLocked();
+
+        /**
+         * Gets the clients for all users.
+         */
+        @NonNull
+        RemoteCallbackList<IAccessibilityManagerClient> getGlobalClientsLocked();
+
+        /**
+         * Gets the clients for the current user.
+         */
+        @NonNull
+        RemoteCallbackList<IAccessibilityManagerClient> getCurrentUserClientsLocked();
+    }
+
+    ProxyManager(Object lock, AccessibilityWindowManager awm,
+            Context context, Handler mainHandler, UiAutomationManager uiAutomationManager,
+            SystemSupport systemSupport) {
         mLock = lock;
         mA11yWindowManager = awm;
         mContext = context;
+        mMainHandler = mainHandler;
+        mUiAutomationManager = uiAutomationManager;
+        mSystemSupport = systemSupport;
+        mLocalVdm = LocalServices.getService(VirtualDeviceManagerInternal.class);
     }
 
     /**
@@ -89,6 +154,12 @@
             Slog.v(LOG_TAG, "Register proxy for display id: " + displayId);
         }
 
+        VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class);
+        if (vdm == null) {
+            return;
+        }
+        final int deviceId = vdm.getDeviceIdForDisplayId(displayId);
+
         // Set a default AccessibilityServiceInfo that is used before the proxy's info is
         // populated. A proxy has the touch exploration and window capabilities.
         AccessibilityServiceInfo info = new AccessibilityServiceInfo();
@@ -101,7 +172,7 @@
                 new ProxyAccessibilityServiceConnection(context, info.getComponentName(), info,
                         id, mainHandler, mLock, securityPolicy, systemSupport, trace,
                         windowManagerInternal,
-                        mA11yWindowManager, displayId);
+                        mA11yWindowManager, displayId, deviceId);
 
         synchronized (mLock) {
             mProxyA11yServiceConnections.put(displayId, connection);
@@ -113,20 +184,16 @@
                     @Override
                     public void binderDied() {
                         client.asBinder().unlinkToDeath(this, 0);
-                        clearConnection(displayId);
+                        clearConnectionAndUpdateState(displayId);
                     }
                 };
         client.asBinder().linkToDeath(deathRecipient, 0);
 
-        // Notify apps that the service state has changed.
-        // A11yManager#A11yServicesStateChangeListener
-        synchronized (mLock) {
-            connection.mSystemSupport.onClientChangeLocked(true);
-        }
-
-        if (mA11yInputFilter != null) {
-            mA11yInputFilter.disableFeaturesForDisplayIfInstalled(displayId);
-        }
+        mMainHandler.post(() -> {
+            if (mA11yInputFilter != null) {
+                mA11yInputFilter.disableFeaturesForDisplayIfInstalled(displayId);
+            }
+        });
         connection.initializeServiceInterface(client);
     }
 
@@ -134,38 +201,101 @@
      * Unregister the proxy based on display id.
      */
     public boolean unregisterProxy(int displayId) {
-        return clearConnection(displayId);
-    }
-
-    private boolean clearConnection(int displayId) {
-        boolean removed = false;
-        synchronized (mLock) {
-            if (mProxyA11yServiceConnections.contains(displayId)) {
-                mProxyA11yServiceConnections.remove(displayId);
-                removed = true;
-                if (DEBUG) {
-                    Slog.v(LOG_TAG, "Unregister proxy for display id " + displayId);
-                }
-            }
-        }
-        if (removed) {
-            mA11yWindowManager.stopTrackingDisplayProxy(displayId);
-            if (mA11yInputFilter != null) {
-                final DisplayManager displayManager = (DisplayManager)
-                        mContext.getSystemService(Context.DISPLAY_SERVICE);
-                final Display proxyDisplay = displayManager.getDisplay(displayId);
-                if (proxyDisplay != null) {
-                    mA11yInputFilter.enableFeaturesForDisplayIfInstalled(proxyDisplay);
-                }
-            }
-        }
-        return removed;
+        return clearConnectionAndUpdateState(displayId);
     }
 
     /**
-     * Checks if a display id is being proxy-ed.
+     * Clears all proxy connections belonging to {@code deviceId}.
      */
-    public boolean isProxyed(int displayId) {
+    public void clearConnections(int deviceId) {
+        final IntArray displaysToClear = new IntArray();
+        synchronized (mLock) {
+            for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+                final ProxyAccessibilityServiceConnection proxy =
+                        mProxyA11yServiceConnections.valueAt(i);
+                if (proxy != null && proxy.getDeviceId() == deviceId) {
+                    displaysToClear.add(proxy.getDisplayId());
+                }
+            }
+        }
+        for (int i = 0; i < displaysToClear.size(); i++) {
+            clearConnectionAndUpdateState(displaysToClear.get(i));
+        }
+    }
+
+    /**
+     * Removes the system connection of an AccessibilityDisplayProxy.
+     *
+     * This will:
+     * <ul>
+     * <li> Reset Clients to belong to the default device if appropriate.
+     * <li> Stop identifying the display's a11y windows as belonging to a proxy.
+     * <li> Re-enable any input filters for the display.
+     * <li> Notify AMS that a proxy has been removed.
+     * </ul>
+     *
+     * @param displayId the display id of the connection to be cleared.
+     * @return whether the proxy was removed.
+     */
+    private boolean clearConnectionAndUpdateState(int displayId) {
+        boolean removedFromConnections = false;
+        int deviceId = DEVICE_ID_INVALID;
+        synchronized (mLock) {
+            if (mProxyA11yServiceConnections.contains(displayId)) {
+                deviceId = mProxyA11yServiceConnections.get(displayId).getDeviceId();
+                mProxyA11yServiceConnections.remove(displayId);
+                removedFromConnections = true;
+            }
+        }
+
+        if (removedFromConnections) {
+            updateStateForRemovedDisplay(displayId, deviceId);
+        }
+
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "Unregistered proxy for display id " + displayId + ": "
+                    + removedFromConnections);
+        }
+        return removedFromConnections;
+    }
+
+    /**
+     * When the connection is removed from tracking in ProxyManager, propagate changes to other a11y
+     * system components like the input filter and IAccessibilityManagerClients.
+     */
+    public void updateStateForRemovedDisplay(int displayId, int deviceId) {
+        mA11yWindowManager.stopTrackingDisplayProxy(displayId);
+        // A11yInputFilter isn't thread-safe, so post on the system thread.
+        mMainHandler.post(
+                () -> {
+                    if (mA11yInputFilter != null) {
+                        final DisplayManager displayManager = (DisplayManager)
+                                mContext.getSystemService(Context.DISPLAY_SERVICE);
+                        final Display proxyDisplay = displayManager.getDisplay(displayId);
+                        if (proxyDisplay != null) {
+                            // A11yInputFilter isn't thread-safe, so post on the system thread.
+                            mA11yInputFilter.enableFeaturesForDisplayIfInstalled(proxyDisplay);
+                        }
+                    }
+                });
+        // If there isn't an existing proxy for the device id, reset clients. Resetting
+        // will usually happen, since in most cases there will only be one proxy for a
+        // device.
+        if (!isProxyedDeviceId(deviceId)) {
+            synchronized (mLock) {
+                mSystemSupport.removeDeviceIdLocked(deviceId);
+                mLastStates.delete(deviceId);
+            }
+        } else {
+            // Update with the states of the remaining proxies.
+            onProxyChanged(deviceId);
+        }
+    }
+
+    /**
+     * Returns {@code true} if {@code displayId} is being proxy-ed.
+     */
+    public boolean isProxyedDisplay(int displayId) {
         synchronized (mLock) {
             final boolean tracked = mProxyA11yServiceConnections.contains(displayId);
             if (DEBUG) {
@@ -176,6 +306,23 @@
     }
 
     /**
+     * Returns {@code true} if {@code deviceId} is being proxy-ed.
+     */
+    public boolean isProxyedDeviceId(int deviceId) {
+        if (deviceId == DEVICE_ID_DEFAULT && deviceId == DEVICE_ID_INVALID) {
+            return false;
+        }
+        boolean isTrackingDeviceId;
+        synchronized (mLock) {
+            isTrackingDeviceId = getFirstProxyForDeviceIdLocked(deviceId) != null;
+        }
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "Tracking device " + deviceId + " : " + isTrackingDeviceId);
+        }
+        return isTrackingDeviceId;
+    }
+
+    /**
      * Sends AccessibilityEvents to a proxy given the event's displayId.
      */
     public void sendAccessibilityEventLocked(AccessibilityEvent event) {
@@ -213,15 +360,37 @@
     /**
      * If there is at least one proxy, accessibility is enabled.
      */
-    public int getStateLocked() {
+    public int getStateLocked(int deviceId, boolean automationRunning) {
         int clientState = 0;
-        final boolean a11yEnabled = mProxyA11yServiceConnections.size() > 0;
-        if (a11yEnabled) {
+        if (automationRunning) {
             clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
         }
         for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
             final ProxyAccessibilityServiceConnection proxy =
                     mProxyA11yServiceConnections.valueAt(i);
+            if (proxy != null && proxy.getDeviceId() == deviceId) {
+                // Combine proxy states.
+                clientState |= getStateForDisplayIdLocked(proxy);
+            }
+        }
+
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "For device id " + deviceId + " a11y is enabled: "
+                    + ((clientState & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0));
+            Slog.v(LOG_TAG, "For device id " + deviceId + " touch exploration is enabled: "
+                    + ((clientState & AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED)
+                            != 0));
+        }
+        return clientState;
+    }
+
+    /**
+     * If there is at least one proxy, accessibility is enabled.
+     */
+    public int getStateForDisplayIdLocked(ProxyAccessibilityServiceConnection proxy) {
+        int clientState = 0;
+        if (proxy != null) {
+            clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
             if (proxy.mRequestTouchExplorationMode) {
                 clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
             }
@@ -235,61 +404,396 @@
                             != 0));
         }
         return clientState;
-        // TODO(b/254545943): When A11yManager is separated, include support for other properties.
     }
 
     /**
-     * Gets the last state.
+     * Gets the last state for a device.
      */
-    public int getLastSentStateLocked() {
-        return mLastState;
+    public int getLastSentStateLocked(int deviceId) {
+        return mLastStates.get(deviceId, 0);
     }
 
     /**
-     * Sets the last state.
+     * Sets the last state for a device.
      */
-    public void setLastStateLocked(int proxyState) {
-        mLastState = proxyState;
+    public void setLastStateLocked(int deviceId, int proxyState) {
+        mLastStates.put(deviceId, proxyState);
     }
 
     /**
-     * Returns the relevant event types of every proxy.
-     * TODO(254545943): When A11yManager is separated, return based on the A11yManager display.
+     * Updates the relevant event types of the app clients that are shown on a display owned by the
+     * specified device.
+     *
+     * A client belongs to a device id, so event types (and other state) is determined by the device
+     * id. In most cases, a device owns a single display. But if multiple displays may belong to one
+     * Virtual Device, the app clients will get the aggregated event types for all proxy-ed displays
+     * belonging to a VirtualDevice.
      */
-    public int getRelevantEventTypesLocked() {
+    public void updateRelevantEventTypesLocked(int deviceId) {
+        if (!isProxyedDeviceId(deviceId)) {
+            return;
+        }
+        mMainHandler.post(() -> {
+            synchronized (mLock) {
+                broadcastToClientsLocked(ignoreRemoteException(client -> {
+                    int relevantEventTypes;
+                    if (client.mDeviceId == deviceId) {
+                        relevantEventTypes = computeRelevantEventTypesLocked(client);
+                        if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
+                            client.mLastSentRelevantEventTypes = relevantEventTypes;
+                            client.mCallback.setRelevantEventTypes(relevantEventTypes);
+                        }
+                    }
+                }));
+            }
+        });
+    }
+
+    /**
+     * Returns the relevant event types for a Client.
+     */
+    int computeRelevantEventTypesLocked(AccessibilityManagerService.Client client) {
         int relevantEventTypes = 0;
         for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
-            ProxyAccessibilityServiceConnection proxy =
+            final ProxyAccessibilityServiceConnection proxy =
                     mProxyA11yServiceConnections.valueAt(i);
-            relevantEventTypes |= proxy.getRelevantEventTypes();
+            if (proxy != null && proxy.getDeviceId() == client.mDeviceId) {
+                relevantEventTypes |= proxy.getRelevantEventTypes();
+                relevantEventTypes |= AccessibilityManagerService.isClientInPackageAllowlist(
+                        mUiAutomationManager.getServiceInfo(), client)
+                        ? mUiAutomationManager.getRelevantEventTypes()
+                        : 0;
+            }
         }
         if (DEBUG) {
-            Slog.v(LOG_TAG, "Relevant event types for all proxies: "
-                    + AccessibilityEvent.eventTypeToString(relevantEventTypes));
+            Slog.v(LOG_TAG, "Relevant event types for device id " + client.mDeviceId
+                    + ": " + AccessibilityEvent.eventTypeToString(relevantEventTypes));
         }
         return relevantEventTypes;
     }
 
     /**
-     * Gets the number of current proxy connections.
-     * @return
-     */
-    public int getNumProxysLocked() {
-        return mProxyA11yServiceConnections.size();
-    }
-
-    /**
      * Adds the service interfaces to a list.
-     * @param interfaces
+     * @param interfaces the list to add to.
+     * @param deviceId the device id of the interested app client.
      */
-    public void addServiceInterfacesLocked(List<IAccessibilityServiceClient> interfaces) {
+    public void addServiceInterfacesLocked(@NonNull List<IAccessibilityServiceClient> interfaces,
+            int deviceId) {
         for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
             final ProxyAccessibilityServiceConnection proxy =
                     mProxyA11yServiceConnections.valueAt(i);
-            final IBinder proxyBinder = proxy.mService;
-            final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface;
-            if ((proxyBinder != null) && (proxyInterface != null)) {
-                interfaces.add(proxyInterface);
+            if (proxy != null && proxy.getDeviceId() == deviceId) {
+                final IBinder proxyBinder = proxy.mService;
+                final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface;
+                if ((proxyBinder != null) && (proxyInterface != null)) {
+                    interfaces.add(proxyInterface);
+                }
+            }
+        }
+    }
+
+    /**
+     * Gets the list of installed and enabled services for a device id.
+     *
+     * Note: Multiple display proxies may belong to the same device.
+     */
+    public List<AccessibilityServiceInfo> getInstalledAndEnabledServiceInfosLocked(int feedbackType,
+            int deviceId) {
+        List<AccessibilityServiceInfo> serviceInfos = new ArrayList<>();
+        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+            final ProxyAccessibilityServiceConnection proxy =
+                    mProxyA11yServiceConnections.valueAt(i);
+            if (proxy != null && proxy.getDeviceId() == deviceId) {
+                // Return all proxy infos for ALL mask.
+                if (feedbackType == AccessibilityServiceInfo.FEEDBACK_ALL_MASK) {
+                    serviceInfos.addAll(proxy.getInstalledAndEnabledServices());
+                } else if ((proxy.mFeedbackType & feedbackType) != 0) {
+                    List<AccessibilityServiceInfo> proxyInfos =
+                            proxy.getInstalledAndEnabledServices();
+                    // Iterate through each info in the proxy.
+                    for (AccessibilityServiceInfo info : proxyInfos) {
+                        if ((info.feedbackType & feedbackType) != 0) {
+                            serviceInfos.add(info);
+                        }
+                    }
+                }
+            }
+        }
+        return serviceInfos;
+    }
+
+    /**
+     * Handles proxy changes.
+     *
+     * <p>
+     * Changes include if the proxy is unregistered, its service info list has
+     * changed, or its focus appearance has changed.
+     * <p>
+     * Some responses may include updating app clients. A client belongs to a device id, so state is
+     * determined by the device id. In most cases, a device owns a single display. But if multiple
+     * displays belong to one Virtual Device, the app clients will get a difference in
+     * behavior depending on what is being updated.
+     *
+     * The following state methods are updated for AccessibilityManager clients belonging to a
+     * proxied device:
+     * <ul>
+     * <li> A11yManager#setRelevantEventTypes - The combined event types of all proxies belonging to
+     * a device id.
+     * <li> A11yManager#setState - The combined states of all proxies belonging to a device id.
+     * <li> A11yManager#notifyServicesStateChanged(timeout) - The highest of all proxies belonging
+     * to a device id.
+     * <li> A11yManager#setFocusAppearance - The appearance of the most recently updated display id
+     * belonging to the device.
+     * </ul>
+     * This is similar to onUserStateChangeLocked and onClientChangeLocked, but does not require an
+     * A11yUserState and only checks proxy-relevant settings.
+     */
+    public void onProxyChanged(int deviceId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "onProxyChanged called for deviceId: " + deviceId);
+        }
+        //The following state updates are excluded:
+        //  - Input-related state
+        //  - Primary-device / hardware-specific state
+        synchronized (mLock) {
+            // A proxy may be registered after the client has been initialized in #addClient.
+            // For example, a user does not turn on accessibility until after the app has launched.
+            // Or the process was started with a default id context and should shift to a device.
+            // Update device ids of the clients if necessary.
+            updateDeviceIdsIfNeededLocked(deviceId);
+            // Start tracking of all displays if necessary.
+            mSystemSupport.updateWindowsForAccessibilityCallbackLocked();
+            // Calls A11yManager#setRelevantEventTypes (test these)
+            updateRelevantEventTypesLocked(deviceId);
+            // Calls A11yManager#setState
+            scheduleUpdateProxyClientsIfNeededLocked(deviceId);
+            //Calls A11yManager#notifyServicesStateChanged(timeout)
+            scheduleNotifyProxyClientsOfServicesStateChangeLocked(deviceId);
+            // Calls A11yManager#setFocusAppearance
+            updateFocusAppearanceLocked(deviceId);
+            mSystemSupport.notifyClearAccessibilityCacheLocked();
+        }
+    }
+
+    /**
+     * Updates the states of the app AccessibilityManagers.
+     */
+    public void scheduleUpdateProxyClientsIfNeededLocked(int deviceId) {
+        final int proxyState = getStateLocked(deviceId,
+                mUiAutomationManager.isUiAutomationRunningLocked());
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "State for device id " + deviceId + " is " + proxyState);
+            Slog.v(LOG_TAG, "Last state for device id " + deviceId + " is "
+                    + getLastSentStateLocked(deviceId));
+        }
+        if ((getLastSentStateLocked(deviceId)) != proxyState) {
+            setLastStateLocked(deviceId, proxyState);
+            mMainHandler.post(() -> {
+                synchronized (mLock) {
+                    broadcastToClientsLocked(ignoreRemoteException(client -> {
+                        if (client.mDeviceId == deviceId) {
+                            client.mCallback.setState(proxyState);
+                        }
+                    }));
+                }
+            });
+        }
+    }
+
+    /**
+     * Notifies AccessibilityManager of services state changes, which includes changes to the
+     * list of service infos and timeouts.
+     *
+     * @see AccessibilityManager.AccessibilityServicesStateChangeListener
+     */
+    public void scheduleNotifyProxyClientsOfServicesStateChangeLocked(int deviceId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "Notify services state change at device id " + deviceId);
+        }
+        mMainHandler.post(()-> {
+            broadcastToClientsLocked(ignoreRemoteException(client -> {
+                if (client.mDeviceId == deviceId) {
+                    synchronized (mLock) {
+                        client.mCallback.notifyServicesStateChanged(
+                                getRecommendedTimeoutMillisLocked(deviceId));
+                    }
+                }
+            }));
+        });
+    }
+
+    /**
+     * Updates the focus appearance of AccessibilityManagerClients.
+     */
+    public void updateFocusAppearanceLocked(int deviceId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "Update proxy focus appearance at device id " + deviceId);
+        }
+        // Reasonably assume that all proxies belonging to a virtual device should have the
+        // same focus appearance, and if they should be different these should belong to different
+        // virtual devices.
+        final ProxyAccessibilityServiceConnection proxy = getFirstProxyForDeviceIdLocked(deviceId);
+        if (proxy != null) {
+            mMainHandler.post(()-> {
+                broadcastToClientsLocked(ignoreRemoteException(client -> {
+                    if (client.mDeviceId == proxy.getDeviceId()) {
+                        client.mCallback.setFocusAppearance(
+                                proxy.getFocusStrokeWidthLocked(),
+                                proxy.getFocusColorLocked());
+                    }
+                }));
+            });
+        }
+    }
+
+    private ProxyAccessibilityServiceConnection getFirstProxyForDeviceIdLocked(int deviceId) {
+        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+            final ProxyAccessibilityServiceConnection proxy =
+                    mProxyA11yServiceConnections.valueAt(i);
+            if (proxy != null && proxy.getDeviceId() == deviceId) {
+                return proxy;
+            }
+        }
+        return null;
+    }
+
+    private void broadcastToClientsLocked(
+            @NonNull Consumer<AccessibilityManagerService.Client> clientAction) {
+        final RemoteCallbackList<IAccessibilityManagerClient> userClients =
+                mSystemSupport.getCurrentUserClientsLocked();
+        final RemoteCallbackList<IAccessibilityManagerClient> globalClients =
+                mSystemSupport.getGlobalClientsLocked();
+        userClients.broadcastForEachCookie(clientAction);
+        globalClients.broadcastForEachCookie(clientAction);
+    }
+
+    /**
+     * Updates the timeout and notifies app clients.
+     *
+     * For real users, timeouts are tracked in A11yUserState. For proxies, timeouts are in the
+     * service connection. The value in user state is preferred, but if this value is 0 the service
+     * info value is used.
+     *
+     * This follows the pattern in readUserRecommendedUiTimeoutSettingsLocked.
+     *
+     * TODO(b/250929565): ProxyUserState or similar should hold the timeouts
+     */
+    public void updateTimeoutsIfNeeded(int nonInteractiveUiTimeout, int interactiveUiTimeout) {
+        synchronized (mLock) {
+            for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+                final ProxyAccessibilityServiceConnection proxy =
+                        mProxyA11yServiceConnections.valueAt(i);
+                if (proxy != null) {
+                    if (proxy.updateTimeouts(nonInteractiveUiTimeout, interactiveUiTimeout)) {
+                        scheduleNotifyProxyClientsOfServicesStateChangeLocked(proxy.getDeviceId());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Gets the recommended timeout belonging to a Virtual Device.
+     *
+     * This is the highest of all display proxies belonging to the virtual device.
+     */
+    public long getRecommendedTimeoutMillisLocked(int deviceId) {
+        int combinedInteractiveTimeout = 0;
+        int combinedNonInteractiveTimeout = 0;
+        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+            final ProxyAccessibilityServiceConnection proxy =
+                    mProxyA11yServiceConnections.valueAt(i);
+            if (proxy != null && proxy.getDeviceId() == deviceId) {
+                final int proxyInteractiveUiTimeout =
+                        (proxy != null) ? proxy.getInteractiveTimeout() : 0;
+                final int nonInteractiveUiTimeout =
+                        (proxy != null) ? proxy.getNonInteractiveTimeout() : 0;
+                combinedInteractiveTimeout = Math.max(proxyInteractiveUiTimeout,
+                        combinedInteractiveTimeout);
+                combinedNonInteractiveTimeout = Math.max(nonInteractiveUiTimeout,
+                        combinedNonInteractiveTimeout);
+            }
+        }
+        return IntPair.of(combinedInteractiveTimeout, combinedNonInteractiveTimeout);
+    }
+
+    /**
+     * Gets the first focus stroke width belonging to the device.
+     */
+    public int getFocusStrokeWidthLocked(int deviceId) {
+        final ProxyAccessibilityServiceConnection proxy = getFirstProxyForDeviceIdLocked(deviceId);
+        if (proxy != null) {
+            return proxy.getFocusStrokeWidthLocked();
+        }
+        return 0;
+
+    }
+
+    /**
+     * Gets the first focus color belonging to the device.
+     */
+    public int getFocusColorLocked(int deviceId) {
+        final ProxyAccessibilityServiceConnection proxy = getFirstProxyForDeviceIdLocked(deviceId);
+        if (proxy != null) {
+            return proxy.getFocusColorLocked();
+        }
+        return 0;
+    }
+
+    /**
+     * Returns the first device id given a UID.
+     * @param callingUid the UID to check.
+     * @return the first matching device id, or DEVICE_ID_INVALID.
+     */
+    public int getFirstDeviceIdForUidLocked(int callingUid) {
+        int firstDeviceId = DEVICE_ID_INVALID;
+        final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+        if (localVdm == null) {
+            return firstDeviceId;
+        }
+        final Set<Integer> deviceIds = localVdm.getDeviceIdsForUid(callingUid);
+        for (Integer uidDeviceId : deviceIds) {
+            if (uidDeviceId != DEVICE_ID_DEFAULT && uidDeviceId != DEVICE_ID_INVALID) {
+                firstDeviceId = uidDeviceId;
+                break;
+            }
+        }
+        return firstDeviceId;
+    }
+
+    /**
+     * Sets a Client device id if the app uid belongs to the virtual device.
+     */
+    public void updateDeviceIdsIfNeededLocked(int deviceId) {
+        final RemoteCallbackList<IAccessibilityManagerClient> userClients =
+                mSystemSupport.getCurrentUserClientsLocked();
+        final RemoteCallbackList<IAccessibilityManagerClient> globalClients =
+                mSystemSupport.getGlobalClientsLocked();
+
+        updateDeviceIdsIfNeededLocked(deviceId, userClients);
+        updateDeviceIdsIfNeededLocked(deviceId, globalClients);
+    }
+
+    /**
+     * Updates the device ids of IAccessibilityManagerClients if needed.
+     */
+    public void updateDeviceIdsIfNeededLocked(int deviceId,
+            @NonNull RemoteCallbackList<IAccessibilityManagerClient> clients) {
+        final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+        if (localVdm == null) {
+            return;
+        }
+
+        for (int i = 0; i < clients.getRegisteredCallbackCount(); i++) {
+            final AccessibilityManagerService.Client client =
+                    ((AccessibilityManagerService.Client) clients.getRegisteredCallbackCookie(i));
+            if (deviceId != DEVICE_ID_DEFAULT && deviceId != DEVICE_ID_INVALID
+                    && localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId)) {
+                if (DEBUG) {
+                    Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
+                            + Arrays.toString(client.mPackageNames));
+                }
+                client.mDeviceId = deviceId;
             }
         }
     }
@@ -306,9 +810,18 @@
     }
 
     void setAccessibilityInputFilter(AccessibilityInputFilter filter) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "Set proxy input filter to " + filter);
+        }
         mA11yInputFilter = filter;
     }
 
+    VirtualDeviceManagerInternal getLocalVdm() {
+        if (mLocalVdm == null) {
+            mLocalVdm =  LocalServices.getService(VirtualDeviceManagerInternal.class);
+        }
+        return mLocalVdm;
+    }
 
     /**
      * Prints information belonging to each display that is controlled by an
@@ -320,13 +833,38 @@
             pw.println("Proxy manager state:");
             pw.println("    Number of proxy connections: " + mProxyA11yServiceConnections.size());
             pw.println("    Registered proxy connections:");
+            final RemoteCallbackList<IAccessibilityManagerClient> userClients =
+                    mSystemSupport.getCurrentUserClientsLocked();
+            final RemoteCallbackList<IAccessibilityManagerClient> globalClients =
+                    mSystemSupport.getGlobalClientsLocked();
             for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
                 final ProxyAccessibilityServiceConnection proxy =
                         mProxyA11yServiceConnections.valueAt(i);
                 if (proxy != null) {
                     proxy.dump(fd, pw, args);
                 }
+                pw.println();
+                pw.println("        User clients for proxy's virtual device id");
+                printClientsForDeviceId(pw, userClients, proxy.getDeviceId());
+                pw.println();
+                pw.println("        Global clients for proxy's virtual device id");
+                printClientsForDeviceId(pw, globalClients, proxy.getDeviceId());
+
             }
         }
     }
-}
\ No newline at end of file
+
+    private void printClientsForDeviceId(PrintWriter pw,
+            RemoteCallbackList<IAccessibilityManagerClient> clients, int deviceId) {
+        if (clients != null) {
+            for (int j = 0; j < clients.getRegisteredCallbackCount(); j++) {
+                final AccessibilityManagerService.Client client =
+                        (AccessibilityManagerService.Client)
+                                clients.getRegisteredCallbackCookie(j);
+                if (client.mDeviceId == deviceId) {
+                    pw.println("            " + Arrays.toString(client.mPackageNames) + "\n");
+                }
+            }
+        }
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index f3a949d..dd7d38f 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -133,18 +133,6 @@
             @UserIdInt int userId, int associationId) {
         final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
 
-        // Check if the request's data type has been requested before.
-        List<SystemDataTransferRequest> storedRequests =
-                mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
-                        associationId);
-        for (SystemDataTransferRequest storedRequest : storedRequests) {
-            if (storedRequest instanceof PermissionSyncRequest) {
-                Slog.e(LOG_TAG, "The request has been sent before, you can not send "
-                        + "the same request type again.");
-                return null;
-            }
-        }
-
         Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId
                 + "] associationId [" + associationId + "]");
 
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index b338d89..1363ef3 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -1046,18 +1046,30 @@
      */
     void showToastWhereUidIsRunning(int uid, String text, @Toast.Duration int duration,
             Looper looper) {
+        ArrayList<Integer> displayIdsForUid = getDisplayIdsWhereUidIsRunning(uid);
+        if (displayIdsForUid.isEmpty()) {
+            return;
+        }
+        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+        for (int i = 0; i < displayIdsForUid.size(); i++) {
+            Display display = displayManager.getDisplay(displayIdsForUid.get(i));
+            if (display != null && display.isValid()) {
+                Toast.makeText(mContext.createDisplayContext(display), looper, text,
+                        duration).show();
+            }
+        }
+    }
+
+    private ArrayList<Integer> getDisplayIdsWhereUidIsRunning(int uid) {
+        ArrayList<Integer> displayIdsForUid = new ArrayList<>();
         synchronized (mVirtualDeviceLock) {
-            DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
             for (int i = 0; i < mVirtualDisplays.size(); i++) {
                 if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
-                    Display display = displayManager.getDisplay(mVirtualDisplays.keyAt(i));
-                    if (display != null && display.isValid()) {
-                        Toast.makeText(mContext.createDisplayContext(display), looper, text,
-                                duration).show();
-                    }
+                    displayIdsForUid.add(mVirtualDisplays.keyAt(i));
                 }
             }
         }
+        return displayIdsForUid;
     }
 
     boolean isDisplayOwnedByVirtualDevice(int displayId) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 8fe61e7..78cbf2b 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -807,9 +807,9 @@
         ServiceRecord r = res.record;
         // Note, when startService() or startForegroundService() is called on an already
         // running SHORT_SERVICE FGS, the call will succeed (i.e. we won't throw
-        // ForegroundServiceStartNotAllowedException), even when the service is alerady timed
-        // out. This is because these APIs will essnetially only change the "started" state
-        // of the service, and it won't afect "the foreground-ness" of the service, or the type
+        // ForegroundServiceStartNotAllowedException), even when the service is already timed
+        // out. This is because these APIs will essentially only change the "started" state
+        // of the service, and it won't affect "the foreground-ness" of the service, or the type
         // of the FGS.
         // However, this call will still _not_ extend the SHORT_SERVICE timeout either.
         // Also, if the app tries to change the type of the FGS later (using
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 82c4796a..553706d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -152,7 +152,7 @@
     static final String KEY_USE_TIERED_CACHED_ADJ = "use_tiered_cached_adj";
     static final String KEY_TIERED_CACHED_ADJ_DECAY_TIME = "tiered_cached_adj_decay_time";
 
-    private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
+    private static final int DEFAULT_MAX_CACHED_PROCESSES = 1024;
     private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true;
     private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
     private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000;
@@ -876,7 +876,7 @@
     private static final String KEY_MAX_EMPTY_TIME_MILLIS =
             "max_empty_time_millis";
 
-    private static final long DEFAULT_MAX_EMPTY_TIME_MILLIS = 30 * 60 * 1000;
+    private static final long DEFAULT_MAX_EMPTY_TIME_MILLIS = 1000L * 60L * 60L * 1000L;
 
     volatile long mMaxEmptyTimeMillis = DEFAULT_MAX_EMPTY_TIME_MILLIS;
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 9079ba8..5dd0a3f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -16,6 +16,11 @@
 
 package com.android.server.am;
 
+import android.util.Log;
+import android.util.LogWriter;
+
+import java.io.PrintWriter;
+
 /**
  * Common class for the various debug {@link android.util.Log} output configuration in the activity
  * manager package.
@@ -38,6 +43,10 @@
     // Default log tag for the activity manager package.
     static final String TAG_AM = "ActivityManager";
 
+    // Default writer that emits "info" log events for the activity manager package.
+    static final PrintWriter LOG_WRITER_INFO = new PrintWriter(
+            new LogWriter(Log.INFO, TAG_AM));
+
     // Enable all debug log categories.
     static final boolean DEBUG_ALL = false;
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 74676cf..06e6df9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -135,6 +135,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
+import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP;
@@ -13650,8 +13651,9 @@
                                 || action.startsWith("android.intent.action.UID_")
                                 || action.startsWith("android.intent.action.EXTERNAL_")) {
                             if (DEBUG_BROADCAST) {
-                                Slog.wtf(TAG, "System internals registering for " + filter
-                                        + " with app priority; this will race with apps!",
+                                Slog.wtf(TAG,
+                                        "System internals registering for " + filter.toLongString()
+                                                + " with app priority; this will race with apps!",
                                         new Throwable());
                             }
 
@@ -13746,17 +13748,6 @@
                                 + "RECEIVER_NOT_EXPORTED flag");
             }
 
-            // STOPSHIP(b/259139792): Allow apps that are currently targeting U and in process of
-            // updating their receivers to be exempt from this requirement until their receivers
-            // are flagged.
-            if (requireExplicitFlagForDynamicReceivers) {
-                if ("com.shannon.imsservice".equals(callerPackage)) {
-                    // Note, a versionCode check for this package is not performed because this
-                    // package consumes the SecurityException, so it wouldn't be caught during
-                    // presubmit.
-                    requireExplicitFlagForDynamicReceivers = false;
-                }
-            }
             if (!onlyProtectedBroadcasts) {
                 if (receiver == null && !explicitExportStateDefined) {
                     // sticky broadcast, no flag specified (flag isn't required)
@@ -18725,27 +18716,25 @@
 
     @Override
     public void waitForBroadcastIdle() {
-        waitForBroadcastIdle(/* printWriter= */ null);
+        waitForBroadcastIdle(LOG_WRITER_INFO);
     }
 
-    public void waitForBroadcastIdle(@Nullable PrintWriter pw) {
+    public void waitForBroadcastIdle(@NonNull PrintWriter pw) {
         enforceCallingPermission(permission.DUMP, "waitForBroadcastIdle()");
         BroadcastLoopers.waitForIdle(pw);
         for (BroadcastQueue queue : mBroadcastQueues) {
             queue.waitForIdle(pw);
         }
-        if (pw != null) {
-            pw.println("All broadcast queues are idle!");
-            pw.flush();
-        }
+        pw.println("All broadcast queues are idle!");
+        pw.flush();
     }
 
     @Override
     public void waitForBroadcastBarrier() {
-        waitForBroadcastBarrier(/* printWriter= */ null, false, false);
+        waitForBroadcastBarrier(LOG_WRITER_INFO, false, false);
     }
 
-    public void waitForBroadcastBarrier(@Nullable PrintWriter pw,
+    public void waitForBroadcastBarrier(@NonNull PrintWriter pw,
             boolean flushBroadcastLoopers, boolean flushApplicationThreads) {
         enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
         if (flushBroadcastLoopers) {
@@ -18763,11 +18752,7 @@
      * Wait for all pending {@link IApplicationThread} events to be processed in
      * all currently running apps.
      */
-    public void waitForApplicationBarrier(@Nullable PrintWriter pw) {
-        if (pw == null) {
-            pw = new PrintWriter(new LogWriter(Log.VERBOSE, TAG));
-        }
-
+    public void waitForApplicationBarrier(@NonNull PrintWriter pw) {
         final CountDownLatch finishedLatch = new CountDownLatch(1);
         final AtomicInteger pingCount = new AtomicInteger(0);
         final AtomicInteger pongCount = new AtomicInteger(0);
@@ -18815,15 +18800,18 @@
             try {
                 if (finishedLatch.await(1, TimeUnit.SECONDS)) {
                     pw.println("Finished application barriers!");
+                    pw.flush();
                     return;
                 } else {
                     pw.println("Waiting for application barriers, at " + pongCount.get() + " of "
                             + pingCount.get() + "...");
+                    pw.flush();
                 }
             } catch (InterruptedException ignored) {
             }
         }
         pw.println("Gave up waiting for application barriers!");
+        pw.flush();
     }
 
     void setIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 72e17d8..350ac3b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -38,6 +38,7 @@
 import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW;
 import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_MODERATE;
 import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL;
+import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_COUNT;
@@ -112,6 +113,7 @@
 import android.util.ArraySet;
 import android.util.DebugUtils;
 import android.util.DisplayMetrics;
+import android.util.TeeWriter;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 import android.window.SplashScreen;
@@ -1093,7 +1095,7 @@
                 synchronized (mInternal.mProcLock) {
                     mInternal.mOomAdjuster.mCachedAppOptimizer.compactApp(app,
                             CachedAppOptimizer.CompactProfile.FULL,
-                            CachedAppOptimizer.CompactSource.APP, true);
+                            CachedAppOptimizer.CompactSource.SHELL, true);
                 }
                 pw.println("Finished full compaction for " + app.mPid);
             } else if (isSomeCompact) {
@@ -1101,7 +1103,7 @@
                 synchronized (mInternal.mProcLock) {
                     mInternal.mOomAdjuster.mCachedAppOptimizer.compactApp(app,
                             CachedAppOptimizer.CompactProfile.SOME,
-                            CachedAppOptimizer.CompactSource.APP, true);
+                            CachedAppOptimizer.CompactSource.SHELL, true);
                 }
                 pw.println("Finished some compaction for " + app.mPid);
             }
@@ -3364,11 +3366,13 @@
     }
 
     int runWaitForBroadcastIdle(PrintWriter pw) throws RemoteException {
+        pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw));
         mInternal.waitForBroadcastIdle(pw);
         return 0;
     }
 
     int runWaitForBroadcastBarrier(PrintWriter pw) throws RemoteException {
+        pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw));
         boolean flushBroadcastLoopers = false;
         boolean flushApplicationThreads = false;
         String opt;
@@ -3387,6 +3391,7 @@
     }
 
     int runWaitForApplicationBarrier(PrintWriter pw) throws RemoteException {
+        pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw));
         mInternal.waitForApplicationBarrier(pw);
         return 0;
     }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index d9ba845..ed297d0 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -654,6 +654,7 @@
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             mHandler.post(() -> {
+                mCpuWakeupStats.onUidRemoved(uid);
                 synchronized (mStats) {
                     mStats.removeUidStatsLocked(uid, elapsedRealtime);
                 }
@@ -764,6 +765,7 @@
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
             mHandler.post(() -> {
+                mCpuWakeupStats.noteUidProcessState(uid, state);
                 synchronized (mStats) {
                     mStats.noteUidProcessStateLocked(uid, state, elapsedRealtime, uptime);
                 }
diff --git a/services/core/java/com/android/server/am/BroadcastLoopers.java b/services/core/java/com/android/server/am/BroadcastLoopers.java
index a5535cb..92547ea 100644
--- a/services/core/java/com/android/server/am/BroadcastLoopers.java
+++ b/services/core/java/com/android/server/am/BroadcastLoopers.java
@@ -17,7 +17,6 @@
 package com.android.server.am;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -74,7 +73,7 @@
      * defined by {@link MessageQueue#isIdle()}. Note that {@link Message#when}
      * still in the future are ignored for the purposes of the idle test.
      */
-    public static void waitForIdle(@Nullable PrintWriter pw) {
+    public static void waitForIdle(@NonNull PrintWriter pw) {
         waitForCondition(pw, (looper, latch) -> {
             final MessageQueue queue = looper.getQueue();
             queue.addIdleHandler(() -> {
@@ -89,7 +88,7 @@
      * Note that {@link Message#when} still in the future are ignored for the purposes
      * of the idle test.
      */
-    public static void waitForBarrier(@Nullable PrintWriter pw) {
+    public static void waitForBarrier(@NonNull PrintWriter pw) {
         waitForCondition(pw, (looper, latch) -> {
             (new Handler(looper)).post(() -> {
                 latch.countDown();
@@ -100,7 +99,7 @@
     /**
      * Wait for all registered {@link Looper} instances to meet a certain condition.
      */
-    private static void waitForCondition(@Nullable PrintWriter pw,
+    private static void waitForCondition(@NonNull PrintWriter pw,
             @NonNull BiConsumer<Looper, CountDownLatch> condition) {
         final CountDownLatch latch;
         synchronized (sLoopers) {
@@ -122,18 +121,12 @@
             final long now = SystemClock.uptimeMillis();
             if (now >= lastPrint + 1000) {
                 lastPrint = now;
-                logv("Waiting for " + latch.getCount() + " loopers to drain...", pw);
+                pw.println("Waiting for " + latch.getCount() + " loopers to drain...");
+                pw.flush();
             }
             SystemClock.sleep(100);
         }
-        logv("Loopers drained!", pw);
-    }
-
-    private static void logv(@NonNull String msg, @Nullable PrintWriter pw) {
-        Slog.v(TAG, msg);
-        if (pw != null) {
-            pw.println(msg);
-            pw.flush();
-        }
+        pw.println("Loopers drained!");
+        pw.flush();
     }
 }
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 056e17a..48ab96c 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -864,6 +864,7 @@
     static final int REASON_CONTAINS_RESULT_TO = 15;
     static final int REASON_CONTAINS_INSTRUMENTED = 16;
     static final int REASON_CONTAINS_MANIFEST = 17;
+    static final int REASON_FOREGROUND_ACTIVITIES = 18;
 
     @IntDef(flag = false, prefix = { "REASON_" }, value = {
             REASON_EMPTY,
@@ -883,6 +884,7 @@
             REASON_CONTAINS_RESULT_TO,
             REASON_CONTAINS_INSTRUMENTED,
             REASON_CONTAINS_MANIFEST,
+            REASON_FOREGROUND_ACTIVITIES,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Reason {}
@@ -906,6 +908,7 @@
             case REASON_CONTAINS_RESULT_TO: return "CONTAINS_RESULT_TO";
             case REASON_CONTAINS_INSTRUMENTED: return "CONTAINS_INSTRUMENTED";
             case REASON_CONTAINS_MANIFEST: return "CONTAINS_MANIFEST";
+            case REASON_FOREGROUND_ACTIVITIES: return "FOREGROUND_ACTIVITIES";
             default: return Integer.toString(reason);
         }
     }
@@ -963,6 +966,11 @@
             } else if (mProcessInstrumented) {
                 mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
                 mRunnableAtReason = REASON_INSTRUMENTED;
+            } else if (app != null && app.hasForegroundActivities()) {
+                // TODO: Listen for uid state changes to check when an uid goes in and out of
+                // the TOP state.
+                mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
+                mRunnableAtReason = REASON_FOREGROUND_ACTIVITIES;
             } else if (mCountOrdered > 0) {
                 mRunnableAt = runnableAt;
                 mRunnableAtReason = REASON_CONTAINS_ORDERED;
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 75e9336..6d1344d 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -69,14 +69,6 @@
         Slog.v(TAG, msg);
     }
 
-    static void logv(@NonNull String msg, @Nullable PrintWriter pw) {
-        logv(msg);
-        if (pw != null) {
-            pw.println(msg);
-            pw.flush();
-        }
-    }
-
     static void checkState(boolean expression, @NonNull String msg) {
         if (!expression) {
             throw new IllegalStateException(msg);
@@ -219,7 +211,7 @@
      * since running apps can continue sending new broadcasts in perpetuity;
      * consider using {@link #waitForBarrier} instead.
      */
-    public abstract void waitForIdle(@Nullable PrintWriter pw);
+    public abstract void waitForIdle(@NonNull PrintWriter pw);
 
     /**
      * Wait until any currently waiting broadcasts have been dispatched.
@@ -230,7 +222,7 @@
      * Callers are advised that this method will <em>not</em> wait for any
      * future broadcasts that are newly enqueued after being invoked.
      */
-    public abstract void waitForBarrier(@Nullable PrintWriter pw);
+    public abstract void waitForBarrier(@NonNull PrintWriter pw);
 
     /**
      * Delays delivering broadcasts to the specified package.
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 4b8dc99..bd36c3f 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -835,7 +835,7 @@
                         OOM_ADJ_REASON_START_RECEIVER);
             }
         } else if (filter.receiverList.app != null) {
-            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(filter.receiverList.app,
+            mService.mOomAdjuster.unfreezeTemporarily(filter.receiverList.app,
                     CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER);
         }
 
@@ -1129,7 +1129,7 @@
                     }
                     if (sendResult) {
                         if (r.callerApp != null) {
-                            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
+                            mService.mOomAdjuster.unfreezeTemporarily(
                                     r.callerApp,
                                     CachedAppOptimizer.UNFREEZE_REASON_FINISH_RECEIVER);
                         }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 7157fff..a1fccbc 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -28,6 +28,7 @@
 import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
 import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
 import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
 import static com.android.server.am.BroadcastProcessQueue.reasonToString;
 import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
@@ -927,7 +928,7 @@
         final ProcessRecord app = r.resultToApp;
         final IApplicationThread thread = (app != null) ? app.getOnewayThread() : null;
         if (thread != null) {
-            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
+            mService.mOomAdjuster.unfreezeTemporarily(
                     app, CachedAppOptimizer.UNFREEZE_REASON_FINISH_RECEIVER);
             if (r.shareIdentity && app.uid != r.callingUid) {
                 mService.mPackageManagerInt.grantImplicitAccess(r.userId, r.intent,
@@ -1247,7 +1248,7 @@
      * the given {@link Predicate}.
      */
     private boolean testAllProcessQueues(@NonNull Predicate<BroadcastProcessQueue> test,
-            @NonNull String label, @Nullable PrintWriter pw) {
+            @NonNull String label, @NonNull PrintWriter pw) {
         for (int i = 0; i < mProcessQueues.size(); i++) {
             BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
             while (leaf != null) {
@@ -1255,14 +1256,16 @@
                     final long now = SystemClock.uptimeMillis();
                     if (now > mLastTestFailureTime + DateUtils.SECOND_IN_MILLIS) {
                         mLastTestFailureTime = now;
-                        logv("Test " + label + " failed due to " + leaf.toShortString(), pw);
+                        pw.println("Test " + label + " failed due to " + leaf.toShortString());
+                        pw.flush();
                     }
                     return false;
                 }
                 leaf = leaf.processNameNext;
             }
         }
-        logv("Test " + label + " passed", pw);
+        pw.println("Test " + label + " passed");
+        pw.flush();
         return true;
     }
 
@@ -1349,30 +1352,30 @@
 
     @Override
     public boolean isIdleLocked() {
-        return isIdleLocked(null);
+        return isIdleLocked(LOG_WRITER_INFO);
     }
 
-    public boolean isIdleLocked(@Nullable PrintWriter pw) {
+    public boolean isIdleLocked(@NonNull PrintWriter pw) {
         return testAllProcessQueues(q -> q.isIdle(), "idle", pw);
     }
 
     @Override
     public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime) {
-        return isBeyondBarrierLocked(barrierTime, null);
+        return isBeyondBarrierLocked(barrierTime, LOG_WRITER_INFO);
     }
 
     public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime,
-            @Nullable PrintWriter pw) {
+            @NonNull PrintWriter pw) {
         return testAllProcessQueues(q -> q.isBeyondBarrierLocked(barrierTime), "barrier", pw);
     }
 
     @Override
-    public void waitForIdle(@Nullable PrintWriter pw) {
+    public void waitForIdle(@NonNull PrintWriter pw) {
         waitFor(() -> isIdleLocked(pw));
     }
 
     @Override
-    public void waitForBarrier(@Nullable PrintWriter pw) {
+    public void waitForBarrier(@NonNull PrintWriter pw) {
         final long now = SystemClock.uptimeMillis();
         waitFor(() -> isBeyondBarrierLocked(now, pw));
     }
@@ -1515,7 +1518,7 @@
                 mService.updateLruProcessLocked(queue.app, false, null);
             }
 
-            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(queue.app,
+            mService.mOomAdjuster.unfreezeTemporarily(queue.app,
                     CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER);
 
             if (queue.runningOomAdjusted) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 6c9f602..9c15463 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -198,7 +198,7 @@
     // Format of this string should be a comma separated list of integers.
     @VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
             String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
-    @VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 600_000L;
+    @VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 10_000L;
     @VisibleForTesting static final Boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = true;
 
     @VisibleForTesting static final Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor(
@@ -226,8 +226,8 @@
         FULL // File+anon compaction
     }
 
-    // This indicates the process OOM memory state that initiated the compaction request
-    public enum CompactSource { APP, PERSISTENT, BFGS }
+    // This indicates who initiated the compaction request
+    public enum CompactSource { APP, SHELL }
 
     public enum CancelCompactReason {
         SCREEN_ON, // screen was turned on which cancels all compactions.
@@ -373,10 +373,6 @@
     @GuardedBy("mPhenotypeFlagLock")
     @VisibleForTesting volatile long mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
     @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting volatile long mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5;
-    @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting volatile long mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
-    @GuardedBy("mPhenotypeFlagLock")
     @VisibleForTesting volatile long mCompactThrottleMinOomAdj =
             DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ;
     @GuardedBy("mPhenotypeFlagLock")
@@ -635,8 +631,6 @@
             pw.println("  " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull);
             pw.println("  " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome);
             pw.println("  " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull);
-            pw.println("  " + KEY_COMPACT_THROTTLE_5 + "=" + mCompactThrottleBFGS);
-            pw.println("  " + KEY_COMPACT_THROTTLE_6 + "=" + mCompactThrottlePersistent);
             pw.println("  " + KEY_COMPACT_THROTTLE_MIN_OOM_ADJ + "=" + mCompactThrottleMinOomAdj);
             pw.println("  " + KEY_COMPACT_THROTTLE_MAX_OOM_ADJ + "=" + mCompactThrottleMaxOomAdj);
             pw.println("  " + KEY_COMPACT_STATSD_SAMPLE_RATE + "=" + mCompactStatsdSampleRate);
@@ -728,32 +722,6 @@
         }
     }
 
-    // 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 (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")
     boolean compactApp(
             ProcessRecord app, CompactProfile compactProfile, CompactSource source, boolean force) {
@@ -777,7 +745,7 @@
                 return false;
         }
 
-        if (!app.mOptRecord.hasPendingCompact() && (meetsCompactionRequirements(app) || force)) {
+        if (!app.mOptRecord.hasPendingCompact()) {
             final String processName = (app.processName != null ? app.processName : "");
             if (DEBUG_COMPACTION) {
                 Slog.d(TAG_AM,
@@ -795,8 +763,7 @@
         if (DEBUG_COMPACTION) {
             Slog.d(TAG_AM,
                     " compactApp Skipped for " + app.processName + " pendingCompact= "
-                            + app.mOptRecord.hasPendingCompact() + " meetsCompactionRequirements="
-                            + meetsCompactionRequirements(app) + ". Requested compact profile: "
+                            + app.mOptRecord.hasPendingCompact() + ". Requested compact profile: "
                             + app.mOptRecord.getReqCompactProfile().name() + ". Compact source "
                             + app.mOptRecord.getReqCompactSource().name());
         }
@@ -831,18 +798,6 @@
         return stats;
     }
 
-    @GuardedBy("mProcLock")
-    boolean shouldCompactPersistent(ProcessRecord app, long now) {
-        return (app.mOptRecord.getLastCompactTime() == 0
-                || (now - app.mOptRecord.getLastCompactTime()) > mCompactThrottlePersistent);
-    }
-
-    @GuardedBy("mProcLock")
-    boolean shouldCompactBFGS(ProcessRecord app, long now) {
-        return (app.mOptRecord.getLastCompactTime() == 0
-                || (now - app.mOptRecord.getLastCompactTime()) > mCompactThrottleBFGS);
-    }
-
     void compactAllSystem() {
         if (useCompaction()) {
             if (DEBUG_COMPACTION) {
@@ -1130,8 +1085,6 @@
                 mCompactThrottleSomeFull = Integer.parseInt(throttleSomeFullFlag);
                 mCompactThrottleFullSome = Integer.parseInt(throttleFullSomeFlag);
                 mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag);
-                mCompactThrottleBFGS = Integer.parseInt(throttleBFGSFlag);
-                mCompactThrottlePersistent = Integer.parseInt(throttlePersistentFlag);
                 mCompactThrottleMinOomAdj = Long.parseLong(throttleMinOomAdjFlag);
                 mCompactThrottleMaxOomAdj = Long.parseLong(throttleMaxOomAdjFlag);
             } catch (NumberFormatException e) {
@@ -1144,8 +1097,6 @@
             mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
             mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
             mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
-            mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5;
-            mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
             mCompactThrottleMinOomAdj = DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ;
             mCompactThrottleMaxOomAdj = DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ;
         }
@@ -1497,23 +1448,23 @@
 
     @GuardedBy({"mService", "mProcLock"})
     void onOomAdjustChanged(int oldAdj, int newAdj, ProcessRecord app) {
-        // Cancel any currently executing compactions
-        // if the process moved out of cached state
-        if (newAdj < oldAdj && newAdj < ProcessList.CACHED_APP_MIN_ADJ) {
-            cancelCompactionForProcess(app, CancelCompactReason.OOM_IMPROVEMENT);
-        }
-
-        if (oldAdj <= ProcessList.PERCEPTIBLE_APP_ADJ
-                && (newAdj == ProcessList.PREVIOUS_APP_ADJ || newAdj == ProcessList.HOME_APP_ADJ)) {
-            if (ENABLE_FILE_COMPACT) {
-                // Perform a minor compaction when a perceptible app becomes the prev/home app
-                compactApp(app, CompactProfile.SOME, CompactSource.APP, false);
+        if (useCompaction()) {
+            // Cancel any currently executing compactions
+            // if the process moved out of cached state
+            if (newAdj < oldAdj && newAdj < ProcessList.CACHED_APP_MIN_ADJ) {
+                cancelCompactionForProcess(app, CancelCompactReason.OOM_IMPROVEMENT);
             }
-        } else if (oldAdj < ProcessList.CACHED_APP_MIN_ADJ
-                && newAdj >= ProcessList.CACHED_APP_MIN_ADJ
-                && newAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
-            // Perform a major compaction when any app enters cached
-            compactApp(app, CompactProfile.FULL, CompactSource.APP, false);
+        }
+    }
+
+    /**
+     * Callback received after a process has been frozen.
+     */
+    void onProcessFrozen(ProcessRecord frozenProc) {
+        if (useCompaction()) {
+            synchronized (mProcLock) {
+                compactApp(frozenProc, CompactProfile.FULL, CompactSource.APP, false);
+            }
         }
     }
 
@@ -1687,26 +1638,6 @@
                             return true;
                         }
                     }
-                } else if (source == CompactSource.PERSISTENT) {
-                    if (start - lastCompactTime < mCompactThrottlePersistent) {
-                        if (DEBUG_COMPACTION) {
-                            Slog.d(TAG_AM,
-                                    "Skipping persistent compaction for " + name
-                                            + ": too soon. throttle=" + mCompactThrottlePersistent
-                                            + " last=" + (start - lastCompactTime) + "ms ago");
-                        }
-                        return true;
-                    }
-                } else if (source == CompactSource.BFGS) {
-                    if (start - lastCompactTime < mCompactThrottleBFGS) {
-                        if (DEBUG_COMPACTION) {
-                            Slog.d(TAG_AM,
-                                    "Skipping bfgs compaction for " + name
-                                            + ": too soon. throttle=" + mCompactThrottleBFGS
-                                            + " last=" + (start - lastCompactTime) + "ms ago");
-                        }
-                        return true;
-                    }
                 }
             }
 
@@ -2021,6 +1952,9 @@
                             }
                         }
                     }
+                    if (proc.mOptRecord.isFrozen()) {
+                        onProcessFrozen(proc);
+                    }
                 }
                     break;
                 case REPORT_UNFREEZE_MSG:
@@ -2050,6 +1984,10 @@
             freezeAppAsyncLSP(proc);
         }
 
+        /**
+         * Freeze a process.
+         * @param proc process to be frozen
+         */
         @GuardedBy({"mAm"})
         private void freezeProcess(final ProcessRecord proc) {
             int pid = proc.getPid(); // Unlocked intentionally
@@ -2083,6 +2021,10 @@
                 if (pid == 0 || opt.isFrozen()) {
                     // Already frozen or not a real process, either one being
                     // launched or one being killed
+                    if (DEBUG_FREEZER) {
+                        Slog.d(TAG_AM, "Skipping freeze for process " + pid
+                                + " " + name + ". Already frozen or not a real process");
+                    }
                     return;
                 }
 
@@ -2124,7 +2066,7 @@
                 frozen = opt.isFrozen();
 
                 final UidRecord uidRec = proc.getUidRecord();
-                if (frozen && uidRec.areAllProcessesFrozen()) {
+                if (frozen && uidRec != null && uidRec.areAllProcessesFrozen()) {
                     uidRec.setFrozen(true);
                     mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(
                             UID_FROZEN_STATE_CHANGED_MSG, proc));
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 84a8099..a98571b 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -349,6 +349,7 @@
     private final ArrayList<UidRecord> mTmpBecameIdle = new ArrayList<UidRecord>();
     private final ActiveUids mTmpUidRecords;
     private final ArrayDeque<ProcessRecord> mTmpQueue;
+    private final ArraySet<ProcessRecord> mTmpProcessSet = new ArraySet<>();
     private final ArraySet<ProcessRecord> mPendingProcessSet = new ArraySet<>();
     private final ArraySet<ProcessRecord> mProcessesInCycle = new ArraySet<>();
 
@@ -2936,30 +2937,8 @@
 
         int changes = 0;
 
-        // don't compact during bootup
-        if (mCachedAppOptimizer.useCompaction() && mService.mBooted) {
-            // Cached and prev/home compaction
-            // 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) {
-                // See if we can compact persistent and bfgs services now that screen is off
-                if (state.getSetAdj() < 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.compactApp(app, CachedAppOptimizer.CompactProfile.FULL,
-                            CachedAppOptimizer.CompactSource.PERSISTENT, false);
-                } else if (state.getCurProcState()
-                                == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
-                        && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
-                    mCachedAppOptimizer.compactApp(app, CachedAppOptimizer.CompactProfile.FULL,
-                            CachedAppOptimizer.CompactSource.BFGS, false);
-                }
-            }
+        if (state.getCurAdj() != state.getSetAdj()) {
+            mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app);
         }
 
         if (state.getCurAdj() != state.getSetAdj()) {
@@ -3472,4 +3451,29 @@
                     CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
         }
     }
+
+    @GuardedBy("mService")
+    void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
+        if (!mCachedAppOptimizer.useFreezer()) {
+            return;
+        }
+
+        final ProcessCachedOptimizerRecord opt = app.mOptRecord;
+        if (!opt.isFrozen() && !opt.isPendingFreeze()) {
+            return;
+        }
+
+        final ArrayList<ProcessRecord> processes = mTmpProcessList;
+        final ActiveUids uids = mTmpUidRecords;
+        mTmpProcessSet.add(app);
+        collectReachableProcessesLocked(mTmpProcessSet, processes, uids);
+        mTmpProcessSet.clear();
+        // Now processes contains app's downstream and app
+        final int size = processes.size();
+        for (int i = 0; i < size; i++) {
+            ProcessRecord proc = processes.get(i);
+            mCachedAppOptimizer.unfreezeTemporarily(proc, reason);
+        }
+        processes.clear();
+    }
 }
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 7788ea4..8fce888 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -22,7 +22,9 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
 import static com.android.server.am.ActivityManagerService.MY_PID;
 import static com.android.server.am.ProcessRecord.TAG;
+import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AnrController;
 import android.app.ApplicationErrorReport;
@@ -56,6 +58,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.ResourcePressureUtil;
 import com.android.server.criticalevents.CriticalEventLog;
+import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
 import com.android.server.wm.WindowProcessController;
 
 import java.io.File;
@@ -400,6 +403,8 @@
                 });
             }
         }
+        // Build memory headers for the ANRing process.
+        String memoryHeaders = buildMemoryHeadersFor(pid);
 
         // Get critical event log before logging the ANR so that it doesn't occur in the log.
         latencyTracker.criticalEventLogStarted();
@@ -500,7 +505,8 @@
         File tracesFile = StackTracesDumpHelper.dumpStackTraces(firstPids,
                 isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
                 nativePidsFuture, tracesFileException, firstPidEndOffset, annotation,
-                criticalEventLog, auxiliaryTaskExecutor, firstPidFilePromise, latencyTracker);
+                criticalEventLog, memoryHeaders, auxiliaryTaskExecutor, firstPidFilePromise,
+                latencyTracker);
 
         if (isMonitorCpuUsage()) {
             // Wait for the first call to finish
@@ -714,6 +720,27 @@
             resolver.getUserId()) != 0;
     }
 
+    private @Nullable String buildMemoryHeadersFor(int pid) {
+        if (pid <= 0) {
+            Slog.i(TAG, "Memory header requested with invalid pid: " + pid);
+            return null;
+        }
+        MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid);
+        if (snapshot == null) {
+            Slog.i(TAG, "Failed to get memory snapshot for pid:" + pid);
+            return null;
+        }
+
+        StringBuilder memoryHeaders = new StringBuilder();
+        memoryHeaders.append("RssHwmKb: ")
+            .append(snapshot.rssHighWaterMarkInKilobytes)
+            .append("\n");
+        memoryHeaders.append("RssKb: ").append(snapshot.rssInKilobytes).append("\n");
+        memoryHeaders.append("RssAnonKb: ").append(snapshot.anonRssInKilobytes).append("\n");
+        memoryHeaders.append("RssShmemKb: ").append(snapshot.rssShmemKilobytes).append("\n");
+        memoryHeaders.append("VmSwapKb: ").append(snapshot.swapInKilobytes).append("\n");
+        return memoryHeaders.toString();
+    }
     /**
      * Unless configured otherwise, swallow ANRs in background processes & kill the process.
      * Non-private access is for tests only.
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 50d00b4..e651e23 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1050,6 +1050,11 @@
         return mState.isCached();
     }
 
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    public boolean hasForegroundActivities() {
+        return mState.hasForegroundActivities();
+    }
+
     boolean hasActivities() {
         return mWindowProcessController.hasActivities();
     }
diff --git a/services/core/java/com/android/server/am/StackTracesDumpHelper.java b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
index 2e99e97..d9553a3 100644
--- a/services/core/java/com/android/server/am/StackTracesDumpHelper.java
+++ b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
@@ -93,7 +93,7 @@
             Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
             @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
         return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
-                logExceptionCreatingFile, null, null, null, auxiliaryTaskExecutor, null,
+                logExceptionCreatingFile, null, null, null, null, auxiliaryTaskExecutor, null,
                 latencyTracker);
     }
 
@@ -108,7 +108,7 @@
             AnrLatencyTracker latencyTracker) {
         return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
                 logExceptionCreatingFile, null, subject, criticalEventSection,
-                auxiliaryTaskExecutor, null, latencyTracker);
+                /* memoryHeaders= */ null, auxiliaryTaskExecutor, null, latencyTracker);
     }
 
     /**
@@ -119,8 +119,8 @@
             ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
             Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
             AtomicLong firstPidEndOffset, String subject, String criticalEventSection,
-            @NonNull Executor auxiliaryTaskExecutor, Future<File> firstPidFilePromise,
-            AnrLatencyTracker latencyTracker) {
+            String memoryHeaders, @NonNull Executor auxiliaryTaskExecutor,
+           Future<File> firstPidFilePromise, AnrLatencyTracker latencyTracker) {
         try {
 
             if (latencyTracker != null) {
@@ -160,9 +160,10 @@
                 return null;
             }
 
-            if (subject != null || criticalEventSection != null) {
+            if (subject != null || criticalEventSection != null || memoryHeaders != null) {
                 appendtoANRFile(tracesFile.getAbsolutePath(),
-                        (subject != null ? "Subject: " + subject + "\n\n" : "")
+                        (subject != null ? "Subject: " + subject + "\n" : "")
+                        + (memoryHeaders != null ? memoryHeaders + "\n\n" : "")
                         + (criticalEventSection != null ? criticalEventSection : ""));
             }
 
diff --git a/services/core/java/com/android/server/appop/AppOpMigrationHelper.java b/services/core/java/com/android/server/appop/AppOpMigrationHelper.java
new file mode 100644
index 0000000..bc59dd6
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpMigrationHelper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import android.annotation.NonNull;
+
+import java.util.Map;
+
+/**
+ * In-process api for app-ops migration.
+ *
+ * @hide
+ */
+public interface AppOpMigrationHelper {
+
+    /**
+     * @return a map of app ID to app-op modes (op name -> mode) for a given user.
+     */
+    @NonNull
+    Map<Integer, Map<String, Integer>> getLegacyAppIdAppOpModes(int userId);
+
+    /**
+     * @return a map of package name to app-op modes (op name -> mode) for a given user.
+     */
+    @NonNull
+    Map<String, Map<String, Integer>> getLegacyPackageAppOpModes(int userId);
+
+    /**
+     * @return AppOps file version, the version is same for all the user.
+     */
+    int getLegacyAppOpVersion();
+}
diff --git a/services/core/java/com/android/server/appop/AppOpMigrationHelperImpl.java b/services/core/java/com/android/server/appop/AppOpMigrationHelperImpl.java
new file mode 100644
index 0000000..d81a13f
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpMigrationHelperImpl.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.SystemServiceManager;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Provider of legacy app-ops data for new permission subsystem.
+ *
+ * @hide
+ */
+public class AppOpMigrationHelperImpl implements AppOpMigrationHelper {
+    private SparseArray<Map<Integer, Map<String, Integer>>> mAppIdAppOpModes = null;
+    private SparseArray<Map<String, Map<String, Integer>>> mPackageAppOpModes = null;
+    private int mVersionAtBoot;
+
+    private final Object mLock = new Object();
+
+    @Override
+    @GuardedBy("mLock")
+    @NonNull
+    public Map<Integer, Map<String, Integer>> getLegacyAppIdAppOpModes(int userId) {
+        synchronized (mLock) {
+            if (mAppIdAppOpModes == null) {
+                readLegacyAppOpState();
+            }
+        }
+        return mAppIdAppOpModes.get(userId, Collections.emptyMap());
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    @NonNull
+    public Map<String, Map<String, Integer>> getLegacyPackageAppOpModes(int userId) {
+        synchronized (mLock) {
+            if (mPackageAppOpModes == null) {
+                readLegacyAppOpState();
+            }
+        }
+        return mPackageAppOpModes.get(userId, Collections.emptyMap());
+    }
+
+    @GuardedBy("mLock")
+    private void readLegacyAppOpState() {
+        final File systemDir = SystemServiceManager.ensureSystemDir();
+        AtomicFile appOpFile = new AtomicFile(new File(systemDir, "appops.xml"));
+
+        final SparseArray<SparseIntArray> uidAppOpModes = new SparseArray<>();
+        final SparseArray<ArrayMap<String, SparseIntArray>> packageAppOpModes =
+                new SparseArray<>();
+
+        LegacyAppOpStateParser parser = new LegacyAppOpStateParser();
+        mVersionAtBoot = parser.readState(appOpFile, uidAppOpModes, packageAppOpModes);
+        mAppIdAppOpModes = getAppIdAppOpModes(uidAppOpModes);
+        mPackageAppOpModes = getPackageAppOpModes(packageAppOpModes);
+    }
+
+    private SparseArray<Map<Integer, Map<String, Integer>>> getAppIdAppOpModes(
+            SparseArray<SparseIntArray> uidAppOpModes) {
+        SparseArray<Map<Integer, Map<String, Integer>>> userAppIdAppOpModes = new SparseArray<>();
+
+        int size = uidAppOpModes.size();
+        for (int uidIndex = 0; uidIndex < size; uidIndex++) {
+            int uid = uidAppOpModes.keyAt(uidIndex);
+            int userId = UserHandle.getUserId(uid);
+            Map<Integer, Map<String, Integer>> appIdAppOpModes = userAppIdAppOpModes.get(userId);
+            if (appIdAppOpModes == null) {
+                appIdAppOpModes = new ArrayMap<>();
+                userAppIdAppOpModes.put(userId, appIdAppOpModes);
+            }
+
+            SparseIntArray appOpModes = uidAppOpModes.valueAt(uidIndex);
+            appIdAppOpModes.put(UserHandle.getAppId(uid), getAppOpModesForOpName(appOpModes));
+        }
+        return userAppIdAppOpModes;
+    }
+
+    private SparseArray<Map<String, Map<String, Integer>>> getPackageAppOpModes(
+            SparseArray<ArrayMap<String, SparseIntArray>> legacyPackageAppOpModes) {
+        SparseArray<Map<String, Map<String, Integer>>> userPackageAppOpModes = new SparseArray<>();
+
+        int usersSize = legacyPackageAppOpModes.size();
+        for (int userIndex = 0; userIndex < usersSize; userIndex++) {
+            int userId = legacyPackageAppOpModes.keyAt(userIndex);
+            Map<String, Map<String, Integer>> packageAppOpModes = userPackageAppOpModes.get(userId);
+            if (packageAppOpModes == null) {
+                packageAppOpModes = new ArrayMap<>();
+                userPackageAppOpModes.put(userId, packageAppOpModes);
+            }
+
+            ArrayMap<String, SparseIntArray> legacyPackagesModes =
+                    legacyPackageAppOpModes.valueAt(userIndex);
+
+            int packagesSize = legacyPackagesModes.size();
+            for (int packageIndex = 0; packageIndex < packagesSize; packageIndex++) {
+                String packageName = legacyPackagesModes.keyAt(packageIndex);
+                SparseIntArray modes = legacyPackagesModes.valueAt(packageIndex);
+                packageAppOpModes.put(packageName, getAppOpModesForOpName(modes));
+            }
+        }
+        return userPackageAppOpModes;
+    }
+
+    /**
+     * Converts the map from op code -> mode to op name -> mode.
+     */
+    private Map<String, Integer> getAppOpModesForOpName(SparseIntArray appOpCodeModes) {
+        int modesSize = appOpCodeModes.size();
+        Map<String, Integer> appOpNameModes = new ArrayMap<>(modesSize);
+
+        for (int modeIndex = 0; modeIndex < modesSize; modeIndex++) {
+            int opCode = appOpCodeModes.keyAt(modeIndex);
+            int opMode = appOpCodeModes.valueAt(modeIndex);
+            appOpNameModes.put(AppOpsManager.opToName(opCode), opMode);
+        }
+        return appOpNameModes;
+    }
+
+    @Override
+    public int getLegacyAppOpVersion() {
+        synchronized (mLock) {
+            if (mAppIdAppOpModes == null || mPackageAppOpModes == null) {
+                readLegacyAppOpState();
+            }
+        }
+        return mVersionAtBoot;
+    }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index cb2c5434..886add3 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -19,7 +19,6 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_FOREGROUND;
 import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
-import static android.app.AppOpsManager.opToDefaultMode;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
@@ -30,7 +29,6 @@
 import android.content.pm.UserPackage;
 import android.os.AsyncTask;
 import android.os.Handler;
-import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
 import android.util.Slog;
@@ -41,25 +39,17 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.XmlUtils;
-import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
-
 /**
  * Legacy implementation for App-ops service's app-op mode (uid and package) storage and access.
  * In the future this class will also include mode callbacks and op restrictions.
@@ -111,6 +101,8 @@
     @GuardedBy("mLock")
     final SparseArray<ArrayMap<String, SparseIntArray>> mUserPackageModes = new SparseArray<>();
 
+    private final LegacyAppOpStateParser mAppOpsStateParser = new LegacyAppOpStateParser();
+
     final AtomicFile mFile;
     final Runnable mWriteRunner = new Runnable() {
         public void run() {
@@ -502,58 +494,7 @@
     public void readState() {
         synchronized (mFile) {
             synchronized (mLock) {
-                FileInputStream stream;
-                try {
-                    stream = mFile.openRead();
-                } catch (FileNotFoundException e) {
-                    Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
-                    mVersionAtBoot = NO_FILE_VERSION;
-                    return;
-                }
-
-                try {
-                    TypedXmlPullParser parser = Xml.resolvePullParser(stream);
-                    int type;
-                    while ((type = parser.next()) != XmlPullParser.START_TAG
-                            && type != XmlPullParser.END_DOCUMENT) {
-                        // Parse next until we reach the start or end
-                    }
-
-                    if (type != XmlPullParser.START_TAG) {
-                        throw new IllegalStateException("no start tag found");
-                    }
-
-                    mVersionAtBoot = parser.getAttributeInt(null, "v", NO_VERSION);
-
-                    int outerDepth = parser.getDepth();
-                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                            && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-                        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                            continue;
-                        }
-
-                        String tagName = parser.getName();
-                        if (tagName.equals("pkg")) {
-                            // version 2 has the structure pkg -> uid -> op ->
-                            // in version 3, since pkg and uid states are kept completely
-                            // independent we switch to user -> pkg -> op
-                            readPackage(parser);
-                        } else if (tagName.equals("uid")) {
-                            readUidOps(parser);
-                        } else if (tagName.equals("user")) {
-                            readUser(parser);
-                        } else {
-                            Slog.w(TAG, "Unknown element under <app-ops>: "
-                                    + parser.getName());
-                            XmlUtils.skipCurrentTag(parser);
-                        }
-                    }
-                    return;
-                } catch (XmlPullParserException e) {
-                    throw new RuntimeException(e);
-                } catch (IOException e) {
-                    throw new RuntimeException(e);
-                }
+                mVersionAtBoot = mAppOpsStateParser.readState(mFile, mUidModes, mUserPackageModes);
             }
         }
     }
@@ -575,162 +516,6 @@
     }
 
     @GuardedBy("mLock")
-    private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
-            XmlPullParserException, IOException {
-        final int uid = parser.getAttributeInt(null, "n");
-        SparseIntArray modes = mUidModes.get(uid);
-        if (modes == null) {
-            modes = new SparseIntArray();
-            mUidModes.put(uid, modes);
-        }
-
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("op")) {
-                final int code = parser.getAttributeInt(null, "n");
-                final int mode = parser.getAttributeInt(null, "m");
-
-                if (mode != opToDefaultMode(code)) {
-                    modes.put(code, mode);
-                }
-            } else {
-                Slog.w(TAG, "Unknown element under <uid>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    /*
-     * Used for migration when pkg is the depth=1 tag
-     */
-    @GuardedBy("mLock")
-    private void readPackage(TypedXmlPullParser parser)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        String pkgName = parser.getAttributeValue(null, "n");
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("uid")) {
-                readUid(parser, pkgName);
-            } else {
-                Slog.w(TAG, "Unknown element under <pkg>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    /*
-     * Used for migration when uid is the depth=2 tag
-     */
-    @GuardedBy("mLock")
-    private void readUid(TypedXmlPullParser parser, String pkgName)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        int userId = UserHandle.getUserId(parser.getAttributeInt(null, "n"));
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("op")) {
-                readOp(parser, userId, pkgName);
-            } else {
-                Slog.w(TAG, "Unknown element under <pkg>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void readUser(TypedXmlPullParser parser)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        int userId = parser.getAttributeInt(null, "n");
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("pkg")) {
-                readPackage(parser, userId);
-            } else {
-                Slog.w(TAG, "Unknown element under <user>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void readPackage(TypedXmlPullParser parser, int userId)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        String pkgName = parser.getAttributeValue(null, "n");
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("op")) {
-                readOp(parser, userId, pkgName);
-            } else {
-                Slog.w(TAG, "Unknown element under <pkg>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void readOp(TypedXmlPullParser parser, int userId, @NonNull String pkgName)
-            throws NumberFormatException, XmlPullParserException {
-        final int opCode = parser.getAttributeInt(null, "n");
-        final int defaultMode = AppOpsManager.opToDefaultMode(opCode);
-        final int mode = parser.getAttributeInt(null, "m", defaultMode);
-
-        if (mode != defaultMode) {
-            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId);
-            if (packageModes == null) {
-                packageModes = new ArrayMap<>();
-                mUserPackageModes.put(userId, packageModes);
-            }
-
-            SparseIntArray modes = packageModes.get(pkgName);
-            if (modes == null) {
-                modes = new SparseIntArray();
-                packageModes.put(pkgName, modes);
-            }
-
-            modes.put(opCode, mode);
-        }
-    }
-
-    @GuardedBy("mLock")
     private void upgradeLocked(int oldVersion) {
         if (oldVersion == NO_FILE_VERSION || oldVersion >= CURRENT_VERSION) {
             return;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 018db17..0be69ce 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1567,19 +1567,20 @@
     }
 
     private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
-        final int callingUid = Binder.getCallingUid();
         // We get to access everything
-        if (callingUid == Process.myPid()) {
+        final int callingPid = Binder.getCallingPid();
+        if (callingPid == Process.myPid()) {
             return;
         }
         // Apps can access their own data
+        final int callingUid = Binder.getCallingUid();
         if (uid == callingUid && packageName != null
                 && checkPackage(uid, packageName) == MODE_ALLOWED) {
             return;
         }
         // Otherwise, you need a permission...
-        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
-                Binder.getCallingPid(), callingUid, null);
+        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS, callingPid,
+                callingUid, null);
     }
 
     /**
diff --git a/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java b/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java
new file mode 100644
index 0000000..a6d5050
--- /dev/null
+++ b/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import static android.app.AppOpsManager.opToDefaultMode;
+
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+class LegacyAppOpStateParser {
+    static final String TAG = LegacyAppOpStateParser.class.getSimpleName();
+
+    private static final int NO_FILE_VERSION = -2;
+    private static final int NO_VERSION = -1;
+
+    /**
+     * Reads legacy app-ops data into given maps.
+     */
+    public int readState(AtomicFile file, SparseArray<SparseIntArray> uidModes,
+            SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes) {
+        FileInputStream stream;
+        try {
+            stream = file.openRead();
+        } catch (FileNotFoundException e) {
+            Slog.i(TAG, "No existing app ops " + file.getBaseFile() + "; starting empty");
+            return NO_FILE_VERSION;
+        }
+
+        try {
+            TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                // Parse next until we reach the start or end
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                throw new IllegalStateException("no start tag found");
+            }
+
+            int versionAtBoot = parser.getAttributeInt(null, "v", NO_VERSION);
+
+            int outerDepth = parser.getDepth();
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+
+                String tagName = parser.getName();
+                if (tagName.equals("pkg")) {
+                    // version 2 has the structure pkg -> uid -> op ->
+                    // in version 3, since pkg and uid states are kept completely
+                    // independent we switch to user -> pkg -> op
+                    readPackage(parser, userPackageModes);
+                } else if (tagName.equals("uid")) {
+                    readUidOps(parser, uidModes);
+                } else if (tagName.equals("user")) {
+                    readUser(parser, userPackageModes);
+                } else {
+                    Slog.w(TAG, "Unknown element under <app-ops>: "
+                            + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+            return versionAtBoot;
+        } catch (XmlPullParserException e) {
+            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void readPackage(TypedXmlPullParser parser,
+            SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        String pkgName = parser.getAttributeValue(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("uid")) {
+                readPackageUid(parser, pkgName, userPackageModes);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readPackageUid(TypedXmlPullParser parser, String pkgName,
+            SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        int userId = UserHandle.getUserId(parser.getAttributeInt(null, "n"));
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                readOp(parser, userId, pkgName, userPackageModes);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readUidOps(TypedXmlPullParser parser, SparseArray<SparseIntArray> uidModes)
+            throws NumberFormatException,
+            XmlPullParserException, IOException {
+        final int uid = parser.getAttributeInt(null, "n");
+        SparseIntArray modes = uidModes.get(uid);
+        if (modes == null) {
+            modes = new SparseIntArray();
+            uidModes.put(uid, modes);
+        }
+
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                final int code = parser.getAttributeInt(null, "n");
+                final int mode = parser.getAttributeInt(null, "m");
+
+                if (mode != opToDefaultMode(code)) {
+                    modes.put(code, mode);
+                }
+            } else {
+                Slog.w(TAG, "Unknown element under <uid>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readUser(TypedXmlPullParser parser,
+            SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        int userId = parser.getAttributeInt(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("pkg")) {
+                readPackageOp(parser, userId, userPackageModes);
+            } else {
+                Slog.w(TAG, "Unknown element under <user>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    // read package tag refactored in Android U
+    private void readPackageOp(TypedXmlPullParser parser, int userId,
+            SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        String pkgName = parser.getAttributeValue(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                readOp(parser, userId, pkgName, userPackageModes);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readOp(TypedXmlPullParser parser, int userId, @NonNull String pkgName,
+            SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes)
+            throws NumberFormatException, XmlPullParserException {
+        final int opCode = parser.getAttributeInt(null, "n");
+        final int defaultMode = AppOpsManager.opToDefaultMode(opCode);
+        final int mode = parser.getAttributeInt(null, "m", defaultMode);
+
+        if (mode != defaultMode) {
+            ArrayMap<String, SparseIntArray> packageModes = userPackageModes.get(userId);
+            if (packageModes == null) {
+                packageModes = new ArrayMap<>();
+                userPackageModes.put(userId, packageModes);
+            }
+
+            SparseIntArray modes = packageModes.get(pkgName);
+            if (modes == null) {
+                modes = new SparseIntArray();
+                packageModes.put(pkgName, modes);
+            }
+
+            modes.put(opCode, mode);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
new file mode 100644
index 0000000..f0e4b0f5
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+final class HardwareKeyboardShortcutController {
+    @GuardedBy("ImfLock.class")
+    private final ArrayList<InputMethodSubtypeHandle> mSubtypeHandles = new ArrayList<>();
+
+    @GuardedBy("ImfLock.class")
+    void reset(@NonNull InputMethodUtils.InputMethodSettings settings) {
+        mSubtypeHandles.clear();
+        for (final InputMethodInfo imi : settings.getEnabledInputMethodListLocked()) {
+            if (!imi.shouldShowInInputMethodPicker()) {
+                continue;
+            }
+            final List<InputMethodSubtype> subtypes =
+                    settings.getEnabledInputMethodSubtypeListLocked(imi, true);
+            if (subtypes.isEmpty()) {
+                mSubtypeHandles.add(InputMethodSubtypeHandle.of(imi, null));
+            } else {
+                for (final InputMethodSubtype subtype : subtypes) {
+                    if (subtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
+                        mSubtypeHandles.add(InputMethodSubtypeHandle.of(imi, subtype));
+                    }
+                }
+            }
+        }
+    }
+
+    @AnyThread
+    @Nullable
+    static <T> T getNeighborItem(@NonNull List<T> list, @NonNull T value, boolean next) {
+        final int size = list.size();
+        for (int i = 0; i < size; ++i) {
+            if (Objects.equals(value, list.get(i))) {
+                final int nextIndex = (i + (next ? 1 : -1) + size) % size;
+                return list.get(nextIndex);
+            }
+        }
+        return null;
+    }
+
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    InputMethodSubtypeHandle onSubtypeSwitch(
+            @NonNull InputMethodSubtypeHandle currentImeAndSubtype, boolean forward) {
+        return getNeighborItem(mSubtypeHandles, currentImeAndSubtype, forward);
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index b440208..2433211 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -316,6 +316,8 @@
     final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
     final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>();
     final InputMethodSubtypeSwitchingController mSwitchingController;
+    final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController =
+            new HardwareKeyboardShortcutController();
 
     /**
      * Tracks how many times {@link #mMethodMap} was updated.
@@ -1731,6 +1733,7 @@
         AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
         mSwitchingController =
                 InputMethodSubtypeSwitchingController.createInstanceLocked(mSettings, context);
+        mHardwareKeyboardShortcutController.reset(mSettings);
         mMenuController = new InputMethodMenuController(this);
         mBindingController =
                 bindingControllerForTesting != null
@@ -3268,6 +3271,7 @@
         // TODO: Make sure that mSwitchingController and mSettings are sharing the
         // the same enabled IMEs list.
         mSwitchingController.resetCircularListLocked(mContext);
+        mHardwareKeyboardShortcutController.reset(mSettings);
 
         sendOnNavButtonFlagsChangedLocked();
     }
@@ -5293,6 +5297,7 @@
         // TODO: Make sure that mSwitchingController and mSettings are sharing the
         // the same enabled IMEs list.
         mSwitchingController.resetCircularListLocked(mContext);
+        mHardwareKeyboardShortcutController.reset(mSettings);
 
         sendOnNavButtonFlagsChangedLocked();
 
@@ -5827,10 +5832,37 @@
         @Override
         public void switchKeyboardLayout(int direction) {
             synchronized (ImfLock.class) {
-                if (direction > 0) {
-                    switchToNextInputMethodLocked(null /* token */, true /* onlyCurrentIme */);
-                } else {
-                    // TODO(b/258853866): Support backwards switching.
+                final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked());
+                if (currentImi == null) {
+                    return;
+                }
+                final InputMethodSubtypeHandle currentSubtypeHandle =
+                        InputMethodSubtypeHandle.of(currentImi, mCurrentSubtype);
+                final InputMethodSubtypeHandle nextSubtypeHandle =
+                        mHardwareKeyboardShortcutController.onSubtypeSwitch(currentSubtypeHandle,
+                                direction > 0);
+                if (nextSubtypeHandle == null) {
+                    return;
+                }
+                final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId());
+                if (nextImi == null) {
+                    return;
+                }
+
+                final int subtypeCount = nextImi.getSubtypeCount();
+                if (subtypeCount == 0) {
+                    if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
+                        setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID);
+                    }
+                    return;
+                }
+
+                for (int i = 0; i < subtypeCount; ++i) {
+                    if (nextSubtypeHandle.equals(
+                            InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) {
+                        setInputMethodLocked(nextImi.getId(), i);
+                        return;
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index efa80b7..34b8e65 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -1562,6 +1562,18 @@
         }
         ipw.decreaseIndent();
 
+        ipw.println("Historical Aggregate Gnss Measurement Provider Data:");
+        ipw.increaseIndent();
+        ArrayMap<CallerIdentity, LocationEventLog.GnssMeasurementAggregateStats>
+                gnssAggregateStats = EVENT_LOG.copyGnssMeasurementAggregateStats();
+        for (int i = 0; i < gnssAggregateStats.size(); i++) {
+            ipw.print(gnssAggregateStats.keyAt(i));
+            ipw.print(": ");
+            gnssAggregateStats.valueAt(i).updateTotals();
+            ipw.println(gnssAggregateStats.valueAt(i));
+        }
+        ipw.decreaseIndent();
+
         if (mGnssManagerService != null) {
             ipw.println("GNSS Manager:");
             ipw.increaseIndent();
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index cb952ed..87e193f 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -30,6 +30,7 @@
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
 import android.annotation.Nullable;
+import android.location.GnssMeasurementRequest;
 import android.location.LocationRequest;
 import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
@@ -58,21 +59,25 @@
 
     private static int getLocationsLogSize() {
         if (D) {
-            return 200;
+            return 400;
         } else {
-            return 100;
+            return 200;
         }
     }
 
     @GuardedBy("mAggregateStats")
     private final ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> mAggregateStats;
 
+    @GuardedBy("mGnssMeasAggregateStats")
+    private final ArrayMap<CallerIdentity, GnssMeasurementAggregateStats> mGnssMeasAggregateStats;
+
     @GuardedBy("this")
     private final LocationsEventLog mLocationsLog;
 
     private LocationEventLog() {
         super(getLogSize(), Object.class);
         mAggregateStats = new ArrayMap<>(4);
+        mGnssMeasAggregateStats = new ArrayMap<>();
         mLocationsLog = new LocationsEventLog(getLocationsLogSize());
     }
 
@@ -105,6 +110,29 @@
         }
     }
 
+    /** Copies out gnss measurement aggregated stats. */
+    public ArrayMap<CallerIdentity, GnssMeasurementAggregateStats>
+            copyGnssMeasurementAggregateStats() {
+        synchronized (mGnssMeasAggregateStats) {
+            ArrayMap<CallerIdentity, GnssMeasurementAggregateStats> copy = new ArrayMap<>(
+                    mGnssMeasAggregateStats);
+            return copy;
+        }
+    }
+
+    private GnssMeasurementAggregateStats getGnssMeasurementAggregateStats(
+            CallerIdentity identity) {
+        synchronized (mGnssMeasAggregateStats) {
+            CallerIdentity aggregate = CallerIdentity.forAggregation(identity);
+            GnssMeasurementAggregateStats stats = mGnssMeasAggregateStats.get(aggregate);
+            if (stats == null) {
+                stats = new GnssMeasurementAggregateStats();
+                mGnssMeasAggregateStats.put(aggregate, stats);
+            }
+            return stats;
+        }
+    }
+
     /** Logs a user switched event. */
     public void logUserSwitched(int userIdFrom, int userIdTo) {
         addLog(new UserSwitchedEvent(userIdFrom, userIdTo));
@@ -221,6 +249,29 @@
         addLog(new LocationPowerSaveModeEvent(locationPowerSaveMode));
     }
 
+    /** Logs a new client registration for a GNSS Measurement. */
+    public void logGnssMeasurementClientRegistered(CallerIdentity identity,
+            GnssMeasurementRequest request) {
+        addLog(new GnssMeasurementClientRegisterEvent(true, identity, request));
+        getGnssMeasurementAggregateStats(identity).markRequestAdded(request.getIntervalMillis(),
+                request.isFullTracking());
+    }
+
+    /** Logs a new client unregistration for a GNSS Measurement. */
+    public void logGnssMeasurementClientUnregistered(CallerIdentity identity) {
+        addLog(new GnssMeasurementClientRegisterEvent(false, identity, null));
+        getGnssMeasurementAggregateStats(identity).markRequestRemoved();
+    }
+
+    /** Logs a GNSS measurement event deliver for a client. */
+    public void logGnssMeasurementsDelivered(int numGnssMeasurements,
+            CallerIdentity identity) {
+        synchronized (this) {
+            mLocationsLog.logDeliveredGnssMeasurements(numGnssMeasurements, identity);
+        }
+        getGnssMeasurementAggregateStats(identity).markGnssMeasurementDelivered();
+    }
+
     private void addLog(Object logEvent) {
         addLog(SystemClock.elapsedRealtime(), logEvent);
     }
@@ -528,6 +579,50 @@
         }
     }
 
+    private static final class GnssMeasurementClientRegisterEvent{
+
+        private final boolean mRegistered;
+        private final CallerIdentity mIdentity;
+        @Nullable
+        private final GnssMeasurementRequest mGnssMeasurementRequest;
+
+        GnssMeasurementClientRegisterEvent(boolean registered,
+                CallerIdentity identity, @Nullable GnssMeasurementRequest measurementRequest) {
+            mRegistered = registered;
+            mIdentity = identity;
+            mGnssMeasurementRequest = measurementRequest;
+        }
+
+        @Override
+        public String toString() {
+            if (mRegistered) {
+                return "gnss measurements +registration " + mIdentity + " -> "
+                        + mGnssMeasurementRequest;
+            } else {
+                return "gnss measurements -registration " + mIdentity;
+            }
+        }
+    }
+
+    private static final class GnssMeasurementDeliverEvent {
+
+        private final int mNumGnssMeasurements;
+        @Nullable
+        private final CallerIdentity mIdentity;
+
+        GnssMeasurementDeliverEvent(int numGnssMeasurements,
+                @Nullable CallerIdentity identity) {
+            mNumGnssMeasurements = numGnssMeasurements;
+            mIdentity = identity;
+        }
+
+        @Override
+        public String toString() {
+            return "gnss measurements delivered GnssMeasurements[" + mNumGnssMeasurements + "]"
+                    + " to " + mIdentity;
+        }
+    }
+
     private static final class LocationsEventLog extends LocalEventLog<Object> {
 
         LocationsEventLog(int size) {
@@ -538,6 +633,11 @@
             addLog(new ProviderReceiveLocationEvent(provider, numLocations));
         }
 
+        public void logDeliveredGnssMeasurements(int numGnssMeasurements,
+                CallerIdentity identity) {
+            addLog(new GnssMeasurementDeliverEvent(numGnssMeasurements, identity));
+        }
+
         public void logProviderDeliveredLocations(String provider, int numLocations,
                 CallerIdentity identity) {
             addLog(new ProviderDeliverLocationEvent(provider, numLocations, identity));
@@ -668,4 +768,89 @@
             }
         }
     }
+
+    /**
+     * Aggregate statistics for GNSS measurements.
+     */
+    public static final class GnssMeasurementAggregateStats {
+        @GuardedBy("this")
+        private int mAddedRequestCount;
+        @GuardedBy("this")
+        private int mReceivedMeasurementEventCount;
+        @GuardedBy("this")
+        private long mAddedTimeTotalMs;
+        @GuardedBy("this")
+        private long mAddedTimeLastUpdateRealtimeMs;
+        @GuardedBy("this")
+        private long mFastestIntervalMs = Long.MAX_VALUE;
+        @GuardedBy("this")
+        private long mSlowestIntervalMs = 0;
+        @GuardedBy("this")
+        private boolean mHasFullTracking;
+        @GuardedBy("this")
+        private boolean mHasDutyCycling;
+
+        GnssMeasurementAggregateStats() {
+        }
+
+        synchronized void markRequestAdded(long intervalMillis, boolean fullTracking) {
+            if (mAddedRequestCount++ == 0) {
+                mAddedTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime();
+            }
+            if (fullTracking) {
+                mHasFullTracking = true;
+            } else {
+                mHasDutyCycling = true;
+            }
+            mFastestIntervalMs = min(intervalMillis, mFastestIntervalMs);
+            mSlowestIntervalMs = max(intervalMillis, mSlowestIntervalMs);
+        }
+
+        synchronized void markRequestRemoved() {
+            updateTotals();
+            --mAddedRequestCount;
+            Preconditions.checkState(mAddedRequestCount >= 0);
+        }
+
+        synchronized void markGnssMeasurementDelivered() {
+            mReceivedMeasurementEventCount++;
+        }
+
+        public synchronized void updateTotals() {
+            if (mAddedRequestCount > 0) {
+                long realtimeMs = SystemClock.elapsedRealtime();
+                mAddedTimeTotalMs += realtimeMs - mAddedTimeLastUpdateRealtimeMs;
+                mAddedTimeLastUpdateRealtimeMs = realtimeMs;
+            }
+        }
+
+        @Override
+        public synchronized String toString() {
+            return "min/max interval = "
+                    + intervalToString(mFastestIntervalMs) + "/"
+                    + intervalToString(mSlowestIntervalMs)
+                    + ", total duration = " + formatDuration(mAddedTimeTotalMs)
+                    + ", tracking mode = " + trackingModeToString() + ", GNSS measurement events = "
+                    + mReceivedMeasurementEventCount;
+        }
+
+        private static String intervalToString(long intervalMs) {
+            if (intervalMs == GnssMeasurementRequest.PASSIVE_INTERVAL) {
+                return "passive";
+            } else {
+                return MILLISECONDS.toSeconds(intervalMs) + "s";
+            }
+        }
+
+        @GuardedBy("this")
+        private String trackingModeToString() {
+            if (mHasFullTracking && mHasDutyCycling) {
+                return "mixed tracking mode";
+            } else if (mHasFullTracking) {
+                return "always full-tracking";
+            } else {
+                return "always duty-cycling";
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index 041f11d..d02b6f4 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -18,6 +18,7 @@
 
 import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
 
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
 import static com.android.server.location.gnss.GnssManagerService.D;
 import static com.android.server.location.gnss.GnssManagerService.TAG;
 
@@ -62,11 +63,17 @@
         @Override
         protected void onRegister() {
             super.onRegister();
-
+            EVENT_LOG.logGnssMeasurementClientRegistered(getIdentity(), getRequest());
             executeOperation(listener -> listener.onStatusChanged(
                     GnssMeasurementsEvent.Callback.STATUS_READY));
         }
 
+        @Override
+        protected void onUnregister() {
+            EVENT_LOG.logGnssMeasurementClientUnregistered(getIdentity());
+            super.onUnregister();
+        }
+
         @Nullable
         @Override
         protected void onActive() {
@@ -250,6 +257,8 @@
         deliverToListeners(registration -> {
             if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION,
                     registration.getIdentity())) {
+                EVENT_LOG.logGnssMeasurementsDelivered(event.getMeasurements().size(),
+                        registration.getIdentity());
                 return listener -> listener.onGnssMeasurementsReceived(event);
             } else {
                 return null;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 71f8e16..46c2594 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -656,7 +656,6 @@
     private ConditionProviders mConditionProviders;
     private NotificationUsageStats mUsageStats;
     private boolean mLockScreenAllowSecureNotifications = true;
-    boolean mAllowFgsDismissal = false;
     boolean mSystemExemptFromDismissal = false;
 
     private static final int MY_UID = Process.myUid();
@@ -2581,19 +2580,9 @@
             for (String name : properties.getKeyset()) {
                 if (SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE.equals(name)) {
                     mAssistants.resetDefaultAssistantsIfNecessary();
-                } else if (SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED.equals(name)) {
-                    String value = properties.getString(name, null);
-                    if ("true".equals(value)) {
-                        mAllowFgsDismissal = true;
-                    } else if ("false".equals(value)) {
-                        mAllowFgsDismissal = false;
-                    }
                 }
             }
         };
-        mAllowFgsDismissal = DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED, true);
         mSystemExemptFromDismissal = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
                 /* name= */ "application_exemptions",
@@ -6727,8 +6716,11 @@
         handleSavePolicyFile();
     }
 
-    private void makeStickyHun(Notification notification) {
-        notification.flags |= FLAG_FSI_REQUESTED_BUT_DENIED;
+    private void makeStickyHun(Notification notification, String pkg, @UserIdInt int userId) {
+        if (mPermissionHelper.hasRequestedPermission(
+                Manifest.permission.USE_FULL_SCREEN_INTENT, pkg, userId)) {
+            notification.flags |= FLAG_FSI_REQUESTED_BUT_DENIED;
+        }
         if (notification.contentIntent == null) {
             // On notification click, if contentIntent is null, SystemUI launches the
             // fullScreenIntent instead.
@@ -6792,10 +6784,9 @@
                     SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
 
             if (forceDemoteFsiToStickyHun) {
-                makeStickyHun(notification);
+                makeStickyHun(notification, pkg, userId);
 
             } else if (showStickyHunIfDenied) {
-
                 final AttributionSource source = new AttributionSource.Builder(notificationUid)
                         .setPackageName(pkg)
                         .build();
@@ -6804,7 +6795,7 @@
                         Manifest.permission.USE_FULL_SCREEN_INTENT, source, /* message= */ null);
 
                 if (permissionResult != PermissionManager.PERMISSION_GRANTED) {
-                    makeStickyHun(notification);
+                    makeStickyHun(notification, pkg, userId);
                 }
 
             } else {
@@ -7733,9 +7724,6 @@
                     // flags are set.
                     if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) {
                         notification.flags |= FLAG_NO_CLEAR;
-                        if (!mAllowFgsDismissal) {
-                            notification.flags |= FLAG_ONGOING_EVENT;
-                        }
                     }
 
                     mRankingHelper.extractSignals(r);
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index e6fd7ec..b6fd822 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -78,6 +78,30 @@
     }
 
     /**
+     * Returns whether the given app requested the given permission. Must not be called
+     * with a lock held.
+     */
+    public boolean hasRequestedPermission(String permission, String pkg, @UserIdInt int userId) {
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            PackageInfo pi = mPackageManager.getPackageInfo(pkg, GET_PERMISSIONS, userId);
+            if (pi == null || pi.requestedPermissions == null) {
+                return false;
+            }
+            for (String perm : pi.requestedPermissions) {
+                if (permission.equals(perm)) {
+                    return true;
+                }
+            }
+        } catch (RemoteException e) {
+            Slog.d(TAG, "Could not reach system server", e);
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+        return false;
+    }
+
+    /**
      * Returns all of the apps that have requested the notification permission in a given user.
      * Must not be called with a lock held. Format: uid, packageName
      */
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 20e5d39..7603c45 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -278,6 +278,7 @@
     private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
 
     static final int WRITE_USER_MSG = 1;
+    static final int WRITE_USER_LIST_MSG = 2;
     static final int WRITE_USER_DELAY = 2*1000;  // 2 seconds
 
     private static final long BOOT_USER_SET_TIMEOUT_MS = 300_000;
@@ -321,7 +322,6 @@
     private final Handler mHandler;
 
     private final File mUsersDir;
-    @GuardedBy("mPackagesLock")
     private final File mUserListFile;
 
     private final IBinder mUserRestrictionToken = new Binder();
@@ -3640,77 +3640,95 @@
         mUpdatingSystemUserMode = true;
     }
 
+
+    private ResilientAtomicFile getUserListFile() {
+        File tempBackup = new File(mUserListFile.getParent(), mUserListFile.getName() + ".backup");
+        File reserveCopy = new File(mUserListFile.getParent(),
+                mUserListFile.getName() + ".reservecopy");
+        int fileMode = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH;
+        return new ResilientAtomicFile(mUserListFile, tempBackup, reserveCopy, fileMode,
+                "user list", (priority, msg) -> {
+            Slog.e(LOG_TAG, msg);
+            // Something went wrong, schedule full rewrite.
+            scheduleWriteUserList();
+        });
+    }
+
     @GuardedBy({"mPackagesLock"})
     private void readUserListLP() {
-        if (!mUserListFile.exists()) {
-            fallbackToSingleUserLP();
-            return;
-        }
-        FileInputStream fis = null;
-        AtomicFile userListFile = new AtomicFile(mUserListFile);
-        try {
-            fis = userListFile.openRead();
-            final TypedXmlPullParser parser = Xml.resolvePullParser(fis);
-            int type;
-            while ((type = parser.next()) != XmlPullParser.START_TAG
-                    && type != XmlPullParser.END_DOCUMENT) {
-                // Skip
-            }
+        try (ResilientAtomicFile file = getUserListFile()) {
+            FileInputStream fin = null;
+            try {
+                fin = file.openRead();
+                if (fin == null) {
+                    Slog.e(LOG_TAG, "userlist.xml not found, fallback to single user");
+                    fallbackToSingleUserLP();
+                    return;
+                }
 
-            if (type != XmlPullParser.START_TAG) {
-                Slog.e(LOG_TAG, "Unable to read user list");
-                fallbackToSingleUserLP();
-                return;
-            }
+                final TypedXmlPullParser parser = Xml.resolvePullParser(fin);
+                int type;
+                while ((type = parser.next()) != XmlPullParser.START_TAG
+                        && type != XmlPullParser.END_DOCUMENT) {
+                    // Skip
+                }
 
-            mNextSerialNumber = -1;
-            if (parser.getName().equals(TAG_USERS)) {
-                mNextSerialNumber =
-                        parser.getAttributeInt(null, ATTR_NEXT_SERIAL_NO, mNextSerialNumber);
-                mUserVersion =
-                        parser.getAttributeInt(null, ATTR_USER_VERSION, mUserVersion);
-                mUserTypeVersion =
-                        parser.getAttributeInt(null, ATTR_USER_TYPE_VERSION, mUserTypeVersion);
-            }
+                if (type != XmlPullParser.START_TAG) {
+                    Slog.e(LOG_TAG, "Unable to read user list");
+                    fallbackToSingleUserLP();
+                    return;
+                }
 
-            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
-                if (type == XmlPullParser.START_TAG) {
-                    final String name = parser.getName();
-                    if (name.equals(TAG_USER)) {
-                        UserData userData = readUserLP(parser.getAttributeInt(null, ATTR_ID));
+                mNextSerialNumber = -1;
+                if (parser.getName().equals(TAG_USERS)) {
+                    mNextSerialNumber =
+                            parser.getAttributeInt(null, ATTR_NEXT_SERIAL_NO, mNextSerialNumber);
+                    mUserVersion =
+                            parser.getAttributeInt(null, ATTR_USER_VERSION, mUserVersion);
+                    mUserTypeVersion =
+                            parser.getAttributeInt(null, ATTR_USER_TYPE_VERSION, mUserTypeVersion);
+                }
 
-                        if (userData != null) {
-                            synchronized (mUsersLock) {
-                                mUsers.put(userData.info.id, userData);
-                                if (mNextSerialNumber < 0
-                                        || mNextSerialNumber <= userData.info.id) {
-                                    mNextSerialNumber = userData.info.id + 1;
-                                }
-                            }
-                        }
-                    } else if (name.equals(TAG_GUEST_RESTRICTIONS)) {
-                        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                                && type != XmlPullParser.END_TAG) {
-                            if (type == XmlPullParser.START_TAG) {
-                                if (parser.getName().equals(TAG_RESTRICTIONS)) {
-                                    synchronized (mGuestRestrictions) {
-                                        UserRestrictionsUtils
-                                                .readRestrictions(parser, mGuestRestrictions);
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                    if (type == XmlPullParser.START_TAG) {
+                        final String name = parser.getName();
+                        if (name.equals(TAG_USER)) {
+                            UserData userData = readUserLP(parser.getAttributeInt(null, ATTR_ID));
+
+                            if (userData != null) {
+                                synchronized (mUsersLock) {
+                                    mUsers.put(userData.info.id, userData);
+                                    if (mNextSerialNumber < 0
+                                            || mNextSerialNumber <= userData.info.id) {
+                                        mNextSerialNumber = userData.info.id + 1;
                                     }
                                 }
-                                break;
+                            }
+                        } else if (name.equals(TAG_GUEST_RESTRICTIONS)) {
+                            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                                    && type != XmlPullParser.END_TAG) {
+                                if (type == XmlPullParser.START_TAG) {
+                                    if (parser.getName().equals(TAG_RESTRICTIONS)) {
+                                        synchronized (mGuestRestrictions) {
+                                            UserRestrictionsUtils
+                                                    .readRestrictions(parser, mGuestRestrictions);
+                                        }
+                                    }
+                                    break;
+                                }
                             }
                         }
                     }
                 }
-            }
 
-            updateUserIds();
-            upgradeIfNecessaryLP();
-        } catch (IOException | XmlPullParserException e) {
-            fallbackToSingleUserLP();
-        } finally {
-            IoUtils.closeQuietly(fis);
+                updateUserIds();
+                upgradeIfNecessaryLP();
+            } catch (Exception e) {
+                // Remove corrupted file and retry.
+                file.failRead(fin, e);
+                readUserListLP();
+                return;
+            }
         }
 
         synchronized (mUsersLock) {
@@ -4116,6 +4134,18 @@
         }
     }
 
+    private void scheduleWriteUserList() {
+        if (DBG) {
+            debug("scheduleWriteUserList");
+        }
+        // No need to wrap it within a lock -- worst case, we'll just post the same message
+        // twice.
+        if (!mHandler.hasMessages(WRITE_USER_LIST_MSG)) {
+            Message msg = mHandler.obtainMessage(WRITE_USER_LIST_MSG);
+            mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
+        }
+    }
+
     private void scheduleWriteUser(UserData userData) {
         if (DBG) {
             debug("scheduleWriteUser");
@@ -4128,20 +4158,37 @@
         }
     }
 
+    private ResilientAtomicFile getUserFile(int userId) {
+        File file = new File(mUsersDir, userId + XML_SUFFIX);
+        File tempBackup = new File(mUsersDir, userId + XML_SUFFIX + ".backup");
+        File reserveCopy = new File(mUsersDir, userId + XML_SUFFIX + ".reservecopy");
+        int fileMode = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH;
+        return new ResilientAtomicFile(file, tempBackup, reserveCopy, fileMode,
+                "user info", (priority, msg) -> {
+            Slog.e(LOG_TAG, msg);
+            // Something went wrong, schedule full rewrite.
+            UserData userData = getUserDataNoChecks(userId);
+            if (userData != null) {
+                scheduleWriteUser(userData);
+            }
+        });
+    }
+
     @GuardedBy({"mPackagesLock"})
     private void writeUserLP(UserData userData) {
         if (DBG) {
             debug("writeUserLP " + userData);
         }
-        FileOutputStream fos = null;
-        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userData.info.id + XML_SUFFIX));
-        try {
-            fos = userFile.startWrite();
-            writeUserLP(userData, fos);
-            userFile.finishWrite(fos);
-        } catch (Exception ioe) {
-            Slog.e(LOG_TAG, "Error writing user info " + userData.info.id, ioe);
-            userFile.failWrite(fos);
+        try (ResilientAtomicFile userFile = getUserFile(userData.info.id)) {
+            FileOutputStream fos = null;
+            try {
+                fos = userFile.startWrite();
+                writeUserLP(userData, fos);
+                userFile.finishWrite(fos);
+            } catch (Exception ioe) {
+                Slog.e(LOG_TAG, "Error writing user info " + userData.info.id, ioe);
+                userFile.failWrite(fos);
+            }
         }
     }
 
@@ -4270,65 +4317,71 @@
         if (DBG) {
             debug("writeUserList");
         }
-        FileOutputStream fos = null;
-        AtomicFile userListFile = new AtomicFile(mUserListFile);
-        try {
-            fos = userListFile.startWrite();
-            final TypedXmlSerializer serializer = Xml.resolveSerializer(fos);
-            serializer.startDocument(null, true);
-            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
 
-            serializer.startTag(null, TAG_USERS);
-            serializer.attributeInt(null, ATTR_NEXT_SERIAL_NO, mNextSerialNumber);
-            serializer.attributeInt(null, ATTR_USER_VERSION, mUserVersion);
-            serializer.attributeInt(null, ATTR_USER_TYPE_VERSION, mUserTypeVersion);
+        try (ResilientAtomicFile file = getUserListFile()) {
+            FileOutputStream fos = null;
+            try {
+                fos = file.startWrite();
 
-            serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
-            synchronized (mGuestRestrictions) {
-                UserRestrictionsUtils
-                        .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
-            }
-            serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
-            int[] userIdsToWrite;
-            synchronized (mUsersLock) {
-                userIdsToWrite = new int[mUsers.size()];
-                for (int i = 0; i < userIdsToWrite.length; i++) {
-                    UserInfo user = mUsers.valueAt(i).info;
-                    userIdsToWrite[i] = user.id;
+                final TypedXmlSerializer serializer = Xml.resolveSerializer(fos);
+                serializer.startDocument(null, true);
+                serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
+                        true);
+
+                serializer.startTag(null, TAG_USERS);
+                serializer.attributeInt(null, ATTR_NEXT_SERIAL_NO, mNextSerialNumber);
+                serializer.attributeInt(null, ATTR_USER_VERSION, mUserVersion);
+                serializer.attributeInt(null, ATTR_USER_TYPE_VERSION, mUserTypeVersion);
+
+                serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
+                synchronized (mGuestRestrictions) {
+                    UserRestrictionsUtils
+                            .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
                 }
-            }
-            for (int id : userIdsToWrite) {
-                serializer.startTag(null, TAG_USER);
-                serializer.attributeInt(null, ATTR_ID, id);
-                serializer.endTag(null, TAG_USER);
-            }
+                serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
+                int[] userIdsToWrite;
+                synchronized (mUsersLock) {
+                    userIdsToWrite = new int[mUsers.size()];
+                    for (int i = 0; i < userIdsToWrite.length; i++) {
+                        UserInfo user = mUsers.valueAt(i).info;
+                        userIdsToWrite[i] = user.id;
+                    }
+                }
+                for (int id : userIdsToWrite) {
+                    serializer.startTag(null, TAG_USER);
+                    serializer.attributeInt(null, ATTR_ID, id);
+                    serializer.endTag(null, TAG_USER);
+                }
 
-            serializer.endTag(null, TAG_USERS);
+                serializer.endTag(null, TAG_USERS);
 
-            serializer.endDocument();
-            userListFile.finishWrite(fos);
-        } catch (Exception e) {
-            userListFile.failWrite(fos);
-            Slog.e(LOG_TAG, "Error writing user list");
+                serializer.endDocument();
+                file.finishWrite(fos);
+            } catch (Exception e) {
+                Slog.e(LOG_TAG, "Error writing user list", e);
+                file.failWrite(fos);
+            }
         }
     }
 
     @GuardedBy({"mPackagesLock"})
     private UserData readUserLP(int id) {
-        FileInputStream fis = null;
-        try {
-            AtomicFile userFile =
-                    new AtomicFile(new File(mUsersDir, Integer.toString(id) + XML_SUFFIX));
-            fis = userFile.openRead();
-            return readUserLP(id, fis);
-        } catch (IOException ioe) {
-            Slog.e(LOG_TAG, "Error reading user list");
-        } catch (XmlPullParserException pe) {
-            Slog.e(LOG_TAG, "Error reading user list");
-        } finally {
-            IoUtils.closeQuietly(fis);
+        try (ResilientAtomicFile file = getUserFile(id)) {
+            FileInputStream fis = null;
+            try {
+                fis = file.openRead();
+                if (fis == null) {
+                    Slog.e(LOG_TAG, "User info not found, returning null, user id: " + id);
+                    return null;
+                }
+                return readUserLP(id, fis);
+            } catch (Exception e) {
+                // Remove corrupted file and retry.
+                Slog.e(LOG_TAG, "Error reading user info, user id: " + id);
+                file.failRead(fis, e);
+                return readUserLP(id);
+            }
         }
-        return null;
     }
 
     @GuardedBy({"mPackagesLock"})
@@ -5790,9 +5843,8 @@
         synchronized (mPackagesLock) {
             writeUserListLP();
         }
-        // Remove user file
-        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userId + XML_SUFFIX));
-        userFile.delete();
+        // Remove user file(s)
+        getUserFile(userId).delete();
         updateUserIds();
         if (RELEASE_DELETED_USER_ID) {
             synchronized (mUsersLock) {
@@ -6755,6 +6807,13 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
+                case WRITE_USER_LIST_MSG: {
+                    removeMessages(WRITE_USER_LIST_MSG);
+                    synchronized (mPackagesLock) {
+                        writeUserListLP();
+                    }
+                    break;
+                }
                 case WRITE_USER_MSG:
                     removeMessages(WRITE_USER_MSG, msg.obj);
                     synchronized (mPackagesLock) {
@@ -6767,6 +6826,7 @@
                                     + ", it was probably removed before handler could handle it");
                         }
                     }
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index f340f93..c9ebeae 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -652,20 +652,33 @@
 
     private boolean isAdbVerificationEnabled(PackageInfoLite pkgInfoLite, int userId,
             boolean requestedDisableVerification) {
+        boolean verifierIncludeAdb = android.provider.Settings.Global.getInt(
+                mPm.mContext.getContentResolver(),
+                android.provider.Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) != 0;
+
         if (mPm.isUserRestricted(userId, UserManager.ENSURE_VERIFY_APPS)) {
+            if (!verifierIncludeAdb) {
+                Slog.w(TAG, "Force verification of ADB install because of user restriction.");
+            }
             return true;
         }
-        // Check if the developer wants to skip verification for ADB installs
+
+        // Check if the verification disabled globally, first.
+        if (!verifierIncludeAdb) {
+            return false;
+        }
+
+        // Check if the developer wants to skip verification for ADB installs.
         if (requestedDisableVerification) {
             if (!packageExists(pkgInfoLite.packageName)) {
-                // Always verify fresh install
+                // Always verify fresh install.
                 return true;
             }
-            // Only skip when apk is debuggable
+            // Only skip when apk is debuggable.
             return !pkgInfoLite.debuggable;
         }
-        return android.provider.Settings.Global.getInt(mPm.mContext.getContentResolver(),
-                android.provider.Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) != 0;
+
+        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 572e13c..df0192c 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -64,6 +64,7 @@
 import android.permission.PermissionCheckerManager;
 import android.permission.PermissionManager;
 import android.permission.PermissionManagerInternal;
+import android.service.voice.VoiceInteractionManagerInternal;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -967,12 +968,13 @@
             // the private data in your process; or by you explicitly calling to another
             // app passing the source, in which case you must trust the other side;
 
-            final int callingUid = Binder.getCallingUid();
-            if (source.getUid() != callingUid && mContext.checkPermission(
+            final int callingUid = resolveUid(Binder.getCallingUid());
+            final int sourceUid = resolveUid(source.getUid());
+            if (sourceUid != callingUid && mContext.checkPermission(
                     Manifest.permission.UPDATE_APP_OPS_STATS, /*pid*/ -1, callingUid)
                     != PackageManager.PERMISSION_GRANTED) {
                 throw new SecurityException("Cannot register attribution source for uid:"
-                        + source.getUid() + " from uid:" + callingUid);
+                        + sourceUid + " from uid:" + callingUid);
             }
 
             final PackageManagerInternal packageManagerInternal = LocalServices.getService(
@@ -981,10 +983,10 @@
             // TODO(b/234653108): Clean up this UID/package & cross-user check.
             // If calling from the system process, allow registering attribution for package from
             // any user
-            int userId = UserHandle.getUserId((callingUid == Process.SYSTEM_UID ? source.getUid()
+            int userId = UserHandle.getUserId((callingUid == Process.SYSTEM_UID ? sourceUid
                     : callingUid));
             if (packageManagerInternal.getPackageUid(source.getPackageName(), 0, userId)
-                    != source.getUid()) {
+                    != sourceUid) {
                 throw new SecurityException("Cannot register attribution source for package:"
                         + source.getPackageName() + " from uid:" + callingUid);
             }
@@ -1010,6 +1012,21 @@
                 return false;
             }
         }
+
+        private int resolveUid(int uid) {
+            final VoiceInteractionManagerInternal vimi = LocalServices
+                    .getService(VoiceInteractionManagerInternal.class);
+            if (vimi == null) {
+                return uid;
+            }
+            final VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity
+                    hotwordDetectionServiceIdentity = vimi.getHotwordDetectionServiceIdentity();
+            if (hotwordDetectionServiceIdentity != null
+                    && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) {
+                return hotwordDetectionServiceIdentity.getOwnerUid();
+            }
+            return uid;
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/permission/PermissionMigrationHelperImpl.java b/services/core/java/com/android/server/pm/permission/PermissionMigrationHelperImpl.java
index a213268..2ae39b7 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionMigrationHelperImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionMigrationHelperImpl.java
@@ -92,23 +92,29 @@
                      packageManagerLocal.withUnfilteredSnapshot()) {
             Map<String, PackageState> packageStates = snapshot.getPackageStates();
             legacyState.getPackagePermissions().forEach((packageName, permissionStates) -> {
-                PackageState packageState = packageStates.get(packageName);
-                if (packageState != null) {
-                    int appId = packageState.getAppId();
-                    appIdPermissionStates.put(appId, toLegacyPermissionStates(permissionStates));
-                } else {
-                    Log.w(LOG_TAG, "Package " + packageName + " not found.");
+                if (!permissionStates.isEmpty()) {
+                    PackageState packageState = packageStates.get(packageName);
+                    if (packageState != null) {
+                        int appId = packageState.getAppId();
+                        appIdPermissionStates.put(appId,
+                                toLegacyPermissionStates(permissionStates));
+                    } else {
+                        Log.w(LOG_TAG, "Package " + packageName + " not found.");
+                    }
                 }
             });
 
             Map<String, SharedUserApi> sharedUsers = snapshot.getSharedUsers();
             legacyState.getSharedUserPermissions().forEach((sharedUserName, permissionStates) -> {
-                SharedUserApi sharedUser = sharedUsers.get(sharedUserName);
-                if (sharedUser != null) {
-                    int appId = sharedUser.getAppId();
-                    appIdPermissionStates.put(appId, toLegacyPermissionStates(permissionStates));
-                } else {
-                    Log.w(LOG_TAG, "Shared user " + sharedUserName + " not found.");
+                if (!permissionStates.isEmpty()) {
+                    SharedUserApi sharedUser = sharedUsers.get(sharedUserName);
+                    if (sharedUser != null) {
+                        int appId = sharedUser.getAppId();
+                        appIdPermissionStates.put(appId,
+                                toLegacyPermissionStates(permissionStates));
+                    } else {
+                        Log.w(LOG_TAG, "Shared user " + sharedUserName + " not found.");
+                    }
                 }
             });
         }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 449d2eb..adaad2a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -731,7 +731,7 @@
                     mAutofillManagerInternal.onBackKeyPressed();
                     break;
                 case MSG_SYSTEM_KEY_PRESS:
-                    sendSystemKeyToStatusBar(msg.arg1);
+                    sendSystemKeyToStatusBar((KeyEvent) msg.obj);
                     break;
                 case MSG_HANDLE_ALL_APPS:
                     launchAllAppsAction();
@@ -951,7 +951,7 @@
         final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event);
 
         // Inform the StatusBar; but do not allow it to consume the event.
-        sendSystemKeyToStatusBarAsync(event.getKeyCode());
+        sendSystemKeyToStatusBarAsync(event);
 
         // If the power key has still not yet been handled, then detect short
         // press, long press, or multi press and decide what to do.
@@ -3036,7 +3036,11 @@
                 break;
             case KeyEvent.KEYCODE_N:
                 if (down && event.isMetaPressed()) {
-                    toggleNotificationPanel();
+                    if (event.isCtrlPressed()) {
+                        sendSystemKeyToStatusBarAsync(event);
+                    } else {
+                        toggleNotificationPanel();
+                    }
                     return key_consumed;
                 }
                 break;
@@ -3604,14 +3608,16 @@
 
     @Override
     public int applyKeyguardOcclusionChange() {
-        if (mKeyguardOccludedChanged) {
-            if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded="
-                    + mPendingKeyguardOccluded);
-            if (setKeyguardOccludedLw(mPendingKeyguardOccluded)) {
-                return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER;
-            }
+        if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded commit occluded="
+                + mPendingKeyguardOccluded);
+
+        // TODO(b/276433230): Explicitly save before/after for occlude state in each
+        // Transition so we don't need to update SysUI every time.
+        if (setKeyguardOccludedLw(mPendingKeyguardOccluded)) {
+            return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER;
+        } else {
+            return 0;
         }
-        return 0;
     }
 
     /**
@@ -3889,6 +3895,7 @@
     private boolean setKeyguardOccludedLw(boolean isOccluded) {
         if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded);
         mKeyguardOccludedChanged = false;
+        mPendingKeyguardOccluded = isOccluded;
         mKeyguardDelegate.setOccluded(isOccluded, true /* notify */);
         return mKeyguardDelegate.isShowing();
     }
@@ -4154,7 +4161,7 @@
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_MUTE: {
                 if (down) {
-                    sendSystemKeyToStatusBarAsync(event.getKeyCode());
+                    sendSystemKeyToStatusBarAsync(event);
 
                     NotificationManager nm = getNotificationService();
                     if (nm != null && !mHandleVolumeKeysInWM) {
@@ -4432,7 +4439,7 @@
             case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY:
             case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: {
                 if (down && mStylusButtonsEnabled) {
-                    sendSystemKeyToStatusBarAsync(keyCode);
+                    sendSystemKeyToStatusBarAsync(event);
                 }
                 result &= ~ACTION_PASS_TO_USER;
                 break;
@@ -4529,7 +4536,7 @@
             if (!mAccessibilityManager.isEnabled()
                     || !mAccessibilityManager.sendFingerprintGesture(event.getKeyCode())) {
                 if (mSystemNavigationKeysEnabled) {
-                    sendSystemKeyToStatusBarAsync(event.getKeyCode());
+                    sendSystemKeyToStatusBarAsync(event);
                 }
             }
         }
@@ -4538,11 +4545,11 @@
     /**
      * Notify the StatusBar that a system key was pressed.
      */
-    private void sendSystemKeyToStatusBar(int keyCode) {
+    private void sendSystemKeyToStatusBar(KeyEvent key) {
         IStatusBarService statusBar = getStatusBarService();
         if (statusBar != null) {
             try {
-                statusBar.handleSystemKey(keyCode);
+                statusBar.handleSystemKey(key);
             } catch (RemoteException e) {
                 // Oh well.
             }
@@ -4552,8 +4559,8 @@
     /**
      * Notify the StatusBar that a system key was pressed without blocking the current thread.
      */
-    private void sendSystemKeyToStatusBarAsync(int keyCode) {
-        Message message = mHandler.obtainMessage(MSG_SYSTEM_KEY_PRESS, keyCode, 0);
+    private void sendSystemKeyToStatusBarAsync(KeyEvent keyEvent) {
+        Message message = mHandler.obtainMessage(MSG_SYSTEM_KEY_PRESS, keyEvent);
         message.setAsynchronous(true);
         mHandler.sendMessage(message);
     }
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index a9a1d5e..1a22b89 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -48,7 +48,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
@@ -324,16 +323,7 @@
     private boolean checkTidValid(int uid, int tgid, int [] tids) {
         // Make sure all tids belongs to the same UID (including isolated UID),
         // tids can belong to different application processes.
-        List<Integer> eligiblePids = null;
-        // To avoid deadlock, do not call into AMS if the call is from system.
-        if (uid != Process.SYSTEM_UID) {
-            eligiblePids = mAmInternal.getIsolatedProcesses(uid);
-        }
-        if (eligiblePids == null) {
-            eligiblePids = new ArrayList<>();
-        }
-        eligiblePids.add(tgid);
-
+        List<Integer> isolatedPids = null;
         for (int threadId : tids) {
             final String[] procStatusKeys = new String[] {
                     "Uid:",
@@ -345,7 +335,21 @@
             int pidOfThreadId = (int) output[1];
 
             // use PID check for isolated processes, use UID check for non-isolated processes.
-            if (eligiblePids.contains(pidOfThreadId) || uidOfThreadId == uid) {
+            if (pidOfThreadId == tgid || uidOfThreadId == uid) {
+                continue;
+            }
+            // Only call into AM if the tid is either isolated or invalid
+            if (isolatedPids == null) {
+                // To avoid deadlock, do not call into AMS if the call is from system.
+                if (uid == Process.SYSTEM_UID) {
+                    return false;
+                }
+                isolatedPids = mAmInternal.getIsolatedProcesses(uid);
+                if (isolatedPids == null) {
+                    return false;
+                }
+            }
+            if (isolatedPids.contains(pidOfThreadId)) {
                 continue;
             }
             return false;
diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
index d55fbc2..231ffc6 100644
--- a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
@@ -20,6 +20,7 @@
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -27,11 +28,10 @@
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.util.IndentingPrintWriter;
-import android.util.IntArray;
-import android.util.LongSparseArray;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 import android.util.SparseLongArray;
 import android.util.TimeSparseArray;
 import android.util.TimeUtils;
@@ -59,7 +59,7 @@
     private static final String TRACE_TRACK_WAKEUP_ATTRIBUTION = "wakeup_attribution";
     @VisibleForTesting
     static final long WAKEUP_REASON_HALF_WINDOW_MS = 500;
-    private static final long WAKEUP_WRITE_DELAY_MS = TimeUnit.MINUTES.toMillis(2);
+    private static final long WAKEUP_WRITE_DELAY_MS = TimeUnit.SECONDS.toMillis(30);
 
     private final Handler mHandler;
     private final IrqDeviceMap mIrqDeviceMap;
@@ -69,10 +69,15 @@
 
     @VisibleForTesting
     final TimeSparseArray<Wakeup> mWakeupEvents = new TimeSparseArray<>();
+
+    /* Maps timestamp -> {subsystem  -> {uid -> procState}} */
     @VisibleForTesting
-    final TimeSparseArray<SparseArray<SparseBooleanArray>> mWakeupAttribution =
+    final TimeSparseArray<SparseArray<SparseIntArray>> mWakeupAttribution =
             new TimeSparseArray<>();
 
+    final SparseIntArray mUidProcStates = new SparseIntArray();
+    private final SparseIntArray mReusableUidProcStates = new SparseIntArray(4);
+
     public CpuWakeupStats(Context context, int mapRes, Handler handler) {
         mIrqDeviceMap = IrqDeviceMap.getInstance(context, mapRes);
         mHandler = handler;
@@ -102,13 +107,14 @@
                     FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_UNKNOWN,
                     FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__UNKNOWN,
                     null,
-                    wakeupToLog.mElapsedMillis);
+                    wakeupToLog.mElapsedMillis,
+                    null);
             Trace.instantForTrack(Trace.TRACE_TAG_POWER, TRACE_TRACK_WAKEUP_ATTRIBUTION,
                     wakeupToLog.mElapsedMillis + " --");
             return;
         }
 
-        final SparseArray<SparseBooleanArray> wakeupAttribution = mWakeupAttribution.get(
+        final SparseArray<SparseIntArray> wakeupAttribution = mWakeupAttribution.get(
                 wakeupToLog.mElapsedMillis);
         if (wakeupAttribution == null) {
             // This is not expected but can theoretically happen in extreme situations, e.g. if we
@@ -121,24 +127,28 @@
 
         for (int i = 0; i < wakeupAttribution.size(); i++) {
             final int subsystem = wakeupAttribution.keyAt(i);
-            final SparseBooleanArray uidMap = wakeupAttribution.valueAt(i);
+            final SparseIntArray uidProcStates = wakeupAttribution.valueAt(i);
             final int[] uids;
-            if (uidMap == null || uidMap.size() == 0) {
-                uids = new int[0];
+            final int[] procStatesProto;
+
+            if (uidProcStates == null || uidProcStates.size() == 0) {
+                uids = procStatesProto = new int[0];
             } else {
-                final IntArray tmp = new IntArray(uidMap.size());
-                for (int j = 0; j < uidMap.size(); j++) {
-                    if (uidMap.valueAt(j)) {
-                        tmp.add(uidMap.keyAt(j));
-                    }
+                final int numUids = uidProcStates.size();
+                uids = new int[numUids];
+                procStatesProto = new int[numUids];
+                for (int j = 0; j < numUids; j++) {
+                    uids[j] = uidProcStates.keyAt(j);
+                    procStatesProto[j] = ActivityManager.processStateAmToProto(
+                            uidProcStates.valueAt(j));
                 }
-                uids = tmp.toArray();
             }
             FrameworkStatsLog.write(FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED,
                     FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_IRQ,
                     subsystemToStatsReason(subsystem),
                     uids,
-                    wakeupToLog.mElapsedMillis);
+                    wakeupToLog.mElapsedMillis,
+                    procStatesProto);
 
             if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
                 if (i == 0) {
@@ -154,6 +164,20 @@
                 traceEventBuilder.toString().trim());
     }
 
+    /**
+     * Clean up data for a uid that is being removed.
+     */
+    public synchronized void onUidRemoved(int uid) {
+        mUidProcStates.delete(uid);
+    }
+
+    /**
+     * Notes a procstate change for the given uid to maintain the mapping internally.
+     */
+    public synchronized void noteUidProcessState(int uid, int state) {
+        mUidProcStates.put(uid, state);
+    }
+
     /** Notes a wakeup reason as reported by SuspendControlService to battery stats. */
     public synchronized void noteWakeupTimeAndReason(long elapsedRealtime, long uptime,
             String rawReason) {
@@ -184,8 +208,17 @@
 
     /** Notes a waking activity that could have potentially woken up the CPU. */
     public synchronized void noteWakingActivity(int subsystem, long elapsedRealtime, int... uids) {
-        if (!attemptAttributionWith(subsystem, elapsedRealtime, uids)) {
-            mRecentWakingActivity.recordActivity(subsystem, elapsedRealtime, uids);
+        if (uids == null) {
+            return;
+        }
+        mReusableUidProcStates.clear();
+        for (int i = 0; i < uids.length; i++) {
+            mReusableUidProcStates.put(uids[i],
+                    mUidProcStates.get(uids[i], ActivityManager.PROCESS_STATE_UNKNOWN));
+        }
+        if (!attemptAttributionWith(subsystem, elapsedRealtime, mReusableUidProcStates)) {
+            mRecentWakingActivity.recordActivity(subsystem, elapsedRealtime,
+                    mReusableUidProcStates);
         }
     }
 
@@ -196,7 +229,7 @@
             return;
         }
 
-        SparseArray<SparseBooleanArray> attribution = mWakeupAttribution.get(wakeup.mElapsedMillis);
+        SparseArray<SparseIntArray> attribution = mWakeupAttribution.get(wakeup.mElapsedMillis);
         if (attribution == null) {
             attribution = new SparseArray<>();
             mWakeupAttribution.put(wakeup.mElapsedMillis, attribution);
@@ -210,14 +243,14 @@
             final long startTime = wakeup.mElapsedMillis - WAKEUP_REASON_HALF_WINDOW_MS;
             final long endTime = wakeup.mElapsedMillis + WAKEUP_REASON_HALF_WINDOW_MS;
 
-            final SparseBooleanArray uidsToBlame = mRecentWakingActivity.removeBetween(subsystem,
+            final SparseIntArray uidsToBlame = mRecentWakingActivity.removeBetween(subsystem,
                     startTime, endTime);
             attribution.put(subsystem, uidsToBlame);
         }
     }
 
     private synchronized boolean attemptAttributionWith(int subsystem, long activityElapsed,
-            int... uids) {
+            SparseIntArray uidProcStates) {
         final int startIdx = mWakeupEvents.closestIndexOnOrAfter(
                 activityElapsed - WAKEUP_REASON_HALF_WINDOW_MS);
         final int endIdx = mWakeupEvents.closestIndexOnOrBefore(
@@ -233,19 +266,19 @@
             if (subsystems.get(subsystem)) {
                 // We don't expect more than one wakeup to be found within such a short window, so
                 // just attribute this one and exit
-                SparseArray<SparseBooleanArray> attribution = mWakeupAttribution.get(
+                SparseArray<SparseIntArray> attribution = mWakeupAttribution.get(
                         wakeup.mElapsedMillis);
                 if (attribution == null) {
                     attribution = new SparseArray<>();
                     mWakeupAttribution.put(wakeup.mElapsedMillis, attribution);
                 }
-                SparseBooleanArray uidsToBlame = attribution.get(subsystem);
+                SparseIntArray uidsToBlame = attribution.get(subsystem);
                 if (uidsToBlame == null) {
-                    uidsToBlame = new SparseBooleanArray(uids.length);
-                    attribution.put(subsystem, uidsToBlame);
-                }
-                for (final int uid : uids) {
-                    uidsToBlame.put(uid, true);
+                    attribution.put(subsystem, uidProcStates.clone());
+                } else {
+                    for (int i = 0; i < uidProcStates.size(); i++) {
+                        uidsToBlame.put(uidProcStates.keyAt(i), uidProcStates.valueAt(i));
+                    }
                 }
                 return true;
             }
@@ -267,6 +300,19 @@
         mRecentWakingActivity.dump(pw, nowElapsed);
         pw.println();
 
+        pw.println("Current proc-state map (" + mUidProcStates.size() + "):");
+        pw.increaseIndent();
+        for (int i = 0; i < mUidProcStates.size(); i++) {
+            if (i > 0) {
+                pw.print(", ");
+            }
+            UserHandle.formatUid(pw, mUidProcStates.keyAt(i));
+            pw.print(":" + ActivityManager.procStateToString(mUidProcStates.valueAt(i)));
+        }
+        pw.println();
+        pw.decreaseIndent();
+        pw.println();
+
         final SparseLongArray attributionStats = new SparseLongArray();
         pw.println("Wakeup events:");
         pw.increaseIndent();
@@ -278,7 +324,7 @@
             final Wakeup wakeup = mWakeupEvents.valueAt(i);
             pw.println(wakeup);
             pw.print("Attribution: ");
-            final SparseArray<SparseBooleanArray> attribution = mWakeupAttribution.get(
+            final SparseArray<SparseIntArray> attribution = mWakeupAttribution.get(
                     wakeup.mElapsedMillis);
             if (attribution == null) {
                 pw.println("N/A");
@@ -292,15 +338,17 @@
                     int attributed = IntPair.first(counters);
                     final int total = IntPair.second(counters) + 1;
 
-                    pw.print("subsystem: " + subsystemToString(attribution.keyAt(subsystemIdx)));
-                    pw.print(", uids: [");
-                    final SparseBooleanArray uids = attribution.valueAt(subsystemIdx);
-                    if (uids != null) {
-                        for (int uidIdx = 0; uidIdx < uids.size(); uidIdx++) {
+                    pw.print(subsystemToString(attribution.keyAt(subsystemIdx)));
+                    pw.print(" [");
+                    final SparseIntArray uidProcStates = attribution.valueAt(subsystemIdx);
+                    if (uidProcStates != null) {
+                        for (int uidIdx = 0; uidIdx < uidProcStates.size(); uidIdx++) {
                             if (uidIdx > 0) {
                                 pw.print(", ");
                             }
-                            UserHandle.formatUid(pw, uids.keyAt(uidIdx));
+                            UserHandle.formatUid(pw, uidProcStates.keyAt(uidIdx));
+                            pw.print(" " + ActivityManager.procStateToString(
+                                    uidProcStates.valueAt(uidIdx)));
                         }
                         attributed++;
                     }
@@ -330,29 +378,39 @@
         pw.println();
     }
 
+    /**
+     * This class stores recent unattributed activity history per subsystem.
+     * The activity is stored as a mapping of subsystem to timestamp to uid to procstate.
+     */
     private static final class WakingActivityHistory {
         private static final long WAKING_ACTIVITY_RETENTION_MS = TimeUnit.MINUTES.toMillis(10);
 
-        private SparseArray<TimeSparseArray<SparseBooleanArray>> mWakingActivity =
+        private SparseArray<TimeSparseArray<SparseIntArray>> mWakingActivity =
                 new SparseArray<>();
 
-        void recordActivity(int subsystem, long elapsedRealtime, int... uids) {
-            if (uids == null) {
+        void recordActivity(int subsystem, long elapsedRealtime, SparseIntArray uidProcStates) {
+            if (uidProcStates == null) {
                 return;
             }
-            TimeSparseArray<SparseBooleanArray> wakingActivity = mWakingActivity.get(subsystem);
+            TimeSparseArray<SparseIntArray> wakingActivity = mWakingActivity.get(subsystem);
             if (wakingActivity == null) {
                 wakingActivity = new TimeSparseArray<>();
                 mWakingActivity.put(subsystem, wakingActivity);
             }
-            SparseBooleanArray uidsToBlame = wakingActivity.get(elapsedRealtime);
+            final SparseIntArray uidsToBlame = wakingActivity.get(elapsedRealtime);
             if (uidsToBlame == null) {
-                uidsToBlame = new SparseBooleanArray(uids.length);
+                wakingActivity.put(elapsedRealtime, uidProcStates.clone());
+            } else {
+                for (int i = 0; i < uidProcStates.size(); i++) {
+                    final int uid = uidProcStates.keyAt(i);
+                    // Just in case there are duplicate uids reported with the same timestamp,
+                    // keep the processState which was reported first.
+                    if (uidsToBlame.indexOfKey(uid) < 0) {
+                        uidsToBlame.put(uid, uidProcStates.valueAt(i));
+                    }
+                }
                 wakingActivity.put(elapsedRealtime, uidsToBlame);
             }
-            for (int i = 0; i < uids.length; i++) {
-                uidsToBlame.put(uids[i], true);
-            }
             // Limit activity history per subsystem to the last WAKING_ACTIVITY_RETENTION_MS.
             // Note that the last activity is always present, even if it occurred before
             // WAKING_ACTIVITY_RETENTION_MS.
@@ -365,7 +423,7 @@
 
         void clearAllBefore(long elapsedRealtime) {
             for (int subsystemIdx = mWakingActivity.size() - 1; subsystemIdx >= 0; subsystemIdx--) {
-                final TimeSparseArray<SparseBooleanArray> activityPerSubsystem =
+                final TimeSparseArray<SparseIntArray> activityPerSubsystem =
                         mWakingActivity.valueAt(subsystemIdx);
                 final int endIdx = activityPerSubsystem.closestIndexOnOrBefore(elapsedRealtime);
                 for (int removeIdx = endIdx; removeIdx >= 0; removeIdx--) {
@@ -377,20 +435,20 @@
             }
         }
 
-        SparseBooleanArray removeBetween(int subsystem, long startElapsed, long endElapsed) {
-            final SparseBooleanArray uidsToReturn = new SparseBooleanArray();
+        SparseIntArray removeBetween(int subsystem, long startElapsed, long endElapsed) {
+            final SparseIntArray uidsToReturn = new SparseIntArray();
 
-            final TimeSparseArray<SparseBooleanArray> activityForSubsystem =
+            final TimeSparseArray<SparseIntArray> activityForSubsystem =
                     mWakingActivity.get(subsystem);
             if (activityForSubsystem != null) {
                 final int startIdx = activityForSubsystem.closestIndexOnOrAfter(startElapsed);
                 final int endIdx = activityForSubsystem.closestIndexOnOrBefore(endElapsed);
                 for (int i = endIdx; i >= startIdx; i--) {
-                    final SparseBooleanArray uidsForTime = activityForSubsystem.valueAt(i);
+                    final SparseIntArray uidsForTime = activityForSubsystem.valueAt(i);
                     for (int j = 0; j < uidsForTime.size(); j++) {
-                        if (uidsForTime.valueAt(j)) {
-                            uidsToReturn.put(uidsForTime.keyAt(j), true);
-                        }
+                        // In case the same uid appears in different uidsForTime maps, there is no
+                        // good way to choose one processState, so just arbitrarily pick any.
+                        uidsToReturn.put(uidsForTime.keyAt(j), uidsForTime.valueAt(j));
                     }
                 }
                 // More efficient to remove in a separate loop as it avoids repeatedly calling gc().
@@ -409,25 +467,23 @@
             pw.increaseIndent();
             for (int i = 0; i < mWakingActivity.size(); i++) {
                 pw.println("Subsystem " + subsystemToString(mWakingActivity.keyAt(i)) + ":");
-                final LongSparseArray<SparseBooleanArray> wakingActivity =
-                        mWakingActivity.valueAt(i);
+                final TimeSparseArray<SparseIntArray> wakingActivity = mWakingActivity.valueAt(i);
                 if (wakingActivity == null) {
                     continue;
                 }
                 pw.increaseIndent();
                 for (int j = wakingActivity.size() - 1; j >= 0; j--) {
                     TimeUtils.formatDuration(wakingActivity.keyAt(j), nowElapsed, pw);
-                    final SparseBooleanArray uidsToBlame = wakingActivity.valueAt(j);
+                    final SparseIntArray uidsToBlame = wakingActivity.valueAt(j);
                     if (uidsToBlame == null) {
                         pw.println();
                         continue;
                     }
                     pw.print(": ");
                     for (int k = 0; k < uidsToBlame.size(); k++) {
-                        if (uidsToBlame.valueAt(k)) {
-                            UserHandle.formatUid(pw, uidsToBlame.keyAt(k));
-                            pw.print(", ");
-                        }
+                        UserHandle.formatUid(pw, uidsToBlame.keyAt(k));
+                        pw.print(" [" + ActivityManager.procStateToString(uidsToBlame.valueAt(k)));
+                        pw.print("], ");
                     }
                     pw.println();
                 }
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index fdc2822..8da9e1d 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -154,7 +154,6 @@
 import android.security.metrics.KeystoreAtom;
 import android.security.metrics.KeystoreAtomPayload;
 import android.security.metrics.RkpErrorStats;
-import android.security.metrics.RkpPoolStats;
 import android.security.metrics.StorageStats;
 import android.stats.storage.StorageEnums;
 import android.telephony.ModemActivityInfo;
@@ -738,7 +737,6 @@
                             return pullInstalledIncrementalPackagesLocked(atomTag, data);
                         }
                     case FrameworkStatsLog.KEYSTORE2_STORAGE_STATS:
-                    case FrameworkStatsLog.RKP_POOL_STATS:
                     case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_GENERAL_INFO:
                     case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_AUTH_INFO:
                     case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_PURPOSE_AND_MODES_INFO:
@@ -950,7 +948,6 @@
         registerSettingsStats();
         registerInstalledIncrementalPackages();
         registerKeystoreStorageStats();
-        registerRkpPoolStats();
         registerKeystoreKeyCreationWithGeneralInfo();
         registerKeystoreKeyCreationWithAuthInfo();
         registerKeystoreKeyCreationWithPurposeModesInfo();
@@ -4316,14 +4313,6 @@
                 mStatsCallbackImpl);
     }
 
-    private void registerRkpPoolStats() {
-        mStatsManager.setPullAtomCallback(
-                FrameworkStatsLog.RKP_POOL_STATS,
-                null, // use default PullAtomMetadata values,
-                DIRECT_EXECUTOR,
-                mStatsCallbackImpl);
-    }
-
     private void registerKeystoreKeyCreationWithGeneralInfo() {
         mStatsManager.setPullAtomCallback(
                 FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_GENERAL_INFO,
@@ -4431,19 +4420,6 @@
         return StatsManager.PULL_SUCCESS;
     }
 
-    int parseRkpPoolStats(KeystoreAtom[] atoms, List<StatsEvent> pulledData) {
-        for (KeystoreAtom atomWrapper : atoms) {
-            if (atomWrapper.payload.getTag() != KeystoreAtomPayload.rkpPoolStats) {
-                return StatsManager.PULL_SKIP;
-            }
-            RkpPoolStats atom = atomWrapper.payload.getRkpPoolStats();
-            pulledData.add(FrameworkStatsLog.buildStatsEvent(
-                    FrameworkStatsLog.RKP_POOL_STATS, atom.security_level, atom.expiring,
-                    atom.unassigned, atom.attested, atom.total));
-        }
-        return StatsManager.PULL_SUCCESS;
-    }
-
     int parseKeystoreKeyCreationWithGeneralInfo(KeystoreAtom[] atoms, List<StatsEvent> pulledData) {
         for (KeystoreAtom atomWrapper : atoms) {
             if (atomWrapper.payload.getTag()
@@ -4576,8 +4552,6 @@
             switch (atomTag) {
                 case FrameworkStatsLog.KEYSTORE2_STORAGE_STATS:
                     return parseKeystoreStorageStats(atoms, pulledData);
-                case FrameworkStatsLog.RKP_POOL_STATS:
-                    return parseRkpPoolStats(atoms, pulledData);
                 case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_GENERAL_INFO:
                     return parseKeystoreKeyCreationWithGeneralInfo(atoms, pulledData);
                 case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_AUTH_INFO:
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 35e88c1..363d2fd 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -84,6 +84,7 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.KeyEvent;
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
@@ -902,12 +903,12 @@
     }
 
     @Override
-    public void handleSystemKey(int key) throws RemoteException {
+    public void handleSystemKey(KeyEvent key) throws RemoteException {
         if (!checkCanCollapseStatusBar("handleSystemKey")) {
             return;
         }
 
-        mLastSystemKey = key;
+        mLastSystemKey = key.getKeyCode();
 
         if (mBar != null) {
             try {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 94e041a..5f56923 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -179,6 +179,9 @@
 import static com.android.server.wm.ActivityRecordProto.PROVIDES_MAX_BOUNDS;
 import static com.android.server.wm.ActivityRecordProto.REPORTED_DRAWN;
 import static com.android.server.wm.ActivityRecordProto.REPORTED_VISIBLE;
+import static com.android.server.wm.ActivityRecordProto.SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT;
+import static com.android.server.wm.ActivityRecordProto.SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT;
+import static com.android.server.wm.ActivityRecordProto.SHOULD_REFRESH_ACTIVITY_VIA_PAUSE_FOR_CAMERA_COMPAT;
 import static com.android.server.wm.ActivityRecordProto.SHOULD_SEND_COMPAT_FAKE_FOCUS;
 import static com.android.server.wm.ActivityRecordProto.STARTING_DISPLAYED;
 import static com.android.server.wm.ActivityRecordProto.STARTING_MOVED;
@@ -10228,6 +10231,12 @@
         proto.write(LAST_DROP_INPUT_MODE, mLastDropInputMode);
         proto.write(OVERRIDE_ORIENTATION, getOverrideOrientation());
         proto.write(SHOULD_SEND_COMPAT_FAKE_FOCUS, shouldSendCompatFakeFocus());
+        proto.write(SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT,
+                mLetterboxUiController.shouldForceRotateForCameraCompat());
+        proto.write(SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT,
+                mLetterboxUiController.shouldRefreshActivityForCameraCompat());
+        proto.write(SHOULD_REFRESH_ACTIVITY_VIA_PAUSE_FOR_CAMERA_COMPAT,
+                mLetterboxUiController.shouldRefreshActivityViaPauseForCameraCompat());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index c9213d5..d2fc393 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -100,6 +100,7 @@
 import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORDERED_ID;
 import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_LAST_ORDERED_ID;
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
@@ -2011,7 +2012,7 @@
             return;
         }
 
-        if (r == mRootWindowContainer.getTopResumedActivity()) {
+        if (r.isState(RESUMED) && r == mRootWindowContainer.getTopResumedActivity()) {
             setLastResumedActivityUncheckLocked(r, "setFocusedTask-alreadyTop");
             return;
         }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 22dd0e5..d31fe23 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1981,6 +1981,14 @@
             return;
         }
 
+        if (controlTarget != null) {
+            final WindowState win = controlTarget.getWindow();
+
+            if (win != null && win.isActivityTypeDream()) {
+                return;
+            }
+        }
+
         final @InsetsType int restorePositionTypes = (Type.statusBars() | Type.navigationBars())
                 & controlTarget.getRequestedVisibleTypes();
 
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 052c09a..d65f464 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -99,23 +99,6 @@
         }
     }
 
-    WindowState getHostWindow(IBinder inputToken) {
-        EmbeddedWindow embeddedWindow = mWindows.get(inputToken);
-        return embeddedWindow != null ? embeddedWindow.mHostWindowState : null;
-    }
-
-    boolean isOverlay(IBinder inputToken) {
-        EmbeddedWindow embeddedWindow = mWindows.get(inputToken);
-        return embeddedWindow != null ? embeddedWindow.getIsOverlay() : false;
-    }
-
-    void setIsOverlay(IBinder focusGrantToken) {
-        EmbeddedWindow embeddedWindow = mWindowsByFocusToken.get(focusGrantToken);
-        if (embeddedWindow != null) {
-            embeddedWindow.setIsOverlay();
-        }
-    }
-
     void remove(IWindow client) {
         for (int i = mWindows.size() - 1; i >= 0; i--) {
             EmbeddedWindow ew = mWindows.valueAt(i);
@@ -176,14 +159,15 @@
         public Session mSession;
         InputChannel mInputChannel;
         final int mWindowType;
-        // Track whether the EmbeddedWindow is a system hosted overlay via
-        // {@link OverlayHost}. In the case of client hosted overlays, the client
-        // view hierarchy will take care of invoking requestEmbeddedWindowFocus
-        // but for system hosted overlays we have to do this via tapOutsideDetection
-        // and this variable is mostly used for tracking that.
-        boolean mIsOverlay = false;
 
-        private IBinder mFocusGrantToken;
+        /**
+         * A unique token associated with the embedded window that can be used by the host window
+         * to request focus transfer to the embedded. This is not the input token since we don't
+         * want to give clients access to each others input token.
+         */
+        private final IBinder mFocusGrantToken;
+
+        private boolean mIsFocusable;
 
         /**
          * @param session  calling session to check ownership of the window
@@ -199,7 +183,8 @@
          */
         EmbeddedWindow(Session session, WindowManagerService service, IWindow clientToken,
                        WindowState hostWindowState, int ownerUid, int ownerPid, int windowType,
-                       int displayId, IBinder focusGrantToken, String inputHandleName) {
+                       int displayId, IBinder focusGrantToken, String inputHandleName,
+                       boolean isFocusable) {
             mSession = session;
             mWmService = service;
             mClient = clientToken;
@@ -214,6 +199,7 @@
             final String hostWindowName =
                     (mHostWindowState != null) ? "-" + mHostWindowState.getWindowTag().toString()
                             : "";
+            mIsFocusable = isFocusable;
             mName = "Embedded{" + inputHandleName + hostWindowName + "}";
         }
 
@@ -279,13 +265,6 @@
             return mOwnerUid;
         }
 
-        void setIsOverlay() {
-            mIsOverlay = true;
-        }
-        boolean getIsOverlay() {
-            return mIsOverlay;
-        }
-
         IBinder getFocusGrantToken() {
             return mFocusGrantToken;
         }
@@ -297,20 +276,33 @@
             return null;
         }
 
+        void setIsFocusable(boolean isFocusable) {
+            mIsFocusable = isFocusable;
+        }
+
         /**
-         * System hosted overlays need the WM to invoke grantEmbeddedWindowFocus and
-         * so we need to participate inside handlePointerDownOutsideFocus logic
-         * however client hosted overlays will rely on the hosting view hierarchy
-         * to grant and revoke focus, and so the server side logic is not needed.
+         * When an embedded window is touched when it's not currently focus, we need to switch
+         * focus to that embedded window unless the embedded window was marked as not focusable.
          */
         @Override
         public boolean receiveFocusFromTapOutside() {
-            return mIsOverlay;
+            return mIsFocusable;
         }
 
         private void handleTap(boolean grantFocus) {
             if (mInputChannel != null) {
-                mWmService.grantEmbeddedWindowFocus(mSession, mFocusGrantToken, grantFocus);
+                if (mHostWindowState != null) {
+                    mWmService.grantEmbeddedWindowFocus(mSession, mHostWindowState.mClient,
+                            mFocusGrantToken, grantFocus);
+                    if (grantFocus) {
+                        // If granting focus to the embedded when tapped, we need to ensure the host
+                        // gains focus as well or the transfer won't take effect since it requires
+                        // the host to transfer the focus to the embedded.
+                        mHostWindowState.handleTapOutsideFocusInsideSelf();
+                    }
+                } else {
+                    mWmService.grantEmbeddedWindowFocus(mSession, mFocusGrantToken, grantFocus);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 4be98a3..b4dffdc 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -48,7 +48,7 @@
  * Controller for IME inset source on the server. It's called provider as it provides the
  * {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}.
  */
-final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider {
+final class ImeInsetsSourceProvider extends InsetsSourceProvider {
 
     /** The token tracking the current IME request or {@code null} otherwise. */
     @Nullable
diff --git a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
index 301c184..3d4e0eb 100644
--- a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
+++ b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
@@ -289,6 +289,14 @@
         mChanged = true;
     }
 
+    void setFocusTransferTarget(IBinder toToken) {
+        if (mHandle.focusTransferTarget == toToken) {
+            return;
+        }
+        mHandle.focusTransferTarget = toToken;
+        mChanged = true;
+    }
+
     @Override
     public String toString() {
         return mHandle + ", changed=" + mChanged;
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index a8c9cd3..fe13b87 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -223,10 +223,10 @@
 
         startAnimation(false /* show */, () -> {
             synchronized (mDisplayContent.mWmService.mGlobalLock) {
-                final SparseArray<WindowContainerInsetsSourceProvider> providers =
+                final SparseArray<InsetsSourceProvider> providers =
                         mStateController.getSourceProviders();
                 for (int i = providers.size() - 1; i >= 0; i--) {
-                    final WindowContainerInsetsSourceProvider provider = providers.valueAt(i);
+                    final InsetsSourceProvider provider = providers.valueAt(i);
                     if (!isTransient(provider.getSource().getType())) {
                         continue;
                     }
@@ -341,11 +341,10 @@
             }
         }
 
-        final SparseArray<WindowContainerInsetsSourceProvider> providers =
-                mStateController.getSourceProviders();
+        final SparseArray<InsetsSourceProvider> providers = mStateController.getSourceProviders();
         final int windowType = attrs.type;
         for (int i = providers.size() - 1; i >= 0; i--) {
-            final WindowContainerInsetsSourceProvider otherProvider = providers.valueAt(i);
+            final InsetsSourceProvider otherProvider = providers.valueAt(i);
             if (otherProvider.overridesFrame(windowType)) {
                 if (state == originalState) {
                     state = new InsetsState(state);
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 0953604..3b23f97 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -58,7 +58,7 @@
  * Controller for a specific inset source on the server. It's called provider as it provides the
  * {@link InsetsSource} to the client that uses it in {@link android.view.InsetsSourceConsumer}.
  */
-abstract class InsetsSourceProvider {
+class InsetsSourceProvider {
 
     protected final DisplayContent mDisplayContent;
     protected final @NonNull InsetsSource mSource;
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index fca333d..249ead0 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -56,7 +56,7 @@
     private final InsetsState mState = new InsetsState();
     private final DisplayContent mDisplayContent;
 
-    private final SparseArray<WindowContainerInsetsSourceProvider> mProviders = new SparseArray<>();
+    private final SparseArray<InsetsSourceProvider> mProviders = new SparseArray<>();
     private final ArrayMap<InsetsControlTarget, ArrayList<InsetsSourceProvider>>
             mControlTargetProvidersMap = new ArrayMap<>();
     private final SparseArray<InsetsControlTarget> mIdControlTargetMap = new SparseArray<>();
@@ -106,22 +106,22 @@
         return result;
     }
 
-    SparseArray<WindowContainerInsetsSourceProvider> getSourceProviders() {
+    SparseArray<InsetsSourceProvider> getSourceProviders() {
         return mProviders;
     }
 
     /**
      * @return The provider of a specific source ID.
      */
-    WindowContainerInsetsSourceProvider getOrCreateSourceProvider(int id, @InsetsType int type) {
-        WindowContainerInsetsSourceProvider provider = mProviders.get(id);
+    InsetsSourceProvider getOrCreateSourceProvider(int id, @InsetsType int type) {
+        InsetsSourceProvider provider = mProviders.get(id);
         if (provider != null) {
             return provider;
         }
         final InsetsSource source = mState.getOrCreateSource(id, type);
         provider = id == ID_IME
                 ? new ImeInsetsSourceProvider(source, this, mDisplayContent)
-                : new WindowContainerInsetsSourceProvider(source, this, mDisplayContent);
+                : new InsetsSourceProvider(source, this, mDisplayContent);
         mProviders.put(id, provider);
         return provider;
     }
@@ -334,7 +334,7 @@
         }
         mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
             for (int i = mProviders.size() - 1; i >= 0; i--) {
-                final WindowContainerInsetsSourceProvider provider = mProviders.valueAt(i);
+                final InsetsSourceProvider provider = mProviders.valueAt(i);
                 provider.onSurfaceTransactionApplied();
             }
             final ArraySet<InsetsControlTarget> newControlTargets = new ArraySet<>();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b7e2265..db44532 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4687,6 +4687,7 @@
         if (!isAttached()) {
             return;
         }
+        mTransitionController.collect(this);
 
         final TaskDisplayArea taskDisplayArea = getDisplayArea();
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index c6ba600..0fe1f92 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -50,6 +50,7 @@
 import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
 import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
 import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
@@ -183,6 +184,12 @@
     private final ArrayList<DisplayContent> mTargetDisplays = new ArrayList<>();
 
     /**
+     * The (non alwaysOnTop) tasks which were on-top of their display before the transition. If
+     * tasks are nested, all the tasks that are parents of the on-top task are also included.
+     */
+    private final ArrayList<Task> mOnTopTasksStart = new ArrayList<>();
+
+    /**
      * Set of participating windowtokens (activity/wallpaper) which are visible at the end of
      * the transition animation.
      */
@@ -515,6 +522,7 @@
         mParticipants.add(wc);
         if (wc.getDisplayContent() != null && !mTargetDisplays.contains(wc.getDisplayContent())) {
             mTargetDisplays.add(wc.getDisplayContent());
+            addOnTopTasks(wc.getDisplayContent(), mOnTopTasksStart);
         }
         if (info.mShowWallpaper) {
             // Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set.
@@ -526,6 +534,27 @@
         }
     }
 
+    /** Adds the top non-alwaysOnTop tasks within `task` to `out`. */
+    private static void addOnTopTasks(Task task, ArrayList<Task> out) {
+        for (int i = task.getChildCount() - 1; i >= 0; --i) {
+            final Task child = task.getChildAt(i).asTask();
+            if (child == null) return;
+            if (child.getWindowConfiguration().isAlwaysOnTop()) continue;
+            out.add(child);
+            addOnTopTasks(child, out);
+            break;
+        }
+    }
+
+    /** Get the top non-alwaysOnTop leaf task on the display `dc`. */
+    private static void addOnTopTasks(DisplayContent dc, ArrayList<Task> out) {
+        final Task topNotAlwaysOnTop = dc.getRootTask(
+                t -> !t.getWindowConfiguration().isAlwaysOnTop());
+        if (topNotAlwaysOnTop == null) return;
+        out.add(topNotAlwaysOnTop);
+        addOnTopTasks(topNotAlwaysOnTop, out);
+    }
+
     /**
      * Records wc as changing its state of existence during this transition. For example, a new
      * task is considered an existence change while moving a task to front is not. wc is added
@@ -1000,11 +1029,13 @@
                 InsetsControlTarget prevImeTarget = dc.getImeTarget(
                         DisplayContent.IME_TARGET_CONTROL);
                 InsetsControlTarget newImeTarget = null;
+                TaskDisplayArea transientTDA = null;
                 // Transient-launch activities cannot be IME target (WindowState#canBeImeTarget),
                 // so re-compute in case the IME target is changed after transition.
                 for (int t = 0; t < mTransientLaunches.size(); ++t) {
                     if (mTransientLaunches.keyAt(t).getDisplayContent() == dc) {
                         newImeTarget = dc.computeImeTarget(true /* updateImeTarget */);
+                        transientTDA = mTransientLaunches.keyAt(i).getTaskDisplayArea();
                         break;
                     }
                 }
@@ -1015,10 +1046,17 @@
                     InputMethodManagerInternal.get().updateImeWindowStatus(
                             false /* disableImeIcon */);
                 }
+                // An uncommitted transient launch can leave incomplete lifecycles if visibilities
+                // didn't change (eg. re-ordering with translucent tasks will leave launcher
+                // in RESUMED state), so force an update here.
+                if (!hasVisibleTransientLaunch && transientTDA != null) {
+                    transientTDA.pauseBackTasks(null /* resuming */);
+                }
             }
             dc.removeImeSurfaceImmediately();
             dc.handleCompleteDeferredRemoval();
         }
+        validateKeyguardOcclusion();
         validateVisibility();
 
         mState = STATE_FINISHED;
@@ -1140,9 +1178,13 @@
         }
         // Check whether the participants were animated from back navigation.
         mController.mAtm.mBackNavigationController.onTransactionReady(this);
+
+        collectOrderChanges();
+
         // Resolve the animating targets from the participants.
         mTargets = calculateTargets(mParticipants, mChanges);
         final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
+        info.setDebugId(mSyncId);
 
         // Repopulate the displays based on the resolved targets.
         mTargetDisplays.clear();
@@ -1175,8 +1217,6 @@
             if (mRecentsDisplayId != INVALID_DISPLAY) break;
         }
 
-        handleNonAppWindowsInTransition(mType, mFlags);
-
         // The callback is only populated for custom activity-level client animations
         sendRemoteCallback(mClientAnimationStartCallback);
 
@@ -1291,6 +1331,27 @@
         info.releaseAnimSurfaces();
     }
 
+    /** Collect tasks which moved-to-top but didn't change otherwise. */
+    @VisibleForTesting
+    void collectOrderChanges() {
+        if (mOnTopTasksStart.isEmpty()) return;
+        final ArrayList<Task> onTopTasksEnd = new ArrayList<>();
+        for (int i = 0; i < mTargetDisplays.size(); ++i) {
+            addOnTopTasks(mTargetDisplays.get(i), onTopTasksEnd);
+        }
+        for (int i = 0; i < onTopTasksEnd.size(); ++i) {
+            final Task task = onTopTasksEnd.get(i);
+            if (mOnTopTasksStart.contains(task)) continue;
+            mParticipants.add(task);
+            int changeIdx = mChanges.indexOfKey(task);
+            if (changeIdx < 0) {
+                mChanges.put(task, new ChangeInfo(task));
+                changeIdx = mChanges.indexOfKey(task);
+            }
+            mChanges.valueAt(changeIdx).mFlags |= ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP;
+        }
+    }
+
     private void postCleanupOnFailure() {
         mController.mAtm.mH.post(() -> {
             synchronized (mController.mAtm.mGlobalLock) {
@@ -1480,19 +1541,6 @@
         }
     }
 
-    private void handleNonAppWindowsInTransition(
-            @TransitionType int transit, @TransitionFlags int flags) {
-        if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
-            // If the occlusion changed but the transition isn't an occlude/unocclude transition,
-            // then we have to notify KeyguardService directly. This can happen if there is
-            // another ongoing transition when the app changes occlusion OR if the app dies or
-            // is killed. Both of these are common during tests.
-            if (transit != TRANSIT_KEYGUARD_OCCLUDE && transit != TRANSIT_KEYGUARD_UNOCCLUDE) {
-                mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange();
-            }
-        }
-    }
-
     private void reportStartReasonsToLogger() {
         // Record transition start in metrics logger. We just assume everything is "DRAWN"
         // at this point since splash-screen is a presentation (shell) detail.
@@ -2185,6 +2233,13 @@
         return mainWin.getAttrs().rotationAnimation;
     }
 
+    private void validateKeyguardOcclusion() {
+        if ((mFlags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
+            mController.mStateValidators.add(
+                mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange);
+        }
+    }
+
     private void validateVisibility() {
         for (int i = mTargets.size() - 1; i >= 0; --i) {
             if (reduceMode(mTargets.get(i).mReadyMode) != TRANSIT_CLOSE) {
@@ -2246,13 +2301,17 @@
          */
         private static final int FLAG_CHANGE_YES_ANIMATION = 0x10;
 
+        /** Whether this change's container moved to the top. */
+        private static final int FLAG_CHANGE_MOVED_TO_TOP = 0x20;
+
         @IntDef(prefix = { "FLAG_" }, value = {
                 FLAG_NONE,
                 FLAG_SEAMLESS_ROTATION,
                 FLAG_TRANSIENT_LAUNCH,
                 FLAG_ABOVE_TRANSIENT_LAUNCH,
                 FLAG_CHANGE_NO_ANIMATION,
-                FLAG_CHANGE_YES_ANIMATION
+                FLAG_CHANGE_YES_ANIMATION,
+                FLAG_CHANGE_MOVED_TO_TOP
         })
         @Retention(RetentionPolicy.SOURCE)
         @interface Flag {}
@@ -2283,7 +2342,7 @@
         int mDisplayId = -1;
         @ActivityInfo.Config int mKnownConfigChanges;
 
-        /** These are just extra info. They aren't used for change-detection. */
+        /** Extra information about this change. */
         @Flag int mFlags = FLAG_NONE;
 
         /** Snapshot surface and luma, if relevant. */
@@ -2335,7 +2394,8 @@
                     || (mWindowingMode != 0 && mContainer.getWindowingMode() != mWindowingMode)
                     || !mContainer.getBounds().equals(mAbsoluteBounds)
                     || mRotation != mContainer.getWindowConfiguration().getRotation()
-                    || mDisplayId != getDisplayId(mContainer);
+                    || mDisplayId != getDisplayId(mContainer)
+                    || (mFlags & ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP) != 0;
         }
 
         @TransitionInfo.TransitionMode
@@ -2436,6 +2496,9 @@
                     && (mFlags & FLAG_CHANGE_YES_ANIMATION) == 0) {
                 flags |= FLAG_NO_ANIMATION;
             }
+            if ((mFlags & FLAG_CHANGE_MOVED_TO_TOP) != 0) {
+                flags |= FLAG_MOVED_TO_TOP;
+            }
             return flags;
         }
 
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index 57c0d65..a4c931c 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -365,6 +365,24 @@
         Trace.endSection();
     }
 
+    /**
+     * Being called while taking a bugreport so that tracing files can be included in the bugreport.
+     *
+     * @param pw Print writer
+     */
+    public void saveForBugreport(@Nullable PrintWriter pw) {
+        if (IS_USER) {
+            LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
+            return;
+        }
+        Trace.beginSection("TransitionTracer#saveForBugreport");
+        synchronized (mEnabledLock) {
+            final File outputFile = new File(TRACE_FILE);
+            writeTraceToFileLocked(pw, outputFile);
+        }
+        Trace.endSection();
+    }
+
     boolean isActiveTracingEnabled() {
         return mActiveTracingEnabled;
     }
diff --git a/services/core/java/com/android/server/wm/TrustedOverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
index 88c410b..f8edc2b 100644
--- a/services/core/java/com/android/server/wm/TrustedOverlayHost.java
+++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
@@ -90,8 +90,6 @@
         requireOverlaySurfaceControl();
         mOverlays.add(p);
 
-        mWmService.mEmbeddedWindowController.setIsOverlay(p.getInputToken());
-
         SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
         t.reparent(p.getSurfaceControl(), mSurfaceControl)
             .show(p.getSurfaceControl());
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 2e21782..4117641 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -4122,7 +4122,7 @@
         }
 
         private void hideInsetSourceViewOverflows() {
-            final SparseArray<WindowContainerInsetsSourceProvider> providers =
+            final SparseArray<InsetsSourceProvider> providers =
                     getDisplayContent().getInsetsStateController().getSourceProviders();
             for (int i = providers.size(); i >= 0; i--) {
                 final InsetsSourceProvider insetProvider = providers.valueAt(i);
diff --git a/services/core/java/com/android/server/wm/WindowContainerInsetsSourceProvider.java b/services/core/java/com/android/server/wm/WindowContainerInsetsSourceProvider.java
deleted file mode 100644
index aa2e8f5..0000000
--- a/services/core/java/com/android/server/wm/WindowContainerInsetsSourceProvider.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.view.InsetsSource;
-
-/**
- * Controller for a specific inset source on the server. It's called provider as it provides the
- * {@link InsetsSource} to the client that uses it in {@link android.view.InsetsSourceConsumer}.
- */
-class WindowContainerInsetsSourceProvider extends InsetsSourceProvider {
-    // TODO(b/218734524): Move the window container specific stuff from InsetsSourceProvider to
-    //  this class.
-
-    WindowContainerInsetsSourceProvider(InsetsSource source,
-            InsetsStateController stateController, DisplayContent displayContent) {
-        super(source, stateController, displayContent);
-    }
-}
-
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d7c9d9c..2156c6d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8600,7 +8600,8 @@
             EmbeddedWindowController.EmbeddedWindow win =
                     new EmbeddedWindowController.EmbeddedWindow(session, this, window,
                             mInputToWindowMap.get(hostInputToken), callingUid, callingPid,
-                            sanitizedType, displayId, focusGrantToken, inputHandleName);
+                            sanitizedType, displayId, focusGrantToken, inputHandleName,
+                            (flags & FLAG_NOT_FOCUSABLE) == 0);
             clientChannel = win.openInputChannel();
             mEmbeddedWindowController.add(clientChannel.getToken(), win);
             applicationHandle = win.getApplicationHandle();
@@ -8721,6 +8722,7 @@
             }
             name = win.toString();
             applicationHandle = win.getApplicationHandle();
+            win.setIsFocusable((flags & FLAG_NOT_FOCUSABLE) == 0);
         }
 
         updateInputChannel(channelToken, win.mOwnerUid, win.mOwnerPid, displayId, surface, name,
@@ -8998,24 +9000,23 @@
                 Slog.e(TAG, "Embedded window does not belong to the host");
                 return;
             }
-            SurfaceControl.Transaction t = mTransactionFactory.get();
             if (grantFocus) {
-                t.requestFocusTransfer(embeddedWindow.getInputChannelToken(), embeddedWindow.toString(),
-                        hostWindow.mInputChannel.getToken(),
-                        hostWindow.getName(),
-                        hostWindow.getDisplayId()).apply();
+                hostWindow.mInputWindowHandle.setFocusTransferTarget(
+                        embeddedWindow.getInputChannelToken());
                 EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
                         "Transfer focus request " + embeddedWindow,
                         "reason=grantEmbeddedWindowFocus(true)");
             } else {
-                t.requestFocusTransfer(hostWindow.mInputChannel.getToken(), hostWindow.getName(),
-                        embeddedWindow.getInputChannelToken(),
-                        embeddedWindow.toString(),
-                        hostWindow.getDisplayId()).apply();
+                hostWindow.mInputWindowHandle.setFocusTransferTarget(null);
                 EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
                         "Transfer focus request " + hostWindow,
                         "reason=grantEmbeddedWindowFocus(false)");
             }
+            DisplayContent dc = mRoot.getDisplayContent(hostWindow.getDisplayId());
+            if (dc != null) {
+                dc.getInputMonitor().updateInputWindowsLw(true);
+            }
+
             ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s grantFocus=%s",
                     embeddedWindow, grantFocus);
         }
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 437af4b..a153708 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -1301,6 +1301,9 @@
             case "stop":
                 mInternal.mTransitionTracer.stopTrace(pw);
                 break;
+            case "save-for-bugreport":
+                mInternal.mTransitionTracer.saveForBugreport(pw);
+                break;
             default:
                 getErrPrintWriter()
                         .println("Error: expected 'start' or 'stop', but got '" + arg + "'");
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 71acbb4..d64b5a1 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -136,7 +136,6 @@
     jmethodID getContextForDisplay;
     jmethodID notifyDropWindow;
     jmethodID getParentSurfaceForPointers;
-    jmethodID isPerDisplayTouchModeEnabled;
 } gServiceClassInfo;
 
 static struct {
@@ -369,10 +368,6 @@
     virtual PointerIconStyle getCustomPointerIconId();
     virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position);
 
-    /* --- If touch mode is enabled per display or global --- */
-
-    virtual bool isPerDisplayTouchModeEnabled();
-
 private:
     sp<InputManagerInterface> mInputManager;
 
@@ -1645,16 +1640,6 @@
             InputReaderConfiguration::CHANGE_STYLUS_BUTTON_REPORTING);
 }
 
-bool NativeInputManager::isPerDisplayTouchModeEnabled() {
-    JNIEnv* env = jniEnv();
-    jboolean enabled =
-            env->CallBooleanMethod(mServiceObj, gServiceClassInfo.isPerDisplayTouchModeEnabled);
-    if (checkAndClearExceptionFromCallback(env, "isPerDisplayTouchModeEnabled")) {
-        return false;
-    }
-    return static_cast<bool>(enabled);
-}
-
 FloatPoint NativeInputManager::getMouseCursorPosition() {
     std::scoped_lock _l(mLock);
     const auto pc = mLocked.pointerController.lock();
@@ -2846,9 +2831,6 @@
     GET_METHOD_ID(gServiceClassInfo.getParentSurfaceForPointers, clazz,
                   "getParentSurfaceForPointers", "(I)J");
 
-    GET_METHOD_ID(gServiceClassInfo.isPerDisplayTouchModeEnabled, clazz,
-                  "isPerDisplayTouchModeEnabled", "()Z");
-
     // InputDevice
 
     FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice");
diff --git a/services/core/jni/gnss/AGnssRil.cpp b/services/core/jni/gnss/AGnssRil.cpp
index c7a1af7..b21489a 100644
--- a/services/core/jni/gnss/AGnssRil.cpp
+++ b/services/core/jni/gnss/AGnssRil.cpp
@@ -89,6 +89,10 @@
 }
 
 jboolean AGnssRil::injectNiSuplMessageData(const jbyteArray& msgData, jint length, jint slotIndex) {
+    if (mIAGnssRil->getInterfaceVersion() <= 2) {
+        ALOGE("IAGnssRil does not support injectNiSuplMessageData().");
+        return JNI_FALSE;
+    }
     JNIEnv* env = getJniEnv();
     jbyte* bytes = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(msgData, 0));
     auto status = mIAGnssRil->injectNiSuplMessageData(std::vector<uint8_t>((const uint8_t*)bytes,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f01b0eb..7d26c04 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9193,34 +9193,53 @@
             Objects.requireNonNull(who, "ComponentName is null");
         }
 
-
         final int userHandle = caller.getUserId();
         int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
         synchronized (getLockObject()) {
-            ActiveAdmin ap;
-            if (isPermissionCheckFlagEnabled()) {
+            if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
                 // SUPPORT USES_POLICY_DISABLE_KEYGUARD_FEATURES
-                ap = enforcePermissionAndGetEnforcingAdmin(
+                EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
                         who, MANAGE_DEVICE_POLICY_KEYGUARD, caller.getPackageName(),
-                        affectedUserId).getActiveAdmin();
-            } else {
-                ap = getActiveAdminForCallerLocked(
-                        who, DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent);
-            }
-            if (isManagedProfile(userHandle)) {
-                if (parent) {
-                    if (isProfileOwnerOfOrganizationOwnedDevice(caller)) {
-                        which = which & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
-                    } else {
-                        which = which & NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
-                    }
+                        affectedUserId);
+                if (which == 0) {
+                    mDevicePolicyEngine.removeLocalPolicy(
+                            PolicyDefinition.KEYGUARD_DISABLED_FEATURES, admin, affectedUserId);
                 } else {
-                    which = which & PROFILE_KEYGUARD_FEATURES;
+                    // TODO(b/273723433): revisit silent masking of features
+                    if (isManagedProfile(userHandle)) {
+                        if (parent) {
+                            if (isProfileOwnerOfOrganizationOwnedDevice(caller)) {
+                                which = which & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+                            } else {
+                                which = which
+                                        & NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+                            }
+                        } else {
+                            which = which & PROFILE_KEYGUARD_FEATURES;
+                        }
+                    }
+                    mDevicePolicyEngine.setLocalPolicy(PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
+                            admin, new IntegerPolicyValue(which), affectedUserId);
                 }
-            }
-            if (ap.disabledKeyguardFeatures != which) {
-                ap.disabledKeyguardFeatures = which;
-                saveSettingsLocked(userHandle);
+                invalidateBinderCaches();
+            } else {
+                ActiveAdmin ap = getActiveAdminForCallerLocked(
+                        who, DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent);
+                if (isManagedProfile(userHandle)) {
+                    if (parent) {
+                        if (isProfileOwnerOfOrganizationOwnedDevice(caller)) {
+                            which = which & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+                        } else {
+                            which = which & NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+                        }
+                    } else {
+                        which = which & PROFILE_KEYGUARD_FEATURES;
+                    }
+                }
+                if (ap.disabledKeyguardFeatures != which) {
+                    ap.disabledKeyguardFeatures = which;
+                    saveSettingsLocked(userHandle);
+                }
             }
         }
         if (SecurityLog.isLoggingEnabled()) {
@@ -9252,15 +9271,51 @@
         Preconditions.checkCallAuthorization(
                 who == null || isCallingFromPackage(who.getPackageName(), caller.getUid())
                         || isSystemUid(caller));
+        int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
 
-        final long ident = mInjector.binderClearCallingIdentity();
-        try {
-            synchronized (getLockObject()) {
-                if (who != null) {
+        synchronized (getLockObject()) {
+            if (who != null) {
+                if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+                    EnforcingAdmin admin = getEnforcingAdminForCaller(
+                            who, who.getPackageName());
+                    Integer features = mDevicePolicyEngine.getLocalPolicySetByAdmin(
+                            PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
+                            admin,
+                            affectedUserId);
+                    return features == null ? 0 : features;
+                } else {
                     ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
                     return (admin != null) ? admin.disabledKeyguardFeatures : 0;
                 }
+            }
 
+            if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+                Integer features = mDevicePolicyEngine.getResolvedPolicy(
+                        PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
+                        affectedUserId);
+
+                return Binder.withCleanCallingIdentity(() -> {
+                    int combinedFeatures = features == null ? 0 : features;
+                    List<UserInfo> profiles = mUserManager.getProfiles(affectedUserId);
+                    for (UserInfo profile : profiles) {
+                        int profileId = profile.id;
+                        if (profileId == affectedUserId) {
+                            continue;
+                        }
+                        Integer profileFeatures = mDevicePolicyEngine.getResolvedPolicy(
+                                PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
+                                profileId);
+                        if (profileFeatures != null) {
+                            combinedFeatures |= (profileFeatures
+                                    & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER);
+                        }
+                    }
+                    return combinedFeatures;
+                });
+            }
+
+            final long ident = mInjector.binderClearCallingIdentity();
+            try {
                 final List<ActiveAdmin> admins;
                 if (!parent && isManagedProfile(userHandle)) {
                     // If we are being asked about a managed profile, just return keyguard features
@@ -9290,9 +9345,9 @@
                     }
                 }
                 return which;
+            } finally {
+                mInjector.binderRestoreCallingIdentity(ident);
             }
-        } finally {
-            mInjector.binderRestoreCallingIdentity(ident);
         }
     }
 
@@ -12519,9 +12574,8 @@
             case UserManager.RESTRICTION_NOT_SET:
                 return false;
             case UserManager.RESTRICTION_SOURCE_DEVICE_OWNER:
-                return !isDeviceOwner(admin, userId);
             case UserManager.RESTRICTION_SOURCE_PROFILE_OWNER:
-                return !isProfileOwner(admin, userId);
+                return !(isDeviceOwner(admin, userId) || isProfileOwner(admin, userId));
             default:
                 return true;
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index a08c2054..8812c3d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -246,6 +246,14 @@
             (Long value, Context context, Integer userId, PolicyKey policyKey) -> true,
             new LongPolicySerializer());
 
+    static PolicyDefinition<Integer> KEYGUARD_DISABLED_FEATURES = new PolicyDefinition<>(
+            new NoArgsPolicyKey(DevicePolicyIdentifiers.KEYGUARD_DISABLED_FEATURES_POLICY),
+            new FlagUnion(),
+            POLICY_FLAG_LOCAL_ONLY_POLICY,
+            // Nothing is enforced for keyguard features, we just need to store it
+            (Integer value, Context context, Integer userId, PolicyKey policyKey) -> true,
+            new IntegerPolicySerializer());
+
     private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
     private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c7f5223..dbff90a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -108,6 +108,8 @@
 import com.android.server.am.ActivityManagerService;
 import com.android.server.ambientcontext.AmbientContextManagerService;
 import com.android.server.appbinding.AppBindingService;
+import com.android.server.appop.AppOpMigrationHelper;
+import com.android.server.appop.AppOpMigrationHelperImpl;
 import com.android.server.art.ArtModuleServiceInitializer;
 import com.android.server.art.DexUseManagerLocal;
 import com.android.server.attention.AttentionManagerService;
@@ -1136,6 +1138,8 @@
         t.traceBegin("StartAccessCheckingService");
         LocalServices.addService(PermissionMigrationHelper.class,
                 new PermissionMigrationHelperImpl());
+        LocalServices.addService(AppOpMigrationHelper.class,
+                new AppOpMigrationHelperImpl());
         mSystemServiceManager.startService(AccessCheckingService.class);
         t.traceEnd();
 
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
new file mode 100644
index 0000000..71e4f2a
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.appop
+
+import com.android.server.LocalServices
+import com.android.server.appop.AppOpMigrationHelper
+import com.android.server.permission.access.AccessState
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.PackageVersionMigration
+
+class AppIdAppOpMigration {
+    fun migrateUserState(state: AccessState, userId: Int) {
+        val legacyAppOpsManager = LocalServices.getService(AppOpMigrationHelper::class.java)!!
+        val legacyAppIdAppOpModes = legacyAppOpsManager.getLegacyAppIdAppOpModes(userId)
+        val appIdAppOpModes = state.userStates[userId].appIdAppOpModes
+        val version = PackageVersionMigration.getVersion(userId)
+        legacyAppIdAppOpModes.forEach { (appId, legacyAppOpModes) ->
+            val appOpModes = appIdAppOpModes.getOrPut(appId) { IndexedMap() }
+            legacyAppOpModes.forEach { (appOpName, appOpMode) ->
+                appOpModes[appOpName] = appOpMode
+            }
+
+            state.systemState.appIds[appId].forEachIndexed { _, packageName ->
+                state.userStates[userId].packageVersions[packageName] = version
+            }
+        }
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
index 5a2522e..bd94955 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
@@ -17,14 +17,20 @@
 package com.android.server.permission.access.appop
 
 import android.app.AppOpsManager
+import com.android.server.permission.access.AccessState
 import com.android.server.permission.access.AccessUri
 import com.android.server.permission.access.AppOpUri
 import com.android.server.permission.access.GetStateScope
 import com.android.server.permission.access.MutateStateScope
 import com.android.server.permission.access.UidUri
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.pm.pkg.PackageState
 
 class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) {
+    private val migration = AppIdAppOpMigration()
+
+    private val upgrade = AppIdAppOpUpgrade(this)
+
     @Volatile
     private var onAppOpModeChangedListeners = IndexedListSet<OnAppOpModeChangedListener>()
     private val onAppOpModeChangedListenersLock = Any()
@@ -117,6 +123,18 @@
         }
     }
 
+    override fun migrateUserState(state: AccessState, userId: Int) {
+        with(migration) { migrateUserState(state, userId) }
+    }
+
+    override fun MutateStateScope.upgradePackageState(
+        packageState: PackageState,
+        userId: Int,
+        version: Int,
+    ) {
+        with(upgrade) { upgradePackageState(packageState, userId, version) }
+    }
+
     /**
      * Listener for app op mode changes.
      */
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt
new file mode 100644
index 0000000..4bd36f4
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.appop
+
+import android.app.AppOpsManager
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.pm.pkg.PackageState
+
+class AppIdAppOpUpgrade(private val policy: AppIdAppOpPolicy) {
+    fun MutateStateScope.upgradePackageState(
+        packageState: PackageState,
+        userId: Int,
+        version: Int,
+    ) {
+        if (version == 0) {
+            return
+        }
+
+        if (version <= 2) {
+            with(policy) {
+                val appOpMode = getAppOpMode(
+                    packageState.appId, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND
+                )
+                setAppOpMode(
+                    packageState.appId, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, appOpMode
+                )
+            }
+        }
+        if (version <= 13) {
+            val permissionName = AppOpsManager.opToPermission(AppOpsManager.OP_SCHEDULE_EXACT_ALARM)
+            if (permissionName in packageState.androidPackage!!.requestedPermissions) {
+                with(policy) {
+                    val appOpMode = getAppOpMode(
+                        packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM
+                    )
+                    val defaultAppOpMode =
+                        AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM)
+                    if (appOpMode == defaultAppOpMode) {
+                        setAppOpMode(
+                            packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM,
+                            AppOpsManager.MODE_ALLOWED
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
new file mode 100644
index 0000000..c9651b2
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.appop
+
+import com.android.server.LocalServices
+import com.android.server.appop.AppOpMigrationHelper
+import com.android.server.permission.access.AccessState
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.PackageVersionMigration
+
+class PackageAppOpMigration {
+    fun migrateUserState(state: AccessState, userId: Int) {
+        val legacyAppOpsManager = LocalServices.getService(AppOpMigrationHelper::class.java)!!
+        val legacyPackageAppOpModes = legacyAppOpsManager.getLegacyPackageAppOpModes(userId)
+        val packageAppOpModes = state.userStates[userId].packageAppOpModes
+        val version = PackageVersionMigration.getVersion(userId)
+        legacyPackageAppOpModes.forEach { (packageName, legacyAppOpModes) ->
+            val appOpModes = packageAppOpModes.getOrPut(packageName) { IndexedMap() }
+            legacyAppOpModes.forEach { (appOpName, appOpMode) ->
+                appOpModes[appOpName] = appOpMode
+            }
+            state.userStates[userId].packageVersions[packageName] = version
+        }
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index 7d3578d..b4c4796 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -17,14 +17,20 @@
 package com.android.server.permission.access.appop
 
 import android.app.AppOpsManager
+import com.android.server.permission.access.AccessState
 import com.android.server.permission.access.AccessUri
 import com.android.server.permission.access.AppOpUri
 import com.android.server.permission.access.GetStateScope
 import com.android.server.permission.access.MutateStateScope
 import com.android.server.permission.access.PackageUri
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.pm.pkg.PackageState
 
 class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
+    private val migration = PackageAppOpMigration()
+
+    private val upgrade = PackageAppOpUpgrade(this)
+
     @Volatile
     private var onAppOpModeChangedListeners = IndexedListSet<OnAppOpModeChangedListener>()
     private val onAppOpModeChangedListenersLock = Any()
@@ -117,6 +123,18 @@
         }
     }
 
+    override fun migrateUserState(state: AccessState, userId: Int) {
+        with(migration) { migrateUserState(state, userId) }
+    }
+
+    override fun MutateStateScope.upgradePackageState(
+        packageState: PackageState,
+        userId: Int,
+        version: Int,
+    ) {
+        with(upgrade) { upgradePackageState(packageState, userId, version) }
+    }
+
     /**
      * Listener for app op mode changes.
      */
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt
new file mode 100644
index 0000000..fdf2b64
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.appop
+
+import android.app.AppOpsManager
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.pm.pkg.PackageState
+
+class PackageAppOpUpgrade(private val policy: PackageAppOpPolicy) {
+    fun MutateStateScope.upgradePackageState(
+        packageState: PackageState,
+        userId: Int,
+        version: Int,
+    ) {
+        if (version == 0) {
+            return
+        }
+
+        if (version <= 2) {
+            with(policy) {
+                val appOpMode = getAppOpMode(
+                    packageState.packageName, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND
+                )
+                setAppOpMode(
+                    packageState.packageName, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND,
+                    appOpMode
+                )
+            }
+        }
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt
index ee7a4f4..b4b185f 100644
--- a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt
+++ b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt
@@ -16,7 +16,9 @@
 
 package com.android.server.permission.access.util
 
+import android.util.Log
 import com.android.server.LocalServices
+import com.android.server.appop.AppOpMigrationHelper
 import com.android.server.permission.access.AccessPolicy
 import com.android.server.pm.permission.PermissionMigrationHelper
 
@@ -31,8 +33,8 @@
             LocalServices.getService(PermissionMigrationHelper::class.java)
         val permissionVersion = permissionMigrationHelper.getLegacyPermissionsVersion(userId)
 
-        // TODO appops version would be fixed in appops cl
-        val appOpVersion = 1
+        val appOpMigrationHelper = LocalServices.getService(AppOpMigrationHelper::class.java)
+        val appOpVersion = appOpMigrationHelper.legacyAppOpVersion
 
         return when {
             // Both files don't exist.
@@ -57,10 +59,13 @@
             permissionVersion == 9 && appOpVersion == 1 -> 12
             permissionVersion == 10 && appOpVersion == 1 -> 13
             permissionVersion == 10 && appOpVersion == 3 -> AccessPolicy.VERSION_LATEST
-            else -> throw IllegalArgumentException(
-                "Version combination not recognized, permission" +
-                    "version: $permissionVersion, app-op version: $appOpVersion"
-            )
+            else -> {
+                Log.w(
+                    "PackageVersionMigration", "Version combination not recognized, permission" +
+                        "version: $permissionVersion, app-op version: $appOpVersion"
+                )
+                AccessPolicy.VERSION_LATEST
+            }
         }
     }
 }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java
deleted file mode 100644
index 111cabd..0000000
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.inputmethod;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class SwitchKeyboardLayoutTest extends InputMethodManagerServiceTestBase {
-    @Test
-    public void testSwitchToNextKeyboardLayout() {
-        ExtendedMockito.spyOn(mInputMethodManagerService.mSwitchingController);
-        InputMethodManagerInternal.get().switchKeyboardLayout(1);
-        verify(mInputMethodManagerService.mSwitchingController)
-                .getNextInputMethodLocked(eq(true) /* onlyCurrentIme */, any(), any());
-    }
-}
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
index d813962..a849b66 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
@@ -22,6 +22,7 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.rules.TemporaryFolder
@@ -41,6 +42,7 @@
 @RunWith(DeviceJUnit4Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(
         DeviceJUnit4ClassRunnerWithParameters.RunnerFactory::class)
+@Ignore("b/275403538")
 class SdCardEjectionTests : BaseHostJUnit4Test() {
 
     companion object {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 36d191b..41a5ddb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -20,6 +20,7 @@
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
+import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
 import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_ALARM;
 import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_FOREGROUND;
 import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_INTERACTIVE;
@@ -1141,7 +1142,7 @@
             mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
             mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
         }
-        mImpl.waitForIdle(null);
+        mImpl.waitForIdle(LOG_WRITER_INFO);
 
         // Verify that there is only one delivery event reported since one of the broadcasts
         // should have been skipped.
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index bca39ae..3b964bc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -18,6 +18,7 @@
 
 import static android.os.UserHandle.USER_SYSTEM;
 
+import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
 import static com.android.server.am.BroadcastProcessQueue.reasonToString;
 import static com.android.server.am.BroadcastRecord.deliveryStateToString;
 import static com.android.server.am.BroadcastRecord.isReceiverEquals;
@@ -222,7 +223,7 @@
         realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
         realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
         realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
-        realAms.mOomAdjuster.mCachedAppOptimizer = spy(realAms.mOomAdjuster.mCachedAppOptimizer);
+        realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
         realAms.mPackageManagerInt = mPackageManagerInt;
         realAms.mUsageStatsService = mUsageStatsManagerInt;
         realAms.mProcessesReady = true;
@@ -659,7 +660,7 @@
     }
 
     private void waitForIdle() throws Exception {
-        mQueue.waitForIdle(null);
+        mQueue.waitForIdle(LOG_WRITER_INFO);
     }
 
     private void verifyScheduleReceiver(ProcessRecord app, Intent intent) throws Exception {
@@ -773,9 +774,6 @@
         mQueue.dumpToDropBoxLocked(TAG);
 
         BroadcastQueue.logv(TAG);
-        BroadcastQueue.logv(TAG, null);
-        BroadcastQueue.logv(TAG, new PrintWriter(new ByteArrayOutputStream()));
-
         BroadcastQueue.logw(TAG);
 
         assertNotNull(mQueue.toString());
@@ -951,7 +949,7 @@
                 // cold-started apps to be thawed, but the modern stack does
             } else {
                 // Confirm that app was thawed
-                verify(mAms.mOomAdjuster.mCachedAppOptimizer, atLeastOnce()).unfreezeTemporarily(
+                verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
                         eq(receiverApp), eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
 
                 // Confirm that we added package to process
@@ -1394,7 +1392,7 @@
                 anyInt(), any());
 
         // Finally, verify that we thawed the final receiver
-        verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(callerApp),
+        verify(mAms.mOomAdjuster).unfreezeTemporarily(eq(callerApp),
                 eq(OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER));
     }
 
@@ -1891,10 +1889,10 @@
             assertFalse(mQueue.isBeyondBarrierLocked(afterSecond));
         }
 
-        mQueue.waitForBarrier(null);
+        mQueue.waitForBarrier(LOG_WRITER_INFO);
         assertTrue(mQueue.isBeyondBarrierLocked(afterFirst));
 
-        mQueue.waitForIdle(null);
+        mQueue.waitForIdle(LOG_WRITER_INFO);
         assertTrue(mQueue.isIdleLocked());
         assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
         assertTrue(mQueue.isBeyondBarrierLocked(afterFirst));
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index 1fbb8dd..eb6efd2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -166,10 +166,6 @@
                     CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE);
             assertThat(mCachedAppOptimizerUnderTest.mFreezerStatsdSampleRate).isEqualTo(
                     CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE);
-            assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                    CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
-            assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                    CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
             assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
                     CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
             assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
@@ -261,10 +257,6 @@
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
         assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleMinOomAdj).isEqualTo(
@@ -275,10 +267,6 @@
                 CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
         assertThat(mCachedAppOptimizerUnderTest.mFreezerStatsdSampleRate).isEqualTo(
                 CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
         assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
         assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
@@ -425,10 +413,6 @@
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleMinOomAdj).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ + 1);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleMaxOomAdj).isEqualTo(
@@ -454,10 +438,6 @@
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
 
         // Repeat for each of the throttle keys.
         mCountDown = new CountDownLatch(1);
@@ -472,10 +452,6 @@
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
 
         mCountDown = new CountDownLatch(1);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -489,10 +465,6 @@
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
 
         mCountDown = new CountDownLatch(1);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -506,10 +478,6 @@
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
 
         mCountDown = new CountDownLatch(1);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -523,10 +491,6 @@
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
 
         mCountDown = new CountDownLatch(1);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -540,10 +504,6 @@
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
     }
 
     @Test
@@ -953,15 +913,7 @@
         mProcessDependencies.setRssAfterCompaction(rssAfter);
 
         // When moving within cached state
-        mCachedAppOptimizerUnderTest.onOomAdjustChanged(
-                ProcessList.CACHED_APP_MIN_ADJ, ProcessList.CACHED_APP_MIN_ADJ + 1, processRecord);
-        waitForHandler();
-        // THEN process IS NOT compacted.
-        assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull();
-
-        // When moving into cached state
-        mCachedAppOptimizerUnderTest.onOomAdjustChanged(ProcessList.CACHED_APP_MIN_ADJ - 1,
-                ProcessList.CACHED_APP_MIN_ADJ + 1, processRecord);
+        mCachedAppOptimizerUnderTest.onProcessFrozen(processRecord);
         waitForHandler();
         // THEN process IS compacted.
         assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
index b5e0e07..dd44a79 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
@@ -50,6 +50,7 @@
 
 public class ProxyAccessibilityServiceConnectionTest {
     private static final int DISPLAY_ID = 1000;
+    private static final int DEVICE_ID = 2000;
     private static final int CONNECTION_ID = 1000;
     private static final ComponentName COMPONENT_NAME = new ComponentName(
             "com.android.server.accessibility", ".ProxyAccessibilityServiceConnectionTest");
@@ -90,7 +91,7 @@
                 mAccessibilityServiceInfo, CONNECTION_ID , new Handler(
                         getInstrumentation().getContext().getMainLooper()),
                 mMockLock, mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace,
-                mMockWindowManagerInternal, mMockA11yWindowManager, DISPLAY_ID);
+                mMockWindowManagerInternal, mMockA11yWindowManager, DISPLAY_ID, DEVICE_ID);
     }
 
     @Test
@@ -101,7 +102,7 @@
 
         mProxyConnection.setInstalledAndEnabledServices(infos);
 
-        verify(mMockSystemSupport).onClientChangeLocked(true);
+        verify(mMockSystemSupport).onProxyChanged(DEVICE_ID);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java
new file mode 100644
index 0000000..6eedeea
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class HardwareKeyboardShortcutControllerTest {
+
+    @Test
+    public void testForwardRotation() {
+        final List<String> handles = Arrays.asList("0", "1", "2", "3");
+        assertEquals("2", HardwareKeyboardShortcutController.getNeighborItem(handles, "1", true));
+        assertEquals("3", HardwareKeyboardShortcutController.getNeighborItem(handles, "2", true));
+        assertEquals("0", HardwareKeyboardShortcutController.getNeighborItem(handles, "3", true));
+    }
+
+    @Test
+    public void testBackwardRotation() {
+        final List<String> handles = Arrays.asList("0", "1", "2", "3");
+        assertEquals("0", HardwareKeyboardShortcutController.getNeighborItem(handles, "1", false));
+        assertEquals("3", HardwareKeyboardShortcutController.getNeighborItem(handles, "0", false));
+        assertEquals("2", HardwareKeyboardShortcutController.getNeighborItem(handles, "3", false));
+    }
+
+    @Test
+    public void testNotMatching() {
+        final List<String> handles = Arrays.asList("0", "1", "2", "3");
+        assertNull(HardwareKeyboardShortcutController.getNeighborItem(handles, "X", true));
+        assertNull(HardwareKeyboardShortcutController.getNeighborItem(handles, "X", false));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
index 397d7b5..7cf5bc8 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
@@ -26,8 +26,9 @@
 
 import android.content.Context;
 import android.os.Handler;
+import android.util.IntArray;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -57,10 +58,24 @@
     private static final int TEST_UID_4 = 56926423;
     private static final int TEST_UID_5 = 76421423;
 
+    private static final int TEST_PROC_STATE_1 = 72331;
+    private static final int TEST_PROC_STATE_2 = 792351;
+    private static final int TEST_PROC_STATE_3 = 138831;
+    private static final int TEST_PROC_STATE_4 = 23231;
+    private static final int TEST_PROC_STATE_5 = 42;
+
     private static final Context sContext = InstrumentationRegistry.getTargetContext();
     private final Handler mHandler = Mockito.mock(Handler.class);
     private final ThreadLocalRandom mRandom = ThreadLocalRandom.current();
 
+    private void populateDefaultProcStates(CpuWakeupStats obj) {
+        obj.mUidProcStates.put(TEST_UID_1, TEST_PROC_STATE_1);
+        obj.mUidProcStates.put(TEST_UID_2, TEST_PROC_STATE_2);
+        obj.mUidProcStates.put(TEST_UID_3, TEST_PROC_STATE_3);
+        obj.mUidProcStates.put(TEST_UID_4, TEST_PROC_STATE_4);
+        obj.mUidProcStates.put(TEST_UID_5, TEST_PROC_STATE_5);
+    }
+
     @Test
     public void removesOldWakeups() {
         // The xml resource doesn't matter for this test.
@@ -96,6 +111,8 @@
         final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
         final long wakeupTime = 12423121;
 
+        populateDefaultProcStates(obj);
+
         obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_ALARM_IRQ);
 
         // Outside the window, so should be ignored.
@@ -106,15 +123,20 @@
         // Should be attributed
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3, TEST_UID_5);
 
-        final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+        final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
         assertThat(attribution).isNotNull();
         assertThat(attribution.size()).isEqualTo(1);
         assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isTrue();
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_1)).isEqualTo(false);
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_2)).isEqualTo(false);
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(true);
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_4)).isEqualTo(false);
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_5)).isEqualTo(true);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).indexOfKey(TEST_UID_1)).isLessThan(
+                0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).indexOfKey(TEST_UID_2)).isLessThan(
+                0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(
+                TEST_PROC_STATE_3);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).indexOfKey(TEST_UID_4)).isLessThan(
+                0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_5)).isEqualTo(
+                TEST_PROC_STATE_5);
     }
 
     @Test
@@ -122,6 +144,8 @@
         final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
         final long wakeupTime = 12423121;
 
+        populateDefaultProcStates(obj);
+
         obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_WIFI_IRQ);
 
         // Outside the window, so should be ignored.
@@ -132,15 +156,17 @@
         // Should be attributed
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime + 3, TEST_UID_4, TEST_UID_5);
 
-        final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+        final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
         assertThat(attribution).isNotNull();
         assertThat(attribution.size()).isEqualTo(1);
         assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_WIFI)).isTrue();
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_1)).isEqualTo(false);
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_2)).isEqualTo(false);
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_3)).isEqualTo(false);
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_4)).isEqualTo(true);
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_5)).isEqualTo(true);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).indexOfKey(TEST_UID_1)).isLessThan(0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).indexOfKey(TEST_UID_2)).isLessThan(0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).indexOfKey(TEST_UID_3)).isLessThan(0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_4)).isEqualTo(
+                TEST_PROC_STATE_4);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_5)).isEqualTo(
+                TEST_PROC_STATE_5);
     }
 
     @Test
@@ -148,6 +174,8 @@
         final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
         final long wakeupTime = 92123210;
 
+        populateDefaultProcStates(obj);
+
         obj.noteWakeupTimeAndReason(wakeupTime, 4,
                 KERNEL_REASON_WIFI_IRQ + ":" + KERNEL_REASON_ALARM_IRQ);
 
@@ -173,23 +201,31 @@
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime - 1, TEST_UID_2,
                 TEST_UID_5);
 
-        final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+        final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
         assertThat(attribution).isNotNull();
         assertThat(attribution.size()).isEqualTo(2);
 
         assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isTrue();
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_1)).isEqualTo(false);
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_2)).isEqualTo(false);
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(true);
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_4)).isEqualTo(true);
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_5)).isEqualTo(true);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).indexOfKey(TEST_UID_1)).isLessThan(
+                0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).indexOfKey(TEST_UID_2)).isLessThan(
+                0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(
+                TEST_PROC_STATE_3);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_4)).isEqualTo(
+                TEST_PROC_STATE_4);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_5)).isEqualTo(
+                TEST_PROC_STATE_5);
 
         assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_WIFI)).isTrue();
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_1)).isEqualTo(true);
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_2)).isEqualTo(true);
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_3)).isEqualTo(false);
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_4)).isEqualTo(false);
-        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_5)).isEqualTo(true);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_1)).isEqualTo(
+                TEST_PROC_STATE_1);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_2)).isEqualTo(
+                TEST_PROC_STATE_2);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).indexOfKey(TEST_UID_3)).isLessThan(0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).indexOfKey(TEST_UID_4)).isLessThan(0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_5)).isEqualTo(
+                TEST_PROC_STATE_5);
     }
 
     @Test
@@ -206,12 +242,12 @@
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime - 3, TEST_UID_4,
                 TEST_UID_5);
 
-        final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+        final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
         assertThat(attribution).isNotNull();
         assertThat(attribution.size()).isEqualTo(1);
         assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isTrue();
-        final SparseBooleanArray uids = attribution.get(CPU_WAKEUP_SUBSYSTEM_UNKNOWN);
-        assertThat(uids == null || uids.size() == 0).isTrue();
+        final SparseIntArray uidProcStates = attribution.get(CPU_WAKEUP_SUBSYSTEM_UNKNOWN);
+        assertThat(uidProcStates == null || uidProcStates.size() == 0).isTrue();
     }
 
     @Test
@@ -259,4 +295,39 @@
         // Any nearby activity should not end up in the attribution map.
         assertThat(obj.mWakeupAttribution.size()).isEqualTo(0);
     }
+
+    @Test
+    public void uidProcStateBookkeeping() {
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
+
+        assertThat(obj.mUidProcStates.size()).isEqualTo(0);
+
+        final IntArray uids = new IntArray(87);
+        for (int i = 0; i < 87; i++) {
+            final int uid = mRandom.nextInt(1 << 20);
+            if (uids.indexOf(uid) < 0) {
+                uids.add(uid);
+            }
+        }
+
+        for (int i = 0; i < uids.size(); i++) {
+            final int uid = uids.get(i);
+            for (int j = 0; j < 43; j++) {
+                final int procState = mRandom.nextInt(1 << 15);
+                obj.noteUidProcessState(uid, procState);
+                assertThat(obj.mUidProcStates.get(uid)).isEqualTo(procState);
+            }
+            assertThat(obj.mUidProcStates.size()).isEqualTo(i + 1);
+        }
+
+        for (int i = 0; i < uids.size(); i++) {
+            obj.onUidRemoved(uids.get(i));
+            assertThat(obj.mUidProcStates.indexOfKey(uids.get(i))).isLessThan(0);
+        }
+
+        assertThat(obj.mUidProcStates.size()).isEqualTo(0);
+
+        obj.onUidRemoved(213);
+        assertThat(obj.mUidProcStates.size()).isEqualTo(0);
+    }
 }
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 42d1ace..3888b9b 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1638,12 +1638,6 @@
                 any(), anyString(), anyInt(), anyString(), anyInt())).thenReturn(SHOW_IMMEDIATELY);
         mContext.getTestablePermissions().setPermission(
                 android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
-        DeviceConfig.setProperty(
-                DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED,
-                "true",
-                false);
-        Thread.sleep(300);
 
         final String tag = "testEnqueueNotificationWithTag_FgsAddsFlags_dismissalAllowed";
 
@@ -1665,38 +1659,6 @@
     }
 
     @Test
-    public void testEnqueueNotificationWithTag_FGSaddsFlags_dismissalNotAllowed() throws Exception {
-        when(mAmi.applyForegroundServiceNotification(
-                any(), anyString(), anyInt(), anyString(), anyInt())).thenReturn(SHOW_IMMEDIATELY);
-        mContext.getTestablePermissions().setPermission(
-                android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
-        DeviceConfig.setProperty(
-                DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED,
-                "false",
-                false);
-        Thread.sleep(300);
-
-        final String tag = "testEnqueueNotificationWithTag_FGSaddsNoClear";
-
-        Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
-                .setContentTitle("foo")
-                .setSmallIcon(android.R.drawable.sym_def_app_icon)
-                .setFlag(FLAG_FOREGROUND_SERVICE, true)
-                .build();
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
-                n, UserHandle.getUserHandleForUid(mUid), null, 0);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, tag,
-                sbn.getId(), sbn.getNotification(), sbn.getUserId());
-        waitForIdle();
-
-        StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(PKG);
-        assertThat(notifs[0].getNotification().flags).isEqualTo(
-                FLAG_FOREGROUND_SERVICE | FLAG_CAN_COLORIZE | FLAG_NO_CLEAR | FLAG_ONGOING_EVENT);
-    }
-
-    @Test
     public void testEnqueueNotificationWithTag_nullAction_fixed() throws Exception {
         Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
                 .setContentTitle("foo")
@@ -10462,8 +10424,11 @@
         verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
     }
 
-    private void verifyStickyHun(Flag flag, int permissionState, boolean isSticky)
-            throws Exception {
+    private void verifyStickyHun(Flag flag, int permissionState, boolean appRequested,
+            boolean isSticky) throws Exception {
+
+        when(mPermissionHelper.hasRequestedPermission(Manifest.permission.USE_FULL_SCREEN_INTENT,
+                PKG, mUserId)).thenReturn(appRequested);
 
         mTestFlagResolver.setFlagOverride(flag, true);
 
@@ -10491,7 +10456,7 @@
             throws Exception {
 
         verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
-                /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED,
+                /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true,
                 /* isSticky= */ true);
     }
 
@@ -10500,16 +10465,25 @@
             throws Exception {
 
         verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
-                /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED,
+                /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true,
                 /* isSticky= */ true);
     }
 
     @Test
+    public void testFixNotification_fsiPermissionSoftDenied_appNotRequest_noShowStickyHun()
+            throws Exception {
+        verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
+                /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, false,
+                /* isSticky= */ false);
+    }
+
+
+    @Test
     public void testFixNotification_flagEnableStickyHun_fsiPermissionGranted_showFsi()
             throws Exception {
 
         verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
-                /* permissionState= */ PermissionManager.PERMISSION_GRANTED,
+                /* permissionState= */ PermissionManager.PERMISSION_GRANTED, true,
                 /* isSticky= */ false);
     }
 
@@ -10518,7 +10492,7 @@
             throws Exception {
 
         verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
-                /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED,
+                /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true,
                 /* isSticky= */ true);
     }
 
@@ -10527,7 +10501,7 @@
             throws Exception {
 
         verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
-                /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED,
+                /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true,
                 /* isSticky= */ true);
     }
 
@@ -10536,7 +10510,7 @@
             throws Exception {
 
         verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
-                /* permissionState= */ PermissionManager.PERMISSION_GRANTED,
+                /* permissionState= */ PermissionManager.PERMISSION_GRANTED, true,
                 /* isSticky= */ true);
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index f2b1dc9..397e3c1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -138,6 +138,68 @@
     }
 
     @Test
+    public void testHasRequestedPermission_otherPermission() throws Exception {
+        final String permission = "correct";
+
+        String packageName = "testHasRequestedPermission_otherPermission";
+
+        PackageInfo info = new PackageInfo();
+        info.packageName = packageName;
+        info.requestedPermissions = new String[]{"something else"};
+
+        when(mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS, 0)).thenReturn(info);
+
+        assertThat(mPermissionHelper.hasRequestedPermission(permission, packageName, 0)).isFalse();
+
+    }
+
+    @Test
+    public void testHasRequestedPermission_noPermissions() throws Exception {
+        final String permission = "correct";
+
+        String packageName = "testHasRequestedPermission_noPermissions";
+
+        PackageInfo info = new PackageInfo();
+        info.packageName = packageName;
+
+        when(mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS, 0)).thenReturn(info);
+
+        assertThat(mPermissionHelper.hasRequestedPermission(permission, packageName, 0)).isFalse();
+    }
+
+    @Test
+    public void testHasRequestedPermission_singlePermissions() throws Exception {
+        final String permission = "correct";
+
+        String packageName = "testHasRequestedPermission_twoPermissions";
+
+        PackageInfo info = new PackageInfo();
+        info.packageName = packageName;
+        info.requestedPermissions =
+                new String[]{permission};
+
+        when(mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS, 0)).thenReturn(info);
+
+        assertThat(mPermissionHelper.hasRequestedPermission(permission, packageName, 0)).isTrue();
+    }
+
+    @Test
+    public void testHasRequestedPermission_twoPermissions() throws Exception {
+        final String permission = "correct";
+
+        String packageName = "testHasRequestedPermission_twoPermissions";
+
+        PackageInfo info = new PackageInfo();
+        info.packageName = packageName;
+        info.requestedPermissions =
+                new String[]{"something else", permission};
+
+        when(mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS, 0)).thenReturn(info);
+
+        assertThat(mPermissionHelper.hasRequestedPermission(permission, packageName, 0)).isTrue();
+    }
+
+    @Test
     public void testGetAppsGrantedPermission_noApps() throws Exception {
         int userId = 1;
         ParceledListSlice<PackageInfo> infos = ParceledListSlice.emptyList();
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
new file mode 100644
index 0000000..ff2ce15
--- /dev/null
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger;
+
+import static android.hardware.soundtrigger.ConversionUtil.aidl2apiAudioFormatWithDefault;
+import static android.hardware.soundtrigger.ConversionUtil.aidl2apiPhrase;
+import static android.hardware.soundtrigger.ConversionUtil.aidl2apiRecognitionConfig;
+import static android.hardware.soundtrigger.ConversionUtil.api2aidlPhrase;
+import static android.hardware.soundtrigger.ConversionUtil.api2aidlRecognitionConfig;
+import static android.hardware.soundtrigger.ConversionUtil.byteArrayToSharedMemory;
+import static android.hardware.soundtrigger.ConversionUtil.sharedMemoryToByteArray;
+import static android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel;
+import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_GENERIC;
+import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION;
+import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
+import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.hardware.soundtrigger.ConversionUtil;
+import android.hardware.soundtrigger.SoundTrigger;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@RunWith(AndroidJUnit4.class)
+public class ConversionUtilTest {
+    private static final String TAG = "ConversionUtilTest";
+
+    @Test
+    public void testDefaultAudioFormatConstruction() {
+        // This method should generate a real format when passed null
+        final var format = aidl2apiAudioFormatWithDefault(
+                null /** exercise default **/,
+                true /** isInput **/
+                );
+        assertNotNull(format);
+    }
+
+    @Test
+    public void testRecognitionConfigRoundTrip() {
+        final int flags = SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION
+                | SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION;
+        final var data = new byte[] {0x11, 0x22};
+        final var keyphrases = new SoundTrigger.KeyphraseRecognitionExtra[2];
+        keyphrases[0] = new SoundTrigger.KeyphraseRecognitionExtra(99,
+                RECOGNITION_MODE_VOICE_TRIGGER | RECOGNITION_MODE_USER_IDENTIFICATION, 13,
+                    new ConfidenceLevel[] {new ConfidenceLevel(9999, 50),
+                                           new ConfidenceLevel(5000, 80)});
+        keyphrases[1] = new SoundTrigger.KeyphraseRecognitionExtra(101,
+                RECOGNITION_MODE_GENERIC, 8, new ConfidenceLevel[] {
+                    new ConfidenceLevel(7777, 30),
+                    new ConfidenceLevel(2222, 60)});
+
+        var apiconfig = new SoundTrigger.RecognitionConfig(true, false /** must be false **/,
+                keyphrases, data, flags);
+        assertEquals(apiconfig, aidl2apiRecognitionConfig(api2aidlRecognitionConfig(apiconfig)));
+    }
+
+    @Test
+    public void testByteArraySharedMemRoundTrip() {
+        final var data = new byte[] { 0x11, 0x22, 0x33, 0x44,
+                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef };
+        assertArrayEquals(data, sharedMemoryToByteArray(byteArrayToSharedMemory(data, "name"),
+                    10000000));
+
+    }
+
+    @Test
+    public void testPhraseRoundTrip() {
+        final var users = new int[] {10001, 10002};
+        final var apiphrase = new SoundTrigger.Keyphrase(17 /** id **/,
+                RECOGNITION_MODE_VOICE_TRIGGER | RECOGNITION_MODE_USER_AUTHENTICATION,
+                Locale.forLanguageTag("no_NO"),
+                "Hello Android", /** keyphrase **/
+                users);
+        assertEquals(apiphrase, aidl2apiPhrase(api2aidlPhrase(apiphrase)));
+    }
+}
diff --git a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerTest.java
similarity index 89%
rename from tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java
rename to services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerTest.java
index f49d9c9..e6a1be8 100644
--- a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.hardware.soundtrigger;
+package com.android.server.soundtrigger;
 
 import android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel;
 import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
@@ -22,6 +22,7 @@
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
+import android.hardware.soundtrigger.SoundTrigger;
 import android.media.AudioFormat;
 import android.os.Parcel;
 import android.test.InstrumentationTestCase;
@@ -50,10 +51,7 @@
         Keyphrase unparceled = Keyphrase.CREATOR.createFromParcel(parcel);
 
         // Verify that they are the same
-        assertEquals(keyphrase.getId(), unparceled.getId());
-        assertNull(unparceled.getUsers());
-        assertEquals(keyphrase.getLocale(), unparceled.getLocale());
-        assertEquals(keyphrase.getText(), unparceled.getText());
+        assertEquals(keyphrase, unparceled);
     }
 
     @SmallTest
@@ -115,10 +113,7 @@
         KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel);
 
         // Verify that they are the same
-        assertEquals(ksm.getUuid(), unparceled.getUuid());
-        assertNull(unparceled.getData());
-        assertEquals(ksm.getType(), unparceled.getType());
-        assertTrue(Arrays.equals(keyphrases, unparceled.getKeyphrases()));
+        assertEquals(ksm, unparceled);
     }
 
     @SmallTest
@@ -162,10 +157,7 @@
         KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel);
 
         // Verify that they are the same
-        assertEquals(ksm.getUuid(), unparceled.getUuid());
-        assertEquals(ksm.getType(), unparceled.getType());
-        assertNull(unparceled.getKeyphrases());
-        assertTrue(Arrays.equals(ksm.getData(), unparceled.getData()));
+        assertEquals(ksm, unparceled);
     }
 
     @SmallTest
@@ -226,7 +218,11 @@
                 3 /* captureDelayMs */,
                 4 /* capturePreambleMs */,
                 false /* triggerInData */,
-                null /* captureFormat */,
+                new AudioFormat.Builder()
+                        .setSampleRate(16000)
+                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                        .build(),
                 null /* data */,
                 12345678 /* halEventReceivedMillis */);
 
@@ -251,7 +247,11 @@
                 3 /* captureDelayMs */,
                 4 /* capturePreambleMs */,
                 false /* triggerInData */,
-                null /* captureFormat */,
+                new AudioFormat.Builder()
+                        .setSampleRate(16000)
+                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                        .build(),
                 new byte[1] /* data */,
                 12345678 /* halEventReceivedMillis */);
 
@@ -278,7 +278,11 @@
                 3 /* captureDelayMs */,
                 4 /* capturePreambleMs */,
                 false /* triggerInData */,
-                null /* captureFormat */,
+                new AudioFormat.Builder()
+                        .setSampleRate(16000)
+                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                        .build(),
                 data,
                 12345678 /* halEventReceivedMillis */);
 
@@ -335,7 +339,11 @@
                 3 /* captureDelayMs */,
                 4 /* capturePreambleMs */,
                 false /* triggerInData */,
-                null /* captureFormat */,
+                new AudioFormat.Builder()
+                        .setSampleRate(16000)
+                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                        .build(),
                 null /* data */,
                 null /* keyphraseExtras */,
                 12345678 /* halEventReceivedMillis */);
@@ -364,7 +372,11 @@
                 3 /* captureDelayMs */,
                 4 /* capturePreambleMs */,
                 false /* triggerInData */,
-                null /* captureFormat */,
+                new AudioFormat.Builder()
+                        .setSampleRate(16000)
+                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                        .build(),
                 new byte[1] /* data */,
                 kpExtra,
                 12345678 /* halEventReceivedMillis */);
@@ -409,7 +421,11 @@
                 3 /* captureDelayMs */,
                 4 /* capturePreambleMs */,
                 false /* triggerInData */,
-                null /* captureFormat */,
+                new AudioFormat.Builder()
+                        .setSampleRate(16000)
+                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                        .build(),
                 data,
                 kpExtra,
                 12345678 /* halEventReceivedMillis */);
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
index 5661b12..7b7a0a3 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
@@ -16,36 +16,18 @@
 
 package com.android.server.soundtrigger_middleware;
 
-import static android.hardware.soundtrigger.ConversionUtil.aidl2apiAudioFormatWithDefault;
-import static android.hardware.soundtrigger.ConversionUtil.aidl2apiPhrase;
-import static android.hardware.soundtrigger.ConversionUtil.aidl2apiRecognitionConfig;
-import static android.hardware.soundtrigger.ConversionUtil.api2aidlPhrase;
-import static android.hardware.soundtrigger.ConversionUtil.api2aidlRecognitionConfig;
-import static android.hardware.soundtrigger.ConversionUtil.byteArrayToSharedMemory;
-import static android.hardware.soundtrigger.ConversionUtil.sharedMemoryToByteArray;
-import static android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel;
-import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_GENERIC;
-import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION;
-import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
-import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER;
-
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 
 import android.hardware.audio.common.V2_0.Uuid;
-import android.hardware.soundtrigger.SoundTrigger;
 
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.Locale;
-
 @RunWith(AndroidJUnit4.class)
 public class ConversionUtilTest {
-    private static final String TAG = "ConversionUtilTest";
+    private static final String TAG = "SoundTriggerMiddlewareConversionUtilTest";
 
     @Test
     public void testUuidRoundTrip() {
@@ -62,54 +44,4 @@
         Uuid reconstructed = ConversionUtil.aidl2hidlUuid(aidl);
         assertEquals(hidl, reconstructed);
     }
-
-    @Test
-    public void testDefaultAudioFormatConstruction() {
-        // This method should generate a real format when passed null
-        final var format = aidl2apiAudioFormatWithDefault(
-                null /** exercise default **/,
-                true /** isInput **/
-                );
-        assertNotNull(format);
-    }
-
-    @Test
-    public void testRecognitionConfigRoundTrip() {
-        final int flags = SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION
-                | SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION;
-        final var data = new byte[] {0x11, 0x22};
-        final var keyphrases = new SoundTrigger.KeyphraseRecognitionExtra[2];
-        keyphrases[0] = new SoundTrigger.KeyphraseRecognitionExtra(99,
-                RECOGNITION_MODE_VOICE_TRIGGER | RECOGNITION_MODE_USER_IDENTIFICATION, 13,
-                    new ConfidenceLevel[] {new ConfidenceLevel(9999, 50),
-                                           new ConfidenceLevel(5000, 80)});
-        keyphrases[1] = new SoundTrigger.KeyphraseRecognitionExtra(101,
-                RECOGNITION_MODE_GENERIC, 8, new ConfidenceLevel[] {
-                    new ConfidenceLevel(7777, 30),
-                    new ConfidenceLevel(2222, 60)});
-
-        var apiconfig = new SoundTrigger.RecognitionConfig(true, false /** must be false **/,
-                keyphrases, data, flags);
-        assertEquals(apiconfig, aidl2apiRecognitionConfig(api2aidlRecognitionConfig(apiconfig)));
-    }
-
-    @Test
-    public void testByteArraySharedMemRoundTrip() {
-        final var data = new byte[] { 0x11, 0x22, 0x33, 0x44,
-                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef };
-        assertArrayEquals(data, sharedMemoryToByteArray(byteArrayToSharedMemory(data, "name"),
-                    10000000));
-
-    }
-
-    @Test
-    public void testPhraseRoundTrip() {
-        final var users = new int[] {10001, 10002};
-        final var apiphrase = new SoundTrigger.Keyphrase(17 /** id **/,
-                RECOGNITION_MODE_VOICE_TRIGGER | RECOGNITION_MODE_USER_AUTHENTICATION,
-                Locale.forLanguageTag("no_NO"),
-                "Hello Android", /** keyphrase **/
-                users);
-        assertEquals(apiphrase, aidl2apiPhrase(api2aidlPhrase(apiphrase)));
-    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 20d410c..2914de1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -81,6 +81,17 @@
         return win;
     }
 
+    private WindowState createDreamWindow() {
+        final WindowState win = createDreamWindow(null, TYPE_BASE_APPLICATION, "dream");
+        final WindowManager.LayoutParams attrs = win.mAttrs;
+        attrs.width = MATCH_PARENT;
+        attrs.height = MATCH_PARENT;
+        attrs.flags =
+                FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        attrs.format = PixelFormat.OPAQUE;
+        return win;
+    }
+
     private WindowState createDimmingDialogWindow(boolean canBeImTarget) {
         final WindowState win = spy(createWindow(null, TYPE_APPLICATION, "dimmingDialog"));
         final WindowManager.LayoutParams attrs = win.mAttrs;
@@ -384,4 +395,25 @@
         displayPolicy.requestTransientBars(mNavBarWindow, true);
         assertTrue(mDisplayContent.getInsetsPolicy().isTransient(navigationBars()));
     }
+
+    @UseTestDisplay(addWindows = { W_NAVIGATION_BAR })
+    @Test
+    public void testTransientBarsSuppressedOnDreams() {
+        final WindowState win = createDreamWindow();
+
+        ((TestWindowManagerPolicy) mWm.mPolicy).mIsUserSetupComplete = true;
+        win.mAttrs.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+        win.setRequestedVisibleTypes(0, navigationBars());
+
+        final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+        displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
+        final InsetsSourceProvider navBarProvider = mNavBarWindow.getControllableInsetProvider();
+        navBarProvider.updateControlForTarget(win, false);
+        navBarProvider.getSource().setVisible(false);
+
+        displayPolicy.setCanSystemBarsBeShownByUser(true);
+        displayPolicy.requestTransientBars(mNavBarWindow, true);
+
+        assertFalse(mDisplayContent.getInsetsPolicy().isTransient(navigationBars()));
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
similarity index 96%
rename from services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
rename to services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index ef20f2b..b35eceb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -42,20 +42,20 @@
 @SmallTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class WindowContainerInsetsSourceProviderTest extends WindowTestsBase {
+public class InsetsSourceProviderTest extends WindowTestsBase {
 
     private InsetsSource mSource = new InsetsSource(
             InsetsSource.createId(null, 0, statusBars()), statusBars());
-    private WindowContainerInsetsSourceProvider mProvider;
+    private InsetsSourceProvider mProvider;
     private InsetsSource mImeSource = new InsetsSource(ID_IME, ime());
-    private WindowContainerInsetsSourceProvider mImeProvider;
+    private InsetsSourceProvider mImeProvider;
 
     @Before
     public void setUp() throws Exception {
         mSource.setVisible(true);
-        mProvider = new WindowContainerInsetsSourceProvider(mSource,
+        mProvider = new InsetsSourceProvider(mSource,
                 mDisplayContent.getInsetsStateController(), mDisplayContent);
-        mImeProvider = new WindowContainerInsetsSourceProvider(mImeSource,
+        mImeProvider = new InsetsSourceProvider(mImeSource,
                 mDisplayContent.getInsetsStateController(), mDisplayContent);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 74fde65..ff2944a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -287,7 +287,7 @@
         // IME cannot be the IME target.
         ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
 
-        WindowContainerInsetsSourceProvider statusBarProvider =
+        InsetsSourceProvider statusBarProvider =
                 getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars());
         final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>> imeOverrideProviders =
                 new SparseArray<>();
@@ -353,7 +353,7 @@
     public void testTransientVisibilityOfFixedRotationState() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
-        final WindowContainerInsetsSourceProvider provider = getController()
+        final InsetsSourceProvider provider = getController()
                 .getOrCreateSourceProvider(ID_STATUS_BAR, statusBars());
         provider.setWindowContainer(statusBar, null, null);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index d7bf4b0..90506d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1885,6 +1885,39 @@
         assertEquals(newParent.getDisplayArea(), change.mCommonAncestor);
     }
 
+    @Test
+    public void testMoveToTopWhileVisible() {
+        final Transition transition = createTestTransition(TRANSIT_OPEN);
+        final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+        final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+        // Start with taskB on top and taskA on bottom but both visible.
+        final Task rootTaskA = createTask(mDisplayContent);
+        final Task leafTaskA = createTaskInRootTask(rootTaskA, 0 /* userId */);
+        final Task taskB = createTask(mDisplayContent);
+        leafTaskA.setVisibleRequested(true);
+        taskB.setVisibleRequested(true);
+        // manually collect since this is a test transition and not known by transitionController.
+        transition.collect(leafTaskA);
+        rootTaskA.moveToFront("test", leafTaskA);
+
+        // All the tasks were already visible, so there shouldn't be any changes
+        ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
+                participants, changes);
+        assertTrue(targets.isEmpty());
+
+        // After collecting order changes, it should recognize that a task moved to top.
+        transition.collectOrderChanges();
+        targets = Transition.calculateTargets(participants, changes);
+        assertEquals(1, targets.size());
+
+        // Make sure the flag is set
+        final TransitionInfo info = Transition.calculateTransitionInfo(
+                transition.mType, 0 /* flags */, targets, mMockT);
+        assertTrue((info.getChanges().get(0).getFlags() & TransitionInfo.FLAG_MOVED_TO_TOP) != 0);
+        assertEquals(TRANSIT_CHANGE, info.getChanges().get(0).getMode());
+    }
+
     private static void makeTaskOrganized(Task... tasks) {
         final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
         for (Task t : tasks) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 6261e56..a1ddd57 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -484,7 +484,7 @@
                 windowState.mSurfaceAnimator).getAnimationType();
         assertTrue(parent.isAnimating(CHILDREN));
 
-        windowState.setControllableInsetProvider(mock(WindowContainerInsetsSourceProvider.class));
+        windowState.setControllableInsetProvider(mock(InsetsSourceProvider.class));
         assertFalse(parent.isAnimating(CHILDREN));
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 0d7cdc8..7e3ec55 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.app.AppOpsManager.OP_NONE;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -467,6 +468,12 @@
         return createWindow(null, type, activity, name);
     }
 
+    WindowState createDreamWindow(WindowState parent, int type, String name) {
+        final WindowToken token = createWindowToken(
+                mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM, type);
+        return createWindow(parent, type, token, name);
+    }
+
     // TODO: Move these calls to a builder?
     WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name,
             IWindow iwindow) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/FakeSoundTriggerHal.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/FakeSoundTriggerHal.java
index 86c4bbf..37a325e 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/FakeSoundTriggerHal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/FakeSoundTriggerHal.java
@@ -276,16 +276,15 @@
         // for our clients.
         mGlobalEventSession = new IInjectGlobalEvent.Stub() {
             /**
-             * Overrides IInjectGlobalEvent method.
              * Simulate a HAL process restart. This method is not included in regular HAL interface,
              * since the entire process is restarted by sending a signal.
              * Since we run in-proc, we must offer an explicit restart method.
              * oneway
              */
             @Override
-            public void triggerRestart() throws RemoteException {
+            public void triggerRestart() {
                 synchronized (FakeSoundTriggerHal.this.mLock) {
-                    if (mIsDead) throw new DeadObjectException();
+                    if (mIsDead) return;
                     mIsDead = true;
                     mInjectionDispatcher.wrap((ISoundTriggerInjection cb) ->
                             cb.onRestarted(this));
@@ -305,15 +304,15 @@
                 }
             }
 
-            /**
-             * Overrides IInjectGlobalEvent method.
-             * oneway
-             */
+            // oneway
             @Override
             public void setResourceContention(boolean isResourcesContended,
-                        IAcknowledgeEvent callback) throws RemoteException {
+                        IAcknowledgeEvent callback) {
                 synchronized (FakeSoundTriggerHal.this.mLock) {
-                    if (mIsDead) throw new DeadObjectException();
+                    // oneway, so don't throw on death
+                    if (mIsDead || mIsResourceContended == isResourcesContended) {
+                        return;
+                    }
                     mIsResourceContended = isResourcesContended;
                     // Introducing contention is the only injection which can't be
                     // observed by the ST client.
@@ -325,7 +324,19 @@
                     }
                 }
             }
+
+            // oneway
+            @Override
+            public void triggerOnResourcesAvailable() {
+                synchronized (FakeSoundTriggerHal.this.mLock) {
+                    // oneway, so don't throw on death
+                    if (mIsDead) return;
+                    mGlobalCallbackDispatcher.wrap((ISoundTriggerHwGlobalCallback cb) ->
+                            cb.onResourcesAvailable());
+                }
+            }
         };
+
         // Register the global event injection interface
         mInjectionDispatcher.wrap((ISoundTriggerInjection cb)
                 -> cb.registerGlobalEventInjection(mGlobalEventSession));
@@ -465,7 +476,9 @@
             if (session == null) {
                 Slog.wtf(TAG, "Attempted to start recognition with invalid handle");
             }
-
+            if (mIsResourceContended) {
+                throw new ServiceSpecificException(Status.RESOURCE_CONTENTION);
+            }
             if (session.getIsUnloaded()) {
                 // TODO(b/274470274) this is a deficiency in the existing HAL API, there is no way
                 // to handle this race gracefully
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
index 2f8d17d..4c134af 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -35,7 +35,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.util.Log;
+import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -463,7 +463,7 @@
                 printObject(originatorIdentity),
                 printArgs(args),
                 printObject(retVal));
-        Log.i(TAG, message);
+        Slog.i(TAG, message);
         appendMessage(message);
     }
 
@@ -474,7 +474,7 @@
                 object,
                 printObject(originatorIdentity),
                 printArgs(args));
-        Log.i(TAG, message);
+        Slog.i(TAG, message);
         appendMessage(message);
     }
 
@@ -486,7 +486,7 @@
                 object,
                 printObject(originatorIdentity),
                 printArgs(args));
-        Log.e(TAG, message, ex);
+        Slog.e(TAG, message, ex);
         appendMessage(message + " " + ex.toString());
     }
 
diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index fa4c9b2..ff378ba 100644
--- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -43,6 +43,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 /**
@@ -325,11 +326,19 @@
     }
 
     private void addRegistrationCallback(IImsRegistrationCallback c) throws RemoteException {
+        // This is purposefully not synchronized with broadcastToCallbacksLocked because the
+        // list of callbacks to notify is copied over from the original list modified here. I also
+        // do not want to risk introducing a deadlock by using the same mCallbacks Object to
+        // synchronize on outgoing and incoming operations.
         mCallbacks.register(c);
         updateNewCallbackWithState(c);
     }
 
     private void removeRegistrationCallback(IImsRegistrationCallback c) {
+        // This is purposefully not synchronized with broadcastToCallbacksLocked because the
+        // list of callbacks to notify is copied over from the original list modified here. I also
+        // do not want to risk introducing a deadlock by using the same mCallbacks Object to
+        // synchronize on outgoing and incoming operations.
         mCallbacks.unregister(c);
     }
 
@@ -420,7 +429,7 @@
     @SystemApi
     public final void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
         updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERED);
-        mCallbacks.broadcastAction((c) -> {
+        broadcastToCallbacksLocked((c) -> {
             try {
                 c.onRegistered(attributes);
             } catch (RemoteException e) {
@@ -449,7 +458,7 @@
     @SystemApi
     public final void onRegistering(@NonNull ImsRegistrationAttributes attributes) {
         updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERING);
-        mCallbacks.broadcastAction((c) -> {
+        broadcastToCallbacksLocked((c) -> {
             try {
                 c.onRegistering(attributes);
             } catch (RemoteException e) {
@@ -507,7 +516,7 @@
         updateToDisconnectedState(info, suggestedAction, imsRadioTech);
         // ImsReasonInfo should never be null.
         final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
-        mCallbacks.broadcastAction((c) -> {
+        broadcastToCallbacksLocked((c) -> {
             try {
                 c.onDeregistered(reasonInfo, suggestedAction, imsRadioTech);
             } catch (RemoteException e) {
@@ -569,7 +578,7 @@
         updateToDisconnectedState(info, suggestedAction, imsRadioTech);
         // ImsReasonInfo should never be null.
         final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
-        mCallbacks.broadcastAction((c) -> {
+        broadcastToCallbacksLocked((c) -> {
             try {
                 c.onDeregisteredWithDetails(reasonInfo, suggestedAction, imsRadioTech, details);
             } catch (RemoteException e) {
@@ -591,7 +600,7 @@
     public final void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech,
             ImsReasonInfo info) {
         final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
-        mCallbacks.broadcastAction((c) -> {
+        broadcastToCallbacksLocked((c) -> {
             try {
                 c.onTechnologyChangeFailed(imsRadioTech, reasonInfo);
             } catch (RemoteException e) {
@@ -614,7 +623,20 @@
             mUris = ArrayUtils.cloneOrNull(uris);
             mUrisSet = true;
         }
-        mCallbacks.broadcastAction((c) -> onSubscriberAssociatedUriChanged(c, uris));
+        broadcastToCallbacksLocked((c) -> onSubscriberAssociatedUriChanged(c, uris));
+    }
+
+    /**
+     * Broadcast the specified operation in a synchronized manner so that multiple threads do not
+     * try to call broadcast at the same time, which will generate an error.
+     * @param c The Consumer lambda method containing the callback to call.
+     */
+    private void broadcastToCallbacksLocked(Consumer<IImsRegistrationCallback> c) {
+        // One broadcast can happen at a time, so synchronize threads so only one
+        // beginBroadcast/endBroadcast happens at a time.
+        synchronized (mCallbacks) {
+            mCallbacks.broadcastAction(c);
+        }
     }
 
     private void onSubscriberAssociatedUriChanged(IImsRegistrationCallback callback, Uri[] uris) {
diff --git a/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl
index 2954c2d..e229f05 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl
@@ -18,7 +18,7 @@
 
 import android.telephony.satellite.SatelliteDatagram;
 
-import com.android.internal.telephony.ILongConsumer;
+import com.android.internal.telephony.IVoidConsumer;
 
 /**
  * Interface for satellite datagrams callback.
@@ -31,10 +31,10 @@
      * @param datagramId An id that uniquely identifies incoming datagram.
      * @param datagram Datagram received from satellite.
      * @param pendingCount Number of datagrams yet to be received from satellite.
-     * @param callback This callback will be used by datagram receiver app to send received
-     *                 datagramId to Telephony. If the callback is not received within five minutes,
-     *                 Telephony will resend the datagram.
+     * @param callback This callback will be used by datagram receiver app to to inform
+     *                 Telephony that datagram is received. If the callback is not received
+     *                 within five minutes, Telephony will resend the datagram.
      */
     void onSatelliteDatagramReceived(long datagramId, in SatelliteDatagram datagram,
-            int pendingCount, ILongConsumer callback);
+            int pendingCount, IVoidConsumer callback);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
index d8a6faf..d0409bf 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
@@ -19,7 +19,7 @@
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 
-import com.android.internal.telephony.ILongConsumer;
+import java.util.function.Consumer;
 
 /**
  * A callback class for listening to satellite datagrams.
@@ -33,11 +33,11 @@
      * @param datagramId An id that uniquely identifies incoming datagram.
      * @param datagram Datagram to be received over satellite.
      * @param pendingCount Number of datagrams yet to be received by the app.
-     * @param callback This callback will be used by datagram receiver app to send received
-     *                 datagramId to Telephony. If the callback is not received within five minutes,
-     *                 Telephony will resend the datagram.
+     * @param callback This callback will be used by datagram receiver app to inform Telephony
+     *                 that they received the datagram. If the callback is not received within
+     *                 five minutes, Telephony will resend the datagram.
      */
     @UnsupportedAppUsage
     void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
-            int pendingCount, @NonNull ILongConsumer callback);
+            int pendingCount, @NonNull Consumer<Void> callback);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 7d82fd8..20f9bc8b 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -37,7 +37,7 @@
 import android.telephony.TelephonyFrameworkInitializer;
 
 import com.android.internal.telephony.IIntegerConsumer;
-import com.android.internal.telephony.ILongConsumer;
+import com.android.internal.telephony.IVoidConsumer;
 import com.android.internal.telephony.ITelephony;
 import com.android.telephony.Rlog;
 
@@ -862,7 +862,7 @@
      *
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
-     * @param regionId The region ID for the device's current location.
+     * @param provisionData Data from the provisioning app that can be used by provisioning server
      * @param cancellationSignal The optional signal used by the caller to cancel the provision
      *                           request. Even when the cancellation is signaled, Telephony will
      *                           still trigger the callback to return the result of this request.
@@ -874,13 +874,14 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @UnsupportedAppUsage
-    public void provisionSatelliteService(@NonNull String token, @NonNull String regionId,
+    public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
             @Nullable CancellationSignal cancellationSignal,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteError @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(token);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(resultListener);
+        Objects.requireNonNull(provisionData);
 
         ICancellationSignal cancelRemote = null;
         try {
@@ -893,7 +894,7 @@
                                 () -> resultListener.accept(result)));
                     }
                 };
-                cancelRemote = telephony.provisionSatelliteService(mSubId, token, regionId,
+                cancelRemote = telephony.provisionSatelliteService(mSubId, token, provisionData,
                         errorCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
@@ -1186,10 +1187,22 @@
                             @Override
                             public void onSatelliteDatagramReceived(long datagramId,
                                     @NonNull SatelliteDatagram datagram, int pendingCount,
-                                    @NonNull ILongConsumer ack) {
+                                    @NonNull IVoidConsumer internalAck) {
+                                Consumer<Void> externalAck = new Consumer<Void>() {
+                                    @Override
+                                    public void accept(Void result) {
+                                        try {
+                                            internalAck.accept();
+                                        }  catch (RemoteException e) {
+                                              logd("onSatelliteDatagramReceived "
+                                                      + "RemoteException: " + e);
+                                        }
+                                    }
+                                };
+
                                 executor.execute(() -> Binder.withCleanCallingIdentity(
                                         () -> callback.onSatelliteDatagramReceived(
-                                                datagramId, datagram, pendingCount, ack)));
+                                                datagramId, datagram, pendingCount, externalAck)));
                             }
                         };
                 sSatelliteDatagramCallbackMap.put(callback, internalCallback);
@@ -1244,7 +1257,7 @@
      * This method requests modem to check if there are any pending datagrams to be received over
      * satellite. If there are any incoming datagrams, they will be received via
      * {@link SatelliteDatagramCallback#onSatelliteDatagramReceived(long, SatelliteDatagram, int,
-     *        ILongConsumer)}
+     * Consumer)} )}
      *
      * @param executor The executor on which the result listener will be called.
      * @param resultListener Listener for the {@link SatelliteError} result of the operation.
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index a780cb9..ea4e2e2 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -67,6 +67,15 @@
             in IIntegerConsumer resultCallback);
 
     /**
+     * Allow cellular modem scanning while satellite mode is on.
+     * @param enabled  {@code true} to enable cellular modem while satellite mode is on
+     * and {@code false} to disable
+     * @param errorCallback The callback to receive the error code result of the operation.
+     */
+    void enableCellularModemWhileSatelliteModeIsOn(in boolean enabled,
+        in IIntegerConsumer errorCallback);
+
+    /**
      * Request to enable or disable the satellite modem and demo mode. If the satellite modem
      * is enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
      * this may also re-enable the cellular modem.
@@ -194,7 +203,7 @@
      *
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
-     * @param regionId The region ID for the device's current location.
+     * @param provisionData Data from the provisioning app that can be used by provisioning server
      * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
@@ -210,7 +219,7 @@
      *   SatelliteError:REQUEST_ABORTED
      *   SatelliteError:NETWORK_TIMEOUT
      */
-    void provisionSatelliteService(in String token, in String regionId,
+    void provisionSatelliteService(in String token, in byte[] provisionData,
             in IIntegerConsumer resultCallback);
 
     /**
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index debb394..17d026c 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -79,6 +79,15 @@
         }
 
         @Override
+        public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled,
+                IIntegerConsumer errorCallback) throws RemoteException {
+            executeMethodAsync(
+                    () -> SatelliteImplBase.this
+                            .enableCellularModemWhileSatelliteModeIsOn(enabled, errorCallback),
+                    "enableCellularModemWhileSatelliteModeIsOn");
+        }
+
+        @Override
         public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
                 IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
@@ -132,11 +141,11 @@
         }
 
         @Override
-        public void provisionSatelliteService(String token, String regionId,
+        public void provisionSatelliteService(String token, byte[] provisionData,
                 IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .provisionSatelliteService(token, regionId, errorCallback),
+                            .provisionSatelliteService(token, provisionData, errorCallback),
                     "provisionSatelliteService");
         }
 
@@ -261,6 +270,17 @@
     }
 
     /**
+     * Allow cellular modem scanning while satellite mode is on.
+     * @param enabled  {@code true} to enable cellular modem while satellite mode is on
+     * and {@code false} to disable
+     * @param errorCallback The callback to receive the error code result of the operation.
+     */
+    public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled,
+            @NonNull IIntegerConsumer errorCallback) {
+        // stub implementation
+    }
+
+    /**
      * Request to enable or disable the satellite modem and demo mode. If the satellite modem is
      * enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
      * this may also re-enable the cellular modem.
@@ -401,7 +421,8 @@
      *
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
-     * @param regionId The region ID for the device's current location.
+     * @param provisionData Data from the provisioning app that can be used by provisioning
+     *                      server
      * @param errorCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
@@ -417,7 +438,7 @@
      *   SatelliteError:REQUEST_ABORTED
      *   SatelliteError:NETWORK_TIMEOUT
      */
-    public void provisionSatelliteService(@NonNull String token, @NonNull String regionId,
+    public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
             @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index b890005..cbdf38ae 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2823,15 +2823,15 @@
      * @param subId The subId of the subscription to be provisioned.
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
-     * @param regionId The region ID for the device's current location.
+     * @provisionData Data from the provisioning app that can be used by provisioning server
      * @param callback The callback to get the result of the request.
      *
      * @return The signal transport used by callers to cancel the provision request.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    ICancellationSignal provisionSatelliteService(int subId, in String token, in String regionId,
-            in IIntegerConsumer callback);
+    ICancellationSignal provisionSatelliteService(int subId, in String token,
+            in byte[] provisionData, in IIntegerConsumer callback);
 
     /**
      * Unregister the subscription with the satellite provider.
diff --git a/telephony/java/com/android/internal/telephony/IVoidConsumer.aidl b/telephony/java/com/android/internal/telephony/IVoidConsumer.aidl
new file mode 100644
index 0000000..b5557fd
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IVoidConsumer.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package com.android.internal.telephony;
+
+ /**
+  * Copies consumer pattern for an operation that requires void result from another process to
+  * finish.
+  */
+ oneway interface IVoidConsumer {
+    void accept();
+ }
\ No newline at end of file
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index fef5211..4ba538e 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -50,6 +50,9 @@
         "platform-test-annotations",
         "wm-flicker-window-extensions",
     ],
+    data: [
+        ":FlickerTestApp",
+    ],
 }
 
 java_library {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 9dc4bf0..314b9e4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -280,21 +280,28 @@
  *
  * @param originalLayer
  * ```
+ *
  * Layer that should be visible at the start
+ *
  * @param newLayer Layer that should be visible at the end
  * @param ignoreEntriesWithRotationLayer If entries with a visible rotation layer should be ignored
+ *
  * ```
  *      when checking the transition. If true we will not fail the assertion if a rotation layer is
  *      visible to fill the gap between the [originalLayer] being visible and the [newLayer] being
  *      visible.
  * @param ignoreSnapshot
  * ```
+ *
  * If the snapshot layer should be ignored during the transition
+ *
  * ```
  *     (useful mostly for app launch)
  * @param ignoreSplashscreen
  * ```
+ *
  * If the splashscreen layer should be ignored during the transition.
+ *
  * ```
  *      If true then we will allow for a splashscreen to be shown before the layer is shown,
  *      otherwise we won't and the layer must appear immediately.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
index 7aea05d..fde0981 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
@@ -71,7 +71,7 @@
      * Open Assistance UI.
      *
      * @param longpress open the UI by long pressing power button. Otherwise open the UI through
-     * vioceinteraction shell command directly.
+     *   vioceinteraction shell command directly.
      */
     @JvmOverloads
     fun openUI(longpress: Boolean = false) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
index 79c048a..d4f48fe 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
@@ -60,7 +60,6 @@
      *
      * @param wmHelper Helper used to get window region.
      * @param direction UiAutomator Direction enum to indicate the swipe direction.
-     *
      * @return true if the swipe operation is successful.
      */
     fun switchToPreviousAppByQuickSwitchGesture(
@@ -96,7 +95,6 @@
      * @param packageName The targe application's package name.
      * @param identifier The resource id of the target object.
      * @param timeout The timeout duration in milliseconds.
-     *
      * @return true if the target object exists.
      */
     @JvmOverloads
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index e497ae4..a72c12d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -57,18 +57,16 @@
         obj.click()
     }
 
-    /**
-     * Drags the PIP window to the provided final coordinates without releasing the pointer.
-     */
-    fun dragPipWindowAwayFromEdgeWithoutRelease(
-        wmHelper: WindowManagerStateHelper,
-        steps: Int
-    ) {
+    /** Drags the PIP window to the provided final coordinates without releasing the pointer. */
+    fun dragPipWindowAwayFromEdgeWithoutRelease(wmHelper: WindowManagerStateHelper, steps: Int) {
         val initWindowRect = getWindowRect(wmHelper).clone()
 
         // initial pointer at the center of the window
-        val initialCoord = GestureHelper.Tuple(initWindowRect.centerX().toFloat(),
-                initWindowRect.centerY().toFloat())
+        val initialCoord =
+            GestureHelper.Tuple(
+                initWindowRect.centerX().toFloat(),
+                initWindowRect.centerY().toFloat()
+            )
 
         // the offset to the right (or left) of the window center to drag the window to
         val offset = 50
@@ -76,8 +74,8 @@
         // the actual final x coordinate with the offset included;
         // if the pip window is closer to the right edge of the display the offset is negative
         // otherwise the offset is positive
-        val endX = initWindowRect.centerX() +
-            offset * (if (isCloserToRightEdge(wmHelper)) -1 else 1)
+        val endX =
+            initWindowRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) -1 else 1)
         val finalCoord = GestureHelper.Tuple(endX.toFloat(), initWindowRect.centerY().toFloat())
 
         // drag to the final coordinate
@@ -106,7 +104,8 @@
         val startX = initWindowRect.centerX()
         val y = initWindowRect.centerY()
 
-        val displayRect = wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
+        val displayRect =
+            wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
                 ?: throw IllegalStateException("Default display is null")
 
         // the offset to the right (or left) of the display center to drag the window to
@@ -129,7 +128,8 @@
     fun isCloserToRightEdge(wmHelper: WindowManagerStateHelper): Boolean {
         val windowRect = getWindowRect(wmHelper)
 
-        val displayRect = wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
+        val displayRect =
+            wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
                 ?: throw IllegalStateException("Default display is null")
 
         return windowRect.centerX() > displayRect.centerX()
@@ -301,9 +301,7 @@
         closePipWindow(WindowManagerStateHelper(mInstrumentation))
     }
 
-    /**
-     * Returns the pip window bounds.
-     */
+    /** Returns the pip window bounds. */
     fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect {
         val windowRegion = wmHelper.getWindowRegion(this)
         require(!windowRegion.isEmpty) { "Unable to find a PIP window in the current state" }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt
index 432df20..c355e27 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt
@@ -39,4 +39,4 @@
             )
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
index a4e4b6f..df9d33b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
@@ -40,6 +40,7 @@
  *     Don't show if this is not explicitly requested by the user and the input method
  *     is fullscreen. That would be too disruptive.
  * ```
+ *
  * More details on b/190352379
  *
  * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToHomeTest`
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
index e85da1f..7954dd1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
@@ -40,6 +40,7 @@
  *     Don't show if this is not explicitly requested by the user and the input method
  *     is fullscreen. That would be too disruptive.
  * ```
+ *
  * More details on b/190352379
  *
  * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToAppTest`
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index e2d6dbf..2fff001 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -20,7 +20,6 @@
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -29,7 +28,6 @@
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -65,17 +63,9 @@
 
     @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
 
-    @Presubmit
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-    }
-
     @FlakyTest(bugId = 246284124)
     @Test
-    fun visibleLayersShownMoreThanOneConsecutiveEntry_shellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
index 1fee20d..a3fb73b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
@@ -16,20 +16,17 @@
 
 package com.android.server.wm.flicker.ime
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.snapshotStartingWindowLayerCoversExactlyOnApp
-import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -74,17 +71,9 @@
 
     @Presubmit @Test fun imeLayerBecomesVisible() = flicker.imeLayerBecomesVisible()
 
-    @FlakyTest(bugId = 240918620)
-    @Test
-    fun snapshotStartingWindowLayerCoversExactlyOnApp() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
-    }
-
     @Presubmit
     @Test
-    fun snapshotStartingWindowLayerCoversExactlyOnApp_ShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
+    fun snapshotStartingWindowLayerCoversExactlyOnApp() {
         flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt
index efda0ff..e1aa418 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt
@@ -40,4 +40,4 @@
             )
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
index daee332..690ed53 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
@@ -20,13 +20,13 @@
 import android.tools.common.NavBar
 import android.tools.common.Rotation
 import android.tools.common.datatypes.component.ComponentNameMatcher
-import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.FixMethodOrder
@@ -86,9 +86,7 @@
         }
     }
     /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun entireScreenCovered() = super.entireScreenCovered()
+    @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     @Presubmit
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
index 7514c9b..866e858 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
@@ -19,14 +19,14 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
 import android.tools.common.datatypes.component.ComponentNameMatcher
-import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
-import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
+import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -44,20 +44,27 @@
  *     Make sure no apps are running on the device
  *     Launch an app [testApp] that automatically displays IME and wait animation to complete
  * ```
+ *
  * To run only the presubmit assertions add: `--
+ *
  * ```
  *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
  *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
  * ```
+ *
  * To run only the postsubmit assertions add: `--
+ *
  * ```
  *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
  *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
  * ```
+ *
  * To run only the flaky assertions add: `--
+ *
  * ```
  *      --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
  * ```
+ *
  * Notes:
  * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
index a57aa5b..6f22589 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
@@ -19,7 +19,6 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
 import android.tools.common.datatypes.component.ComponentNameMatcher
-import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -29,6 +28,7 @@
 import android.view.WindowInsets.Type.statusBars
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.FixMethodOrder
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt
index cffc05d..8891d26 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt
@@ -45,4 +45,4 @@
             )
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
index 9ea12a9..231d0d7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
@@ -19,8 +19,6 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.common.datatypes.component.ComponentNameMatcher
 import android.tools.common.traces.ConditionsFactory
-import android.tools.device.flicker.isShellTransitionsEnabled
-import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -28,6 +26,7 @@
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
 import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
 import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
 import org.junit.Assume
@@ -101,16 +100,6 @@
         flicker.navBarLayerIsVisibleAtStartAndEnd()
     }
 
-    /** Bars are expected to be hidden while entering overview in landscape (b/227189877) */
-    @Presubmit
-    @Test
-    fun navBarLayerIsVisibleAtStartAndEndGestural() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        flicker.navBarLayerIsVisibleAtStartAndEnd()
-    }
-
     /**
      * In the legacy transitions, the nav bar is not marked as invisible. In the new transitions
      * this is fixed and the nav bar shows as invisible
@@ -121,7 +110,6 @@
         Assume.assumeFalse(flicker.scenario.isTablet)
         Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
         Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
-        Assume.assumeTrue(isShellTransitionsEnabled)
         flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
         flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.NAV_BAR) }
     }
@@ -186,25 +174,15 @@
 
     @Presubmit
     @Test
-    fun statusBarLayerIsInvisibleInLandscapeShell() {
+    fun statusBarLayerIsInvisibleInLandscape() {
         Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
         Assume.assumeFalse(flicker.scenario.isTablet)
-        Assume.assumeTrue(isShellTransitionsEnabled)
         flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
         flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
     }
 
     @Presubmit
     @Test
-    fun statusBarLayerIsVisibleInLandscapeLegacy() {
-        Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
-        Assume.assumeTrue(flicker.scenario.isTablet)
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        flicker.statusBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    @Presubmit
-    @Test
     fun imeLayerIsVisibleAndAssociatedWithAppWidow() {
         flicker.assertLayersStart {
             isVisible(ComponentNameMatcher.IME)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
index e8f9aa3..3c577ac 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -44,6 +44,7 @@
  *     Launch a secondary activity within the app
  *     Close the secondary activity back to the initial one
  * ```
+ *
  * Notes:
  * ```
  *     1. Part of the test setup occurs automatically via
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
index 05abf9f..360a233 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -39,6 +39,7 @@
  *     Make sure no apps are running on the device
  *     Launch an app [testApp] by clicking it's icon on all apps and wait animation to complete
  * ```
+ *
  * Notes:
  * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
index 63ffee6..12c0874 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
@@ -135,14 +135,15 @@
         }
 
         /**
-         * Ensures that posted notifications will be visible on the lockscreen and not
-         * suppressed due to being marked as seen.
+         * Ensures that posted notifications will be visible on the lockscreen and not suppressed
+         * due to being marked as seen.
          */
         @ClassRule
         @JvmField
-        val disableUnseenNotifFilterRule = SettingOverrideRule(
-            Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-            /* value= */ "0",
-        )
+        val disableUnseenNotifFilterRule =
+            SettingOverrideRule(
+                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                /* value= */ "0",
+            )
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
index a221ef6..222caed 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
@@ -150,14 +150,15 @@
         }
 
         /**
-         * Ensures that posted notifications will be visible on the lockscreen and not
-         * suppressed due to being marked as seen.
+         * Ensures that posted notifications will be visible on the lockscreen and not suppressed
+         * due to being marked as seen.
          */
         @ClassRule
         @JvmField
-        val disableUnseenNotifFilterRule = SettingOverrideRule(
-            Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-            /* value= */ "0",
-        )
+        val disableUnseenNotifFilterRule =
+            SettingOverrideRule(
+                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                /* value= */ "0",
+            )
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt
index d90b3ca..43d28fa 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt
@@ -42,4 +42,4 @@
             return FlickerTestFactory.nonRotationTests()
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 3fccd12..6fa65fd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -22,12 +22,10 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.datatypes.component.ComponentNameMatcher
-import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.DEFAULT_TASK_DISPLAY_AREA
 import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.SPLASH_SCREEN
 import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
 import android.tools.common.datatypes.component.ComponentSplashScreenMatcher
 import android.tools.common.datatypes.component.IComponentMatcher
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -38,7 +36,6 @@
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.NewTasksAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -123,22 +120,8 @@
     /** Checks that a color background is visible while the task transition is occurring. */
     @Presubmit
     @Test
-    fun transitionHasColorBackground_legacy() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        transitionHasColorBackground(DEFAULT_TASK_DISPLAY_AREA)
-    }
-
-    /** Checks that a color background is visible while the task transition is occurring. */
-    @Presubmit
-    @Test
-    fun transitionHasColorBackground_shellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-        transitionHasColorBackground(ComponentNameMatcher("", "Animation Background"))
-    }
-
-    private fun transitionHasColorBackground(backgroundColorLayer: IComponentMatcher) {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-
+    fun transitionHasColorBackground() {
+        val backgroundColorLayer = ComponentNameMatcher("", "Animation Background")
         val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
         flicker.assertLayers {
             this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
@@ -221,9 +204,10 @@
                     .getIdentifier("image_wallpaper_component", "string", "android")
             // frameworks/base/core/res/res/values/config.xml returns package plus class name,
             // but wallpaper layer has only class name
-            val rawComponentMatcher = ComponentNameMatcher.unflattenFromString(
-                instrumentation.targetContext.resources.getString(resourceId)
-            )
+            val rawComponentMatcher =
+                ComponentNameMatcher.unflattenFromString(
+                    instrumentation.targetContext.resources.getString(resourceId)
+                )
 
             return ComponentNameMatcher(rawComponentMatcher.className)
         }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index 63299cb..d49f035 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -22,7 +22,6 @@
 import android.tools.common.Rotation
 import android.tools.common.datatypes.Rect
 import android.tools.common.datatypes.component.ComponentNameMatcher
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -30,7 +29,6 @@
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Ignore
 import org.junit.Test
@@ -262,17 +260,9 @@
     @Test
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
-    @Presubmit
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-    }
-
     @FlakyTest(bugId = 246285528)
     @Test
-    fun visibleLayersShownMoreThanOneConsecutiveEntry_shellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 4a4180b..fe789a7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -39,6 +39,7 @@
  *      0 -> 90 degrees
  *      90 -> 0 degrees
  * ```
+ *
  * Actions:
  * ```
  *     Launch an app (via intent)
@@ -47,22 +48,29 @@
  *     Change device orientation
  *     Stop tracing
  * ```
+ *
  * To run this test: `atest FlickerTests:ChangeAppRotationTest`
  *
  * To run only the presubmit assertions add: `--
+ *
  * ```
  *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
  *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
  * ```
+ *
  * To run only the postsubmit assertions add: `--
+ *
  * ```
  *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
  *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
  * ```
+ *
  * To run only the flaky assertions add: `--
+ *
  * ```
  *      --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
  * ```
+ *
  * Notes:
  * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 17b3b2b..4d010f3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -45,6 +45,7 @@
  *      90 -> 0 degrees
  *      90 -> 0 degrees (with starved UI thread)
  * ```
+ *
  * Actions:
  * ```
  *     Launch an app in fullscreen and supporting seamless rotation (via intent)
@@ -53,22 +54,29 @@
  *     Change device orientation
  *     Stop tracing
  * ```
+ *
  * To run this test: `atest FlickerTests:SeamlessAppRotationTest`
  *
  * To run only the presubmit assertions add: `--
+ *
  * ```
  *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
  *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
  * ```
+ *
  * To run only the postsubmit assertions add: `--
+ *
  * ```
  *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
  *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
  * ```
+ *
  * To run only the flaky assertions add: `--
+ *
  * ```
  *      --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
  * ```
+ *
  * Notes:
  * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/tests/OdmApps/Android.bp b/tests/OdmApps/Android.bp
index de86498..5f03aa2 100644
--- a/tests/OdmApps/Android.bp
+++ b/tests/OdmApps/Android.bp
@@ -26,4 +26,7 @@
     srcs: ["src/**/*.java"],
     libs: ["tradefed"],
     test_suites: ["device-tests"],
+    data: [
+        ":TestOdmApp",
+    ],
 }
diff --git a/tests/SoundTriggerTests/Android.mk b/tests/SoundTriggerTests/Android.mk
deleted file mode 100644
index cc0fa1c..0000000
--- a/tests/SoundTriggerTests/Android.mk
+++ /dev/null
@@ -1,39 +0,0 @@
-#
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-ifeq ($(SOUND_TRIGGER_USE_STUB_MODULE), 1)
-  LOCAL_SRC_FILES := $(call all-subdir-java-files)
-  LOCAL_PRIVILEGED_MODULE := true
-  LOCAL_CERTIFICATE := platform
-  TARGET_OUT_DATA_APPS_PRIVILEGED := $(TARGET_OUT_DATA)/priv-app
-else
-  LOCAL_SRC_FILES := src/android/hardware/soundtrigger/SoundTriggerTest.java
-endif
-
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
-
-LOCAL_PACKAGE_NAME := SoundTriggerTests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../NOTICE
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-include $(BUILD_PACKAGE)
diff --git a/tests/SoundTriggerTests/AndroidManifest.xml b/tests/SoundTriggerTests/AndroidManifest.xml
deleted file mode 100644
index f7454c7..0000000
--- a/tests/SoundTriggerTests/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.hardware.soundtrigger">
-    <uses-permission android:name="android.permission.MANAGE_SOUND_TRIGGER" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationTestRunner"
-                     android:targetPackage="android.hardware.soundtrigger"
-                     android:label="Tests for android.hardware.soundtrigger" />
-</manifest>
diff --git a/tests/SoundTriggerTests/OWNERS b/tests/SoundTriggerTests/OWNERS
deleted file mode 100644
index 1e41886..0000000
--- a/tests/SoundTriggerTests/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /media/java/android/media/soundtrigger/OWNERS
diff --git a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java
deleted file mode 100644
index 2c3592c..0000000
--- a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.soundtrigger;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
-import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
-import android.media.soundtrigger.SoundTriggerManager;
-import android.os.ParcelUuid;
-import android.os.ServiceManager;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.internal.app.ISoundTriggerService;
-
-import org.mockito.MockitoAnnotations;
-
-import java.io.DataOutputStream;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Random;
-import java.util.UUID;
-
-public class GenericSoundModelTest extends AndroidTestCase {
-    static final int MSG_DETECTION_ERROR = -1;
-    static final int MSG_DETECTION_RESUME = 0;
-    static final int MSG_DETECTION_PAUSE = 1;
-    static final int MSG_KEYPHRASE_TRIGGER = 2;
-    static final int MSG_GENERIC_TRIGGER = 4;
-
-    private Random random = new Random();
-    private HashSet<UUID> loadedModelUuids;
-    private ISoundTriggerService soundTriggerService;
-    private SoundTriggerManager soundTriggerManager;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        MockitoAnnotations.initMocks(this);
-
-        Context context = getContext();
-        soundTriggerService = ISoundTriggerService.Stub.asInterface(
-                ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE));
-        soundTriggerManager = (SoundTriggerManager) context.getSystemService(
-                Context.SOUND_TRIGGER_SERVICE);
-
-        loadedModelUuids = new HashSet<UUID>();
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        for (UUID modelUuid : loadedModelUuids) {
-            soundTriggerService.deleteSoundModel(new ParcelUuid(modelUuid));
-        }
-        super.tearDown();
-    }
-
-    GenericSoundModel new_sound_model() {
-        // Create sound model
-        byte[] data = new byte[1024];
-        random.nextBytes(data);
-        UUID modelUuid = UUID.randomUUID();
-        UUID mVendorUuid = UUID.randomUUID();
-        return new GenericSoundModel(modelUuid, mVendorUuid, data);
-    }
-
-    @SmallTest
-    public void testUpdateGenericSoundModel() throws Exception {
-        GenericSoundModel model = new_sound_model();
-
-        // Update sound model
-        soundTriggerService.updateSoundModel(model);
-        loadedModelUuids.add(model.getUuid());
-
-        // Confirm it was updated
-        GenericSoundModel returnedModel =
-                soundTriggerService.getSoundModel(new ParcelUuid(model.getUuid()));
-        assertEquals(model, returnedModel);
-    }
-
-    @SmallTest
-    public void testDeleteGenericSoundModel() throws Exception {
-        GenericSoundModel model = new_sound_model();
-
-        // Update sound model
-        soundTriggerService.updateSoundModel(model);
-        loadedModelUuids.add(model.getUuid());
-
-        // Delete sound model
-        soundTriggerService.deleteSoundModel(new ParcelUuid(model.getUuid()));
-        loadedModelUuids.remove(model.getUuid());
-
-        // Confirm it was deleted
-        GenericSoundModel returnedModel =
-                soundTriggerService.getSoundModel(new ParcelUuid(model.getUuid()));
-        assertEquals(null, returnedModel);
-    }
-
-    @LargeTest
-    public void testStartStopGenericSoundModel() throws Exception {
-        GenericSoundModel model = new_sound_model();
-
-        boolean captureTriggerAudio = true;
-        boolean allowMultipleTriggers = true;
-        RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
-                null, null);
-        TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback());
-
-        // Update and start sound model recognition
-        soundTriggerService.updateSoundModel(model);
-        loadedModelUuids.add(model.getUuid());
-        int r = soundTriggerService.startRecognition(new ParcelUuid(model.getUuid()), spyCallback,
-                config);
-        assertEquals("Could Not Start Recognition with code: " + r,
-                android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r);
-
-        // Stop recognition
-        r = soundTriggerService.stopRecognition(new ParcelUuid(model.getUuid()), spyCallback);
-        assertEquals("Could Not Stop Recognition with code: " + r,
-                android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r);
-    }
-
-    @LargeTest
-    public void testTriggerGenericSoundModel() throws Exception {
-        GenericSoundModel model = new_sound_model();
-
-        boolean captureTriggerAudio = true;
-        boolean allowMultipleTriggers = true;
-        RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
-                null, null);
-        TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback());
-
-        // Update and start sound model
-        soundTriggerService.updateSoundModel(model);
-        loadedModelUuids.add(model.getUuid());
-        soundTriggerService.startRecognition(new ParcelUuid(model.getUuid()), spyCallback, config);
-
-        // Send trigger to stub HAL
-        Socket socket = new Socket(InetAddress.getLocalHost(), 14035);
-        DataOutputStream out = new DataOutputStream(socket.getOutputStream());
-        out.writeBytes("trig " + model.getUuid().toString() + "\r\n");
-        out.flush();
-        socket.close();
-
-        // Verify trigger was received
-        verify(spyCallback, timeout(100)).onGenericSoundTriggerDetected(any());
-    }
-
-    /**
-     * Tests a more complicated pattern of loading, unloading, triggering, starting and stopping
-     * recognition. Intended to find unexpected errors that occur in unexpected states.
-     */
-    @LargeTest
-    public void testFuzzGenericSoundModel() throws Exception {
-        int numModels = 2;
-
-        final int STATUS_UNLOADED = 0;
-        final int STATUS_LOADED = 1;
-        final int STATUS_STARTED = 2;
-
-        class ModelInfo {
-            int status;
-            GenericSoundModel model;
-
-            public ModelInfo(GenericSoundModel model, int status) {
-                this.status = status;
-                this.model = model;
-            }
-        }
-
-        Random predictableRandom = new Random(100);
-
-        ArrayList modelInfos = new ArrayList<ModelInfo>();
-        for(int i=0; i<numModels; i++) {
-            // Create sound model
-            byte[] data = new byte[1024];
-            predictableRandom.nextBytes(data);
-            UUID modelUuid = UUID.randomUUID();
-            UUID mVendorUuid = UUID.randomUUID();
-            GenericSoundModel model = new GenericSoundModel(modelUuid, mVendorUuid, data);
-            ModelInfo modelInfo = new ModelInfo(model, STATUS_UNLOADED);
-            modelInfos.add(modelInfo);
-        }
-
-        boolean captureTriggerAudio = true;
-        boolean allowMultipleTriggers = true;
-        RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
-                null, null);
-        TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback());
-
-
-        int numOperationsToRun = 100;
-        for(int i=0; i<numOperationsToRun; i++) {
-            // Select a random model
-            int modelInfoIndex = predictableRandom.nextInt(modelInfos.size());
-            ModelInfo modelInfo = (ModelInfo) modelInfos.get(modelInfoIndex);
-
-            // Perform a random operation
-            int operation = predictableRandom.nextInt(5);
-
-            if (operation == 0 && modelInfo.status == STATUS_UNLOADED) {
-                // Update and start sound model
-                soundTriggerService.updateSoundModel(modelInfo.model);
-                loadedModelUuids.add(modelInfo.model.getUuid());
-                modelInfo.status = STATUS_LOADED;
-            } else if (operation == 1 && modelInfo.status == STATUS_LOADED) {
-                // Start the sound model
-                int r = soundTriggerService.startRecognition(new ParcelUuid(
-                                modelInfo.model.getUuid()),
-                        spyCallback, config);
-                assertEquals("Could Not Start Recognition with code: " + r,
-                        android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r);
-                modelInfo.status = STATUS_STARTED;
-            } else if (operation == 2 && modelInfo.status == STATUS_STARTED) {
-                // Send trigger to stub HAL
-                Socket socket = new Socket(InetAddress.getLocalHost(), 14035);
-                DataOutputStream out = new DataOutputStream(socket.getOutputStream());
-                out.writeBytes("trig " + modelInfo.model.getUuid() + "\r\n");
-                out.flush();
-                socket.close();
-
-                // Verify trigger was received
-                verify(spyCallback, timeout(100)).onGenericSoundTriggerDetected(any());
-                reset(spyCallback);
-            } else if (operation == 3 && modelInfo.status == STATUS_STARTED) {
-                // Stop recognition
-                int r = soundTriggerService.stopRecognition(new ParcelUuid(
-                                modelInfo.model.getUuid()),
-                        spyCallback);
-                assertEquals("Could Not Stop Recognition with code: " + r,
-                        android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r);
-                modelInfo.status = STATUS_LOADED;
-            } else if (operation == 4 && modelInfo.status != STATUS_UNLOADED) {
-                // Delete sound model
-                soundTriggerService.deleteSoundModel(new ParcelUuid(modelInfo.model.getUuid()));
-                loadedModelUuids.remove(modelInfo.model.getUuid());
-
-                // Confirm it was deleted
-                GenericSoundModel returnedModel = soundTriggerService.getSoundModel(
-                        new ParcelUuid(modelInfo.model.getUuid()));
-                assertEquals(null, returnedModel);
-                modelInfo.status = STATUS_UNLOADED;
-            }
-        }
-    }
-
-    public class TestRecognitionStatusCallback extends IRecognitionStatusCallback.Stub {
-        @Override
-        public void onGenericSoundTriggerDetected(GenericRecognitionEvent recognitionEvent) {
-        }
-
-        @Override
-        public void onKeyphraseDetected(KeyphraseRecognitionEvent recognitionEvent) {
-        }
-
-        @Override
-        public void onError(int status) {
-        }
-
-        @Override
-        public void onRecognitionPaused() {
-        }
-
-        @Override
-        public void onRecognitionResumed() {
-        }
-    }
-}
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
index ffde8c7..23efe54 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -55,6 +55,7 @@
         "cts-install-lib-host",
     ],
     data: [
+        ":StagedInstallInternalTestApp",
         ":apex.apexd_test",
         ":com.android.apex.apkrollback.test_v1",
         ":com.android.apex.apkrollback.test_v2",
diff --git a/tests/SystemMemoryTest/host/Android.bp b/tests/SystemMemoryTest/host/Android.bp
index 7974462..cc8bc45 100644
--- a/tests/SystemMemoryTest/host/Android.bp
+++ b/tests/SystemMemoryTest/host/Android.bp
@@ -26,4 +26,7 @@
     srcs: ["src/**/*.java"],
     libs: ["tradefed"],
     test_suites: ["general-tests"],
+    data: [
+        ":SystemMemoryTestDevice",
+    ],
 }