Merge "Create transition when converting an Activity to translucent" into udc-dev
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 051dde0..b732da2 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -127,6 +127,7 @@
     private BroadcastWaiter mBroadcastWaiter;
     private UserSwitchWaiter mUserSwitchWaiter;
     private String mUserSwitchTimeoutMs;
+    private String mDisableUserSwitchingDialogAnimations;
 
     private final BenchmarkRunner mRunner = new BenchmarkRunner();
     @Rule
@@ -153,16 +154,17 @@
             Log.w(TAG, "WARNING: Tests are being run from user " + mAm.getCurrentUser()
                     + " rather than the system user");
         }
-        mUserSwitchTimeoutMs = setSystemProperty("debug.usercontroller.user_switch_timeout_ms",
-                "100000");
-        if (TextUtils.isEmpty(mUserSwitchTimeoutMs)) {
-            mUserSwitchTimeoutMs = "invalid";
-        }
+        mUserSwitchTimeoutMs = setSystemProperty(
+                "debug.usercontroller.user_switch_timeout_ms", "100000");
+        mDisableUserSwitchingDialogAnimations = setSystemProperty(
+                "debug.usercontroller.disable_user_switching_dialog_animations", "true");
     }
 
     @After
     public void tearDown() throws Exception {
         setSystemProperty("debug.usercontroller.user_switch_timeout_ms", mUserSwitchTimeoutMs);
+        setSystemProperty("debug.usercontroller.disable_user_switching_dialog_animations",
+                mDisableUserSwitchingDialogAnimations);
         mBroadcastWaiter.close();
         mUserSwitchWaiter.close();
         for (int userId : mUsersToRemove) {
@@ -1538,7 +1540,7 @@
     private String setSystemProperty(String name, String value) throws Exception {
         final String oldValue = ShellHelper.runShellCommand("getprop " + name);
         assertEquals("", ShellHelper.runShellCommand("setprop " + name + " " + value));
-        return oldValue;
+        return TextUtils.firstNotEmpty(oldValue, "invalid");
     }
 
     private void waitForBroadcastIdle() {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 3312294..9e59ee4 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2547,7 +2547,7 @@
             .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
         new AppOpInfo.Builder(OP_TURN_SCREEN_ON, OPSTR_TURN_SCREEN_ON, "TURN_SCREEN_ON")
             .setPermission(Manifest.permission.TURN_SCREEN_ON)
-            .setDefaultMode(AppOpsManager.MODE_ERRORED).build(),
+            .setDefaultMode(AppOpsManager.MODE_DEFAULT).build(),
         new AppOpInfo.Builder(OP_GET_ACCOUNTS, OPSTR_GET_ACCOUNTS, "GET_ACCOUNTS")
             .setPermission(Manifest.permission.GET_ACCOUNTS)
             .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index d62e15a..3249b41 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -174,6 +174,9 @@
     ActivityTaskManager.RootTaskInfo getFocusedRootTaskInfo();
     Rect getTaskBounds(int taskId);
 
+    /** Focuses the top task on a display if it isn't already focused. Used for Recents. */
+    void focusTopTask(int displayId);
+
     void cancelRecentsAnimation(boolean restoreHomeRootTaskPosition);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES)")
     void updateLockTaskPackages(int userId, in String[] packages);
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 746dcb6..d8cedb8 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -44,6 +44,7 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlSerializer;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -246,6 +247,7 @@
     private boolean mBypassDnd;
     private int mLockscreenVisibility = DEFAULT_VISIBILITY;
     private Uri mSound = Settings.System.DEFAULT_NOTIFICATION_URI;
+    private boolean mSoundRestored = false;
     private boolean mLights;
     private int mLightColor = DEFAULT_LIGHT_COLOR;
     private long[] mVibration;
@@ -929,8 +931,9 @@
     /**
      * @hide
      */
-    public void populateFromXmlForRestore(XmlPullParser parser, Context context) {
-        populateFromXml(XmlUtils.makeTyped(parser), true, context);
+    public void populateFromXmlForRestore(XmlPullParser parser, boolean pkgInstalled,
+            Context context) {
+        populateFromXml(XmlUtils.makeTyped(parser), true, pkgInstalled, context);
     }
 
     /**
@@ -938,14 +941,14 @@
      */
     @SystemApi
     public void populateFromXml(XmlPullParser parser) {
-        populateFromXml(XmlUtils.makeTyped(parser), false, null);
+        populateFromXml(XmlUtils.makeTyped(parser), false, true, null);
     }
 
     /**
      * If {@param forRestore} is true, {@param Context} MUST be non-null.
      */
     private void populateFromXml(TypedXmlPullParser parser, boolean forRestore,
-            @Nullable Context context) {
+            boolean pkgInstalled, @Nullable Context context) {
         Preconditions.checkArgument(!forRestore || context != null,
                 "forRestore is true but got null context");
 
@@ -956,7 +959,8 @@
         setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
 
         Uri sound = safeUri(parser, ATT_SOUND);
-        setSound(forRestore ? restoreSoundUri(context, sound) : sound, safeAudioAttributes(parser));
+        setSound(forRestore ? restoreSoundUri(context, sound, pkgInstalled) : sound,
+                safeAudioAttributes(parser));
 
         enableLights(safeBool(parser, ATT_LIGHTS, false));
         setLightColor(safeInt(parser, ATT_LIGHT_COLOR, DEFAULT_LIGHT_COLOR));
@@ -978,8 +982,58 @@
         setImportantConversation(safeBool(parser, ATT_IMP_CONVERSATION, false));
     }
 
+    /**
+     * Returns whether the sound for this channel was successfully restored
+     *  from backup.
+     * @return false if the sound was not restored successfully. true otherwise (default value)
+     * @hide
+     */
+    public boolean isSoundRestored() {
+        return mSoundRestored;
+    }
+
     @Nullable
-    private Uri restoreSoundUri(Context context, @Nullable Uri uri) {
+    private Uri getCanonicalizedSoundUri(ContentResolver contentResolver, @NonNull Uri uri) {
+        if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(uri)) {
+            return uri;
+        }
+
+        if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) {
+            try {
+                contentResolver.getResourceId(uri);
+                return uri;
+            } catch (FileNotFoundException e) {
+                return null;
+            }
+        }
+
+        if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
+            return uri;
+        }
+
+        return contentResolver.canonicalize(uri);
+    }
+
+    @Nullable
+    private Uri getUncanonicalizedSoundUri(ContentResolver contentResolver, @NonNull Uri uri) {
+        if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(uri)
+                || ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())
+                || ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
+            return uri;
+        }
+        return contentResolver.uncanonicalize(uri);
+    }
+
+    /**
+     * Restore/validate sound Uri from backup
+     * @param context The Context
+     * @param uri The sound Uri to restore
+     * @param pkgInstalled If the parent package is installed
+     * @return restored and validated Uri
+     * @hide
+     */
+    @Nullable
+    public Uri restoreSoundUri(Context context, @Nullable Uri uri, boolean pkgInstalled) {
         if (uri == null || Uri.EMPTY.equals(uri)) {
             return null;
         }
@@ -991,12 +1045,22 @@
         // the uri and in the case of not having the resource we end up with the default - better
         // than broken. As a side effect we'll canonicalize already canonicalized uris, this is fine
         // according to the docs because canonicalize method has to handle canonical uris as well.
-        Uri canonicalizedUri = contentResolver.canonicalize(uri);
+        Uri canonicalizedUri = getCanonicalizedSoundUri(contentResolver, uri);
         if (canonicalizedUri == null) {
-            // We got a null because the uri in the backup does not exist here, so we return default
-            return Settings.System.DEFAULT_NOTIFICATION_URI;
+            // Uri failed to restore with package installed
+            if (!mSoundRestored && pkgInstalled) {
+                mSoundRestored = true;
+                // We got a null because the uri in the backup does not exist here, so we return
+                // default
+                return Settings.System.DEFAULT_NOTIFICATION_URI;
+            } else {
+                // Flag as unrestored and try again later (on package install)
+                mSoundRestored = false;
+                return uri;
+            }
         }
-        return contentResolver.uncanonicalize(canonicalizedUri);
+        mSoundRestored = true;
+        return getUncanonicalizedSoundUri(contentResolver, canonicalizedUri);
     }
 
     /**
@@ -1019,7 +1083,7 @@
         if (sound == null || Uri.EMPTY.equals(sound)) {
             return null;
         }
-        Uri canonicalSound = context.getContentResolver().canonicalize(sound);
+        Uri canonicalSound = getCanonicalizedSoundUri(context.getContentResolver(), sound);
         if (canonicalSound == null) {
             // The content provider does not support canonical uris so we backup the default
             return Settings.System.DEFAULT_NOTIFICATION_URI;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a8a2ad1..7b68357 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -8394,8 +8394,7 @@
      * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_DISABLE_CAMERA} to be able to call this method; if it has
-     * not, a security exception will be thrown, or the caller must hold the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CAMERA}.
+     * not, a security exception will be thrown.
      * <p>
      * <b>Note</b>, this policy type is deprecated for legacy device admins since
      * {@link android.os.Build.VERSION_CODES#Q}. On Android
@@ -8411,8 +8410,7 @@
                      the caller is not a device admin
      * @param disabled Whether or not the camera should be disabled.
      * @throws SecurityException if {@code admin} is not an active administrator or does not use
-     *             {@link DeviceAdminInfo#USES_POLICY_DISABLE_CAMERA} and the caller does not hold
-     *             the permisisons {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CAMERA}.
+     *             {@link DeviceAdminInfo#USES_POLICY_DISABLE_CAMERA}.
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_CAMERA, conditional = true)
     public void setCameraDisabled(@Nullable ComponentName admin, boolean disabled) {
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 30fd77c..de66f05 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -3554,6 +3554,18 @@
         }
 
         /**
+         * @return the path to the validated base APK for this session, which may point at an
+         * APK inside the session (when the session defines the base), or it may
+         * point at the existing base APK (when adding splits to an existing app).
+         *
+         * @hide
+         */
+        @RequiresPermission(Manifest.permission.READ_INSTALLED_SESSION_PATHS)
+        public @Nullable String getResolvedBaseApkPath() {
+            return resolvedBaseCodePath;
+        }
+
+        /**
          * Get the value set in {@link SessionParams#setGrantedRuntimePermissions(String[])}.
          *
          * @hide
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
index 7701125..875550a 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
@@ -46,6 +46,7 @@
 public class CameraExtensionJpegProcessor implements ICaptureProcessorImpl {
     public final static String TAG = "CameraExtensionJpeg";
     private final static int JPEG_QUEUE_SIZE = 1;
+    private final static int JPEG_APP_SEGMENT_SIZE = 64 * 1024;
 
     private final Handler mHandler;
     private final HandlerThread mHandlerThread;
@@ -243,9 +244,10 @@
     private void initializePipeline() throws RemoteException {
         if ((mFormat != -1) && (mOutputSurface != null) && (mResolution != null) &&
                 (mYuvReader == null)) {
-            // Jpeg/blobs are expected to be configured with (w*h)x1
+            // Jpeg/blobs are expected to be configured with (w*h)x1.5 + 64k Jpeg APP1 segment
             mOutputWriter = ImageWriter.newInstance(mOutputSurface, 1 /*maxImages*/,
-                    ImageFormat.JPEG, mResolution.width * mResolution.height, 1);
+                    ImageFormat.JPEG,
+                    (mResolution.width * mResolution.height * 3)/2 + JPEG_APP_SEGMENT_SIZE, 1);
             mYuvReader = ImageReader.newInstance(mResolution.width, mResolution.height, mFormat,
                     JPEG_QUEUE_SIZE);
             mYuvReader.setOnImageAvailableListener(
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index a52e3d49..2c31e32 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -213,6 +213,13 @@
     }
 
     /**
+     * Switch the system to use ANGLE as the default GLES driver.
+     */
+    public void toggleAngleAsSystemDriver(boolean enabled) {
+        nativeToggleAngleAsSystemDriver(enabled);
+    }
+
+    /**
      * Query to determine if the Game Mode has enabled ANGLE.
      */
     private boolean isAngleEnabledByGameMode(Context context, String packageName) {
@@ -992,6 +999,7 @@
             String appPackage, boolean angleIsSystemDriver, String legacyDriverName);
     private static native boolean getShouldUseAngle(String packageName);
     private static native boolean setInjectLayersPrSetDumpable();
+    private static native void nativeToggleAngleAsSystemDriver(boolean enabled);
 
     /**
      * Hint for GraphicsEnvironment that an activity is launching on the process.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2efb265..3487b01 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4677,22 +4677,16 @@
                 "display_color_mode_vendor_hint";
 
         /**
-         * The user selected min refresh rate in frames per second.
-         *
-         * If this isn't set, 0 will be used.
+         * Whether or not the peak refresh rate should be forced. 0=no, 1=yes
          * @hide
          */
-        @Readable
-        public static final String MIN_REFRESH_RATE = "min_refresh_rate";
+        public static final String FORCE_PEAK_REFRESH_RATE = "force_peak_refresh_rate";
 
         /**
-         * The user selected peak refresh rate in frames per second.
-         *
-         * If this isn't set, the system falls back to a device specific default.
+         * Whether or not the peak refresh rate should be used for some content. 0=no, 1=yes
          * @hide
          */
-        @Readable
-        public static final String PEAK_REFRESH_RATE = "peak_refresh_rate";
+        public static final String SMOOTH_DISPLAY = "smooth_display";
 
         /**
          * The amount of time in milliseconds before the device goes to sleep or begins
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
index 82571db..e9bb28c 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -84,6 +84,19 @@
          *
          * @param keepDreaming True if the current dream should continue when undocking.
          */
-        void onKeepDreamingWhenUnpluggingChanged(boolean keepDreaming);
+        default void onKeepDreamingWhenUnpluggingChanged(boolean keepDreaming) {
+        }
+
+        /**
+         * Called when dreaming has started.
+         */
+        default void onDreamingStarted() {
+        }
+
+        /**
+         * Called when dreaming has stopped.
+         */
+        default void onDreamingStopped() {
+        }
     }
 }
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 24c96ea..91c350a 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -1334,13 +1334,7 @@
     @Override
     public void destroy() {
         synchronized (mLock) {
-            if (mAvailability == STATE_KEYPHRASE_ENROLLED) {
-                try {
-                    stopRecognition();
-                } catch (Exception e) {
-                    Log.i(TAG, "failed to stopRecognition in destroy", e);
-                }
-            }
+            detachSessionLocked();
 
             mAvailability = STATE_INVALID;
             mIsAvailabilityOverriddenByTestApi = false;
@@ -1349,6 +1343,17 @@
         super.destroy();
     }
 
+    private void detachSessionLocked() {
+        try {
+            if (DBG) Slog.d(TAG, "detachSessionLocked() " + mSoundTriggerSession);
+            if (mSoundTriggerSession != null) {
+                mSoundTriggerSession.detach();
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 77f3b1d..dd4f964 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.view.inputmethod.InputMethodManager;
+import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -305,6 +306,9 @@
         mImm.startStylusHandwriting(view);
         mState.mHasInitiatedHandwriting = true;
         mState.mShouldInitHandwriting = false;
+        if (view instanceof TextView) {
+            ((TextView) view).hideHint();
+        }
     }
 
     /**
@@ -323,6 +327,9 @@
                 mState.mHasInitiatedHandwriting = true;
                 mState.mShouldInitHandwriting = false;
             }
+            if (view instanceof TextView) {
+                ((TextView) view).hideHint();
+            }
             return true;
         }
         return false;
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index bc6a3b5..99deac4 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -220,6 +220,7 @@
             long newParentNativeObject);
     private static native void nativeSetBuffer(long transactionObj, long nativeObject,
             HardwareBuffer buffer, long fencePtr, Consumer<SyncFence> releaseCallback);
+    private static native void nativeUnsetBuffer(long transactionObj, long nativeObject);
     private static native void nativeSetBufferTransform(long transactionObj, long nativeObject,
             int transform);
     private static native void nativeSetDataSpace(long transactionObj, long nativeObject,
@@ -3664,6 +3665,22 @@
         }
 
         /**
+         * Unsets the buffer for the SurfaceControl in the current Transaction. This will not clear
+         * the buffer being rendered, but resets the buffer state in the Transaction only. The call
+         * will also invoke the release callback.
+         *
+         * Note, this call is different from passing a null buffer to
+         * {@link SurfaceControl.Transaction#setBuffer} which will release the last displayed
+         * buffer.
+         *
+         * @hide
+         */
+        public Transaction unsetBuffer(SurfaceControl sc) {
+            nativeUnsetBuffer(mNativeObject, sc.mNativeObject);
+            return this;
+        }
+
+        /**
          * Updates the HardwareBuffer displayed for the SurfaceControl.
          *
          * Note that the buffer must be allocated with {@link HardwareBuffer#USAGE_COMPOSER_OVERLAY}
@@ -3682,7 +3699,8 @@
          * until all presentation fences have signaled, ensuring the transaction remains consistent.
          *
          * @param sc The SurfaceControl to update
-         * @param buffer The buffer to be displayed
+         * @param buffer The buffer to be displayed. Pass in a null buffer to release the last
+         * displayed buffer.
          * @param fence The presentation fence. If null or invalid, this is equivalent to
          *              {@link #setBuffer(SurfaceControl, HardwareBuffer)}
          * @return this
@@ -3846,14 +3864,14 @@
          *                           100 nits and a max display brightness of 200 nits, this should
          *                           be set to 2.0f.
          *
-         *                           Default value is 1.0f.
+         *                           <p>Default value is 1.0f.
          *
-         *                           Transfer functions that encode their own brightness ranges,
+         *                           <p>Transfer functions that encode their own brightness ranges,
          *                           such as HLG or PQ, should also set this to 1.0f and instead
          *                           communicate extended content brightness information via
          *                           metadata such as CTA861_3 or SMPTE2086.
          *
-         *                           Must be finite && >= 1.0f
+         *                           <p>Must be finite && >= 1.0f
          *
          * @param desiredRatio The desired hdr/sdr ratio. This can be used to communicate the max
          *                     desired brightness range. This is similar to the "max luminance"
@@ -3862,13 +3880,17 @@
          *                     may not be able to, or may choose not to, deliver the
          *                     requested range.
          *
-         *                     If unspecified, the system will attempt to provide the best range
-         *                     it can for the given ambient conditions & device state. However,
-         *                     voluntarily reducing the requested range can help improve battery
-         *                     life as well as can improve quality by ensuring greater bit depth
-         *                     is allocated to the luminance range in use.
+         *                     <p>While requesting a large desired ratio will result in the most
+         *                     dynamic range, voluntarily reducing the requested range can help
+         *                     improve battery life as well as can improve quality by ensuring
+         *                     greater bit depth is allocated to the luminance range in use.
          *
-         *                     Must be finite && >= 1.0f
+         *                     <p>Default value is 1.0f and indicates that extended range brightness
+         *                     is not being used, so the resulting SDR or HDR behavior will be
+         *                     determined entirely by the dataspace being used (ie, typically SDR
+         *                     however PQ or HLG transfer functions will still result in HDR)
+         *
+         *                     <p>Must be finite && >= 1.0f
          * @return this
          **/
         public @NonNull Transaction setExtendedRangeBrightness(@NonNull SurfaceControl sc,
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 6bd9538..c0ac04c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -647,11 +647,18 @@
     boolean mForceNextWindowRelayout;
     CountDownLatch mWindowDrawCountDown;
 
-    // Whether we have used applyTransactionOnDraw to schedule an RT
-    // frame callback consuming a passed in transaction. In this case
-    // we also need to schedule a commit callback so we can observe
-    // if the draw was skipped, and the BBQ pending transactions.
+    /**
+     * Value to indicate whether someone has called {@link #applyTransactionOnDraw}before the
+     * traversal. This is used to determine whether a RT frame callback needs to be registered to
+     * merge the transaction with the next frame. The value is cleared after the VRI has run a
+     * traversal pass.
+     */
     boolean mHasPendingTransactions;
+    /**
+     * The combined transactions passed in from {@link #applyTransactionOnDraw}
+     */
+    private Transaction mPendingTransaction = new Transaction();
+
 
     boolean mIsDrawing;
     int mLastSystemUiVisibility;
@@ -4548,9 +4555,13 @@
     }
 
     private void registerCallbackForPendingTransactions() {
+        Transaction t = new Transaction();
+        t.merge(mPendingTransaction);
+
         registerRtFrameCallback(new FrameDrawingCallback() {
             @Override
             public HardwareRenderer.FrameCommitCallback onFrameDraw(int syncResult, long frame) {
+                mergeWithNextTransaction(t, frame);
                 if ((syncResult
                         & (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
                     mBlastBufferQueue.applyPendingTransactions(frame);
@@ -8780,6 +8791,9 @@
             mActiveSurfaceSyncGroup.markSyncReady();
             mActiveSurfaceSyncGroup = null;
         }
+        if (mHasPendingTransactions) {
+            mPendingTransaction.apply();
+        }
         WindowManagerGlobal.getInstance().doRemoveView(this);
     }
 
@@ -11114,12 +11128,11 @@
         } else {
             // Copy and clear the passed in transaction for thread safety. The new transaction is
             // accessed on the render thread.
-            var localTransaction = new Transaction();
-            localTransaction.merge(t);
+            mPendingTransaction.merge(t);
             mHasPendingTransactions = true;
-            registerRtFrameCallback(frame -> {
-                mergeWithNextTransaction(localTransaction, frame);
-            });
+            // Schedule the traversal to ensure there's an attempt to draw a frame and apply the
+            // pending transactions. This is also where the registerFrameCallback will be scheduled.
+            scheduleTraversals();
         }
         return true;
     }
@@ -11260,6 +11273,10 @@
         if (DEBUG_BLAST) {
             Log.d(mTag, "registerCallbacksForSync syncBuffer=" + syncBuffer);
         }
+
+        Transaction t = new Transaction();
+        t.merge(mPendingTransaction);
+
         mAttachInfo.mThreadedRenderer.registerRtFrameCallback(new FrameDrawingCallback() {
             @Override
             public void onFrameDraw(long frame) {
@@ -11273,6 +11290,7 @@
                                     + frame + ".");
                 }
 
+                mergeWithNextTransaction(t, frame);
                 // If the syncResults are SYNC_LOST_SURFACE_REWARD_IF_FOUND or
                 // SYNC_CONTEXT_IS_STOPPED it means nothing will draw. There's no need to set up
                 // any blast sync or commit callback, and the code should directly call
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index 13ac329..fa0052c 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -840,6 +840,7 @@
         mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
         mTitle = null;
         mTransitionTime = 0;
+        mLocales = LocaleList.getEmptyLocaleList();
     }
 
     /**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3fbb505..34fe935 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -806,6 +806,7 @@
     private CharSequence mHint;
     @UnsupportedAppUsage
     private Layout mHintLayout;
+    private boolean mHideHint;
 
     private MovementMethod mMovement;
 
@@ -7180,6 +7181,8 @@
         sendOnTextChanged(text, 0, oldlen, textLength);
         onTextChanged(text, 0, oldlen, textLength);
 
+        mHideHint = false;
+
         if (a11yTextChangeType == AccessibilityUtils.TEXT) {
             notifyViewAccessibilityStateChangedIfNeeded(
                     AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
@@ -7338,6 +7341,7 @@
     }
 
     private void setHintInternal(CharSequence hint) {
+        mHideHint = false;
         mHint = TextUtils.stringOrSpannedString(hint);
 
         if (mLayout != null) {
@@ -7379,6 +7383,19 @@
     }
 
     /**
+     * Temporarily hides the hint text until the text is modified, or the hint text is modified, or
+     * the view gains or loses focus.
+     *
+     * @hide
+     */
+    public void hideHint() {
+        if (isShowingHint()) {
+            mHideHint = true;
+            invalidate();
+        }
+    }
+
+    /**
      * Returns if the text is constrained to a single horizontally scrolling line ignoring new
      * line characters instead of letting it wrap onto multiple lines.
      *
@@ -8974,7 +8991,7 @@
 
         Layout layout = mLayout;
 
-        if (mHint != null && mText.length() == 0) {
+        if (mHint != null && !mHideHint && mText.length() == 0) {
             if (mHintTextColor != null) {
                 color = mCurHintTextColor;
             }
@@ -11293,7 +11310,7 @@
     }
 
     private boolean isShowingHint() {
-        return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
+        return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint) && !mHideHint;
     }
 
     /**
@@ -12437,6 +12454,7 @@
         sendOnTextChanged(buffer, start, before, after);
         onTextChanged(buffer, start, before, after);
 
+        mHideHint = false;
         clearGesturePreviewHighlight();
     }
 
@@ -12577,6 +12595,8 @@
             return;
         }
 
+        mHideHint = false;
+
         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
 
         if (focused) {
diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index d4ff794..57cc38c 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -57,6 +57,8 @@
     public static final int INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS = 5;
     /** value for INVOCATION_TYPE_KEY: long press on physical power button */
     public static final int INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS = 6;
+    /** value for INVOCATION_TYPE_KEY: press on physcial assistant button */
+    public static final int INVOCATION_TYPE_ASSIST_BUTTON = 7;
 
     private final Context mContext;
     private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
diff --git a/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl b/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl
index 1ccc71a..23de50c 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl
@@ -94,4 +94,9 @@
      */
     @nullable SoundTrigger.ModelParamRange queryParameter(int keyphraseId,
             in ModelParams modelParam);
+    /**
+     * Invalidates the sound trigger session and clears any associated resources. Subsequent calls
+     * to this object will throw IllegalStateException.
+     */
+    void detach();
 }
diff --git a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
new file mode 100644
index 0000000..39d8380
--- /dev/null
+++ b/core/java/com/android/internal/display/RefreshRateSettingsUtils.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.internal.display;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.Display;
+
+/**
+ * Constants and utility methods for refresh rate settings.
+ */
+public class RefreshRateSettingsUtils {
+
+    private static final String TAG = "RefreshRateSettingsUtils";
+
+    public static final float DEFAULT_REFRESH_RATE = 60f;
+
+    /**
+     * Find the highest refresh rate among all the modes of the default display.
+     * @param context The context
+     * @return The highest refresh rate
+     */
+    public static float findHighestRefreshRateForDefaultDisplay(Context context) {
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+        final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
+
+        if (display == null) {
+            Log.w(TAG, "No valid default display device");
+            return DEFAULT_REFRESH_RATE;
+        }
+
+        float maxRefreshRate = DEFAULT_REFRESH_RATE;
+        for (Display.Mode mode : display.getSupportedModes()) {
+            if (Math.round(mode.getRefreshRate()) > maxRefreshRate) {
+                maxRefreshRate = mode.getRefreshRate();
+            }
+        }
+        return maxRefreshRate;
+    }
+
+    /**
+     * Get the min refresh rate which is determined by
+     * {@link Settings.System.FORCE_PEAK_REFRESH_RATE}.
+     * @param context The context
+     * @return The min refresh rate
+     */
+    public static float getMinRefreshRate(Context context) {
+        final ContentResolver cr = context.getContentResolver();
+        int forcePeakRefreshRateSetting = Settings.System.getIntForUser(cr,
+                Settings.System.FORCE_PEAK_REFRESH_RATE, -1, cr.getUserId());
+        return forcePeakRefreshRateSetting == 1
+                ? findHighestRefreshRateForDefaultDisplay(context)
+                : 0;
+    }
+
+    /**
+     * Get the peak refresh rate which is determined by {@link Settings.System.SMOOTH_DISPLAY}.
+     * @param context The context
+     * @param defaultPeakRefreshRate The refresh rate to return if the setting doesn't have a value
+     * @return The peak refresh rate
+     */
+    public static float getPeakRefreshRate(Context context, float defaultPeakRefreshRate) {
+        final ContentResolver cr = context.getContentResolver();
+        int smoothDisplaySetting = Settings.System.getIntForUser(cr,
+                Settings.System.SMOOTH_DISPLAY, -1, cr.getUserId());
+        switch (smoothDisplaySetting) {
+            case 0:
+                return DEFAULT_REFRESH_RATE;
+            case 1:
+                return findHighestRefreshRateForDefaultDisplay(context);
+            default:
+                return defaultPeakRefreshRate;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 0a0785e..80f540c 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -36,6 +36,7 @@
 import android.os.Handler;
 import android.os.Trace;
 import android.text.TextUtils;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Choreographer;
@@ -43,7 +44,9 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.JankData.JankType;
 import android.view.ThreadedRenderer;
+import android.view.View;
 import android.view.ViewRootImpl;
+import android.view.WindowCallbacks;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
@@ -686,6 +689,14 @@
         }
     }
 
+    ThreadedRendererWrapper getThreadedRenderer() {
+        return mRendererWrapper;
+    }
+
+    ViewRootWrapper getViewRoot() {
+        return mViewRoot;
+    }
+
     private boolean shouldTriggerPerfetto(int missedFramesCount, int maxFrameTimeNanos) {
         boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1
                 && missedFramesCount >= mTraceThresholdMissedFrames;
@@ -798,6 +809,28 @@
         public SurfaceControl getSurfaceControl() {
             return mViewRoot.getSurfaceControl();
         }
+
+        void requestInvalidateRootRenderNode() {
+            mViewRoot.requestInvalidateRootRenderNode();
+        }
+
+        void addWindowCallbacks(WindowCallbacks windowCallbacks) {
+            mViewRoot.addWindowCallbacks(windowCallbacks);
+        }
+
+        void removeWindowCallbacks(WindowCallbacks windowCallbacks) {
+            mViewRoot.removeWindowCallbacks(windowCallbacks);
+        }
+
+        View getView() {
+            return mViewRoot.getView();
+        }
+
+        int dipToPx(int dip) {
+            final DisplayMetrics displayMetrics =
+                    mViewRoot.mContext.getResources().getDisplayMetrics();
+            return (int) (displayMetrics.density * dip + 0.5f);
+        }
     }
 
     public static class SurfaceControlWrapper {
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 4b9e77e..c769fb9 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -97,6 +97,7 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
 
 import android.Manifest;
+import android.annotation.ColorInt;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -104,6 +105,7 @@
 import android.annotation.WorkerThread;
 import android.app.ActivityThread;
 import android.content.Context;
+import android.graphics.Color;
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -143,6 +145,14 @@
  * adb shell device_config put interaction_jank_monitor enabled true
  * adb shell device_config put interaction_jank_monitor sampling_interval 1
  *
+ * On debuggable builds, an overlay can be used to display the name of the
+ * currently running cuj using:
+ *
+ * adb shell device_config put interaction_jank_monitor debug_overlay_enabled true
+ *
+ * NOTE: The overlay will interfere with metrics, so it should only be used
+ * for understanding which UI events correspeond to which CUJs.
+ *
  * @hide
  */
 public class InteractionJankMonitor {
@@ -159,6 +169,7 @@
             "trace_threshold_missed_frames";
     private static final String SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY =
             "trace_threshold_frame_time_millis";
+    private static final String SETTINGS_DEBUG_OVERLAY_ENABLED_KEY = "debug_overlay_enabled";
     /** Default to being enabled on debug builds. */
     private static final boolean DEFAULT_ENABLED = Build.IS_DEBUGGABLE;
     /** Default to collecting data for all CUJs. */
@@ -166,6 +177,7 @@
     /** Default to triggering trace if 3 frames are missed OR a frame takes at least 64ms */
     private static final int DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES = 3;
     private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64;
+    private static final boolean DEFAULT_DEBUG_OVERLAY_ENABLED = false;
 
     @VisibleForTesting
     public static final int MAX_LENGTH_OF_CUJ_NAME = 80;
@@ -343,6 +355,9 @@
     private final HandlerThread mWorker;
     private final DisplayResolutionTracker mDisplayResolutionTracker;
     private final Object mLock = new Object();
+    private @ColorInt int mDebugBgColor = Color.CYAN;
+    private double mDebugYOffset = 0.1;
+    private InteractionMonitorDebugOverlay mDebugOverlay;
 
     private volatile boolean mEnabled = DEFAULT_ENABLED;
     private int mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
@@ -533,7 +548,7 @@
         if (needRemoveTasks(action, session)) {
             getTracker(session.getCuj()).getHandler().runWithScissors(() -> {
                 removeTimeout(session.getCuj());
-                removeTracker(session.getCuj());
+                removeTracker(session.getCuj(), session.getReason());
             }, EXECUTOR_TASK_TIMEOUT);
         }
     }
@@ -699,7 +714,7 @@
         if (tracker == null) return false;
         // if the end call doesn't return true, another thread is handling end of the cuj.
         if (tracker.end(REASON_END_NORMAL)) {
-            removeTracker(cujType);
+            removeTracker(cujType, REASON_END_NORMAL);
         }
         return true;
     }
@@ -750,7 +765,7 @@
         if (tracker == null) return false;
         // if the cancel call doesn't return true, another thread is handling cancel of the cuj.
         if (tracker.cancel(reason)) {
-            removeTracker(cujType);
+            removeTracker(cujType, reason);
         }
         return true;
     }
@@ -758,6 +773,13 @@
     private void putTracker(@CujType int cuj, @NonNull FrameTracker tracker) {
         synchronized (mLock) {
             mRunningTrackers.put(cuj, tracker);
+            if (mDebugOverlay != null) {
+                mDebugOverlay.onTrackerAdded(cuj, tracker.getViewRoot());
+            }
+            if (DEBUG) {
+                Log.d(TAG, "Added tracker for " + getNameOfCuj(cuj)
+                        + ". mRunningTrackers=" + listNamesOfCujs(mRunningTrackers));
+            }
         }
     }
 
@@ -767,9 +789,16 @@
         }
     }
 
-    private void removeTracker(@CujType int cuj) {
+    private void removeTracker(@CujType int cuj, int reason) {
         synchronized (mLock) {
             mRunningTrackers.remove(cuj);
+            if (mDebugOverlay != null) {
+                mDebugOverlay.onTrackerRemoved(cuj, reason, mRunningTrackers);
+            }
+            if (DEBUG) {
+                Log.d(TAG, "Removed tracker for " + getNameOfCuj(cuj)
+                        + ". mRunningTrackers=" + listNamesOfCujs(mRunningTrackers));
+            }
         }
     }
 
@@ -782,6 +811,16 @@
         mTraceThresholdFrameTimeMillis = properties.getInt(
                 SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY,
                 DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS);
+        // Never allow the debug overlay to be used on user builds
+        boolean debugOverlayEnabled = Build.IS_DEBUGGABLE && properties.getBoolean(
+                SETTINGS_DEBUG_OVERLAY_ENABLED_KEY,
+                DEFAULT_DEBUG_OVERLAY_ENABLED);
+        if (debugOverlayEnabled && mDebugOverlay == null) {
+            mDebugOverlay = new InteractionMonitorDebugOverlay(mDebugBgColor, mDebugYOffset);
+        } else if (!debugOverlayEnabled && mDebugOverlay != null) {
+            mDebugOverlay.dispose();
+            mDebugOverlay = null;
+        }
         // The memory visibility is powered by the volatile field, mEnabled.
         mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
     }
@@ -822,6 +861,39 @@
     }
 
     /**
+     * Configures the debug overlay used for displaying interaction names on the screen while they
+     * occur.
+     *
+     * @param bgColor the background color of the box used to display the CUJ names
+     * @param yOffset number between 0 and 1 to indicate where the top of the box should be relative
+     *                to the height of the screen
+     */
+    public void configDebugOverlay(@ColorInt int bgColor, double yOffset) {
+        mDebugBgColor = bgColor;
+        mDebugYOffset = yOffset;
+    }
+
+    /**
+     * A helper method for getting a string representation of all running CUJs. For example,
+     * "(LOCKSCREEN_TRANSITION_FROM_AOD, IME_INSETS_ANIMATION)"
+     */
+    private static String listNamesOfCujs(SparseArray<FrameTracker> trackers) {
+        if (!DEBUG) {
+            return null;
+        }
+        StringBuilder sb = new StringBuilder();
+        sb.append('(');
+        for (int i = 0; i < trackers.size(); i++) {
+            sb.append(getNameOfCuj(trackers.keyAt(i)));
+            if (i < trackers.size() - 1) {
+                sb.append(", ");
+            }
+        }
+        sb.append(')');
+        return sb.toString();
+    }
+
+    /**
      * A helper method to translate CUJ type to CUJ name.
      *
      * @param cujType the cuj type defined in this file
diff --git a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
new file mode 100644
index 0000000..99b9f2f
--- /dev/null
+++ b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
@@ -0,0 +1,240 @@
+/*
+ * 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.internal.jank;
+
+import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
+
+import android.annotation.ColorInt;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.os.Trace;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.WindowCallbacks;
+
+import com.android.internal.jank.FrameTracker.Reasons;
+import com.android.internal.jank.InteractionJankMonitor.CujType;
+
+/**
+ * An overlay that uses WindowCallbacks to draw the names of all running CUJs to the window
+ * associated with one of the CUJs being tracked. There's no guarantee which window it will
+ * draw to. NOTE: sometimes the CUJ names will remain displayed on the screen longer than they
+ * are actually running.
+ * <p>
+ * CUJ names will be drawn as follows:
+ * <ul>
+ * <li> Normal text indicates the CUJ is currently running
+ * <li> Grey text indicates the CUJ ended normally and is no longer running
+ * <li> Red text with a strikethrough indicates the CUJ was canceled or ended abnormally
+ * </ul>
+ */
+class InteractionMonitorDebugOverlay implements WindowCallbacks {
+    private static final int REASON_STILL_RUNNING = -1000;
+    // Sparse array where the key in the CUJ and the value is the session status, or null if
+    // it's currently running
+    private final SparseIntArray mRunningCujs = new SparseIntArray();
+    private FrameTracker.ViewRootWrapper mViewRoot = null;
+    private final Paint mDebugPaint;
+    private final Paint.FontMetrics mDebugFontMetrics;
+    // Used to display the overlay in a different color and position for different processes.
+    // Otherwise, two overlays will overlap and be difficult to read.
+    private final int mBgColor;
+    private final double mYOffset;
+    private final String mPackageName;
+
+    InteractionMonitorDebugOverlay(@ColorInt int bgColor, double yOffset) {
+        mBgColor = bgColor;
+        mYOffset = yOffset;
+        mDebugPaint = new Paint();
+        mDebugPaint.setAntiAlias(false);
+        mDebugFontMetrics = new Paint.FontMetrics();
+        final Context context = ActivityThread.currentApplication();
+        mPackageName = context.getPackageName();
+    }
+
+    void dispose() {
+        if (mViewRoot != null) {
+            mViewRoot.removeWindowCallbacks(this);
+            forceRedraw();
+        }
+        mViewRoot = null;
+    }
+
+    private boolean attachViewRootIfNeeded(FrameTracker.ViewRootWrapper viewRoot) {
+        if (mViewRoot == null && viewRoot != null) {
+            mViewRoot = viewRoot;
+            viewRoot.addWindowCallbacks(this);
+            forceRedraw();
+            return true;
+        }
+        return false;
+    }
+
+    private float getWidthOfLongestCujName(int cujFontSize) {
+        mDebugPaint.setTextSize(cujFontSize);
+        float maxLength = 0;
+        for (int i = 0; i < mRunningCujs.size(); i++) {
+            String cujName = InteractionJankMonitor.getNameOfCuj(mRunningCujs.keyAt(i));
+            float textLength = mDebugPaint.measureText(cujName);
+            if (textLength > maxLength) {
+                maxLength = textLength;
+            }
+        }
+        return maxLength;
+    }
+
+    private float getTextHeight(int textSize) {
+        mDebugPaint.setTextSize(textSize);
+        mDebugPaint.getFontMetrics(mDebugFontMetrics);
+        return mDebugFontMetrics.descent - mDebugFontMetrics.ascent;
+    }
+
+    private int dipToPx(int dip) {
+        if (mViewRoot != null) {
+            return mViewRoot.dipToPx(dip);
+        } else {
+            return dip;
+        }
+    }
+
+    private void forceRedraw() {
+        if (mViewRoot != null) {
+            mViewRoot.requestInvalidateRootRenderNode();
+            mViewRoot.getView().invalidate();
+        }
+    }
+
+    void onTrackerRemoved(@CujType int removedCuj, @Reasons int reason,
+                          SparseArray<FrameTracker> runningTrackers) {
+        mRunningCujs.put(removedCuj, reason);
+        // If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended
+        if (mRunningCujs.indexOfValue(REASON_STILL_RUNNING) < 0) {
+            mRunningCujs.clear();
+            dispose();
+        } else {
+            boolean needsNewViewRoot = true;
+            if (mViewRoot != null) {
+                // Check to see if this viewroot is still associated with one of the running
+                // trackers
+                for (int i = 0; i < runningTrackers.size(); i++) {
+                    if (mViewRoot.equals(
+                            runningTrackers.valueAt(i).getViewRoot())) {
+                        needsNewViewRoot = false;
+                        break;
+                    }
+                }
+            }
+            if (needsNewViewRoot) {
+                dispose();
+                for (int i = 0; i < runningTrackers.size(); i++) {
+                    if (attachViewRootIfNeeded(runningTrackers.valueAt(i).getViewRoot())) {
+                        break;
+                    }
+                }
+            } else {
+                forceRedraw();
+            }
+        }
+    }
+
+    void onTrackerAdded(@CujType int addedCuj, FrameTracker.ViewRootWrapper viewRoot) {
+        // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
+        // is still running
+        mRunningCujs.put(addedCuj, REASON_STILL_RUNNING);
+        attachViewRootIfNeeded(viewRoot);
+        forceRedraw();
+    }
+
+    @Override
+    public void onWindowSizeIsChanging(Rect newBounds, boolean fullscreen,
+                                       Rect systemInsets, Rect stableInsets) {
+    }
+
+    @Override
+    public void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen,
+                                        Rect systemInsets, Rect stableInsets) {
+    }
+
+    @Override
+    public void onWindowDragResizeEnd() {
+    }
+
+    @Override
+    public boolean onContentDrawn(int offsetX, int offsetY, int sizeX, int sizeY) {
+        return false;
+    }
+
+    @Override
+    public void onRequestDraw(boolean reportNextDraw) {
+    }
+
+    @Override
+    public void onPostDraw(RecordingCanvas canvas) {
+        Trace.beginSection("InteractionJankMonitor#drawDebug");
+        final int padding = dipToPx(5);
+        final int h = canvas.getHeight();
+        final int w = canvas.getWidth();
+        // Draw sysui CUjs near the bottom of the screen so they don't overlap with the shade,
+        // and draw launcher CUJs near the top of the screen so they don't overlap with gestures
+        final int dy = (int) (h * mYOffset);
+        int packageNameFontSize = dipToPx(12);
+        int cujFontSize = dipToPx(18);
+        final float cujNameTextHeight = getTextHeight(cujFontSize);
+        final float packageNameTextHeight = getTextHeight(packageNameFontSize);
+        float maxLength = getWidthOfLongestCujName(cujFontSize);
+
+        final int dx = (int) ((w - maxLength) / 2f);
+        canvas.translate(dx, dy);
+        // Draw background rectangle for displaying the text showing the CUJ name
+        mDebugPaint.setColor(mBgColor);
+        canvas.drawRect(
+                -padding * 2, // more padding on top so we can draw the package name
+                -padding,
+                padding * 2 + maxLength,
+                padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(),
+                mDebugPaint);
+        mDebugPaint.setTextSize(packageNameFontSize);
+        mDebugPaint.setColor(Color.BLACK);
+        mDebugPaint.setStrikeThruText(false);
+        canvas.translate(0, packageNameTextHeight);
+        canvas.drawText("package:" + mPackageName, 0, 0, mDebugPaint);
+        mDebugPaint.setTextSize(cujFontSize);
+        // Draw text for CUJ names
+        for (int i = 0; i < mRunningCujs.size(); i++) {
+            int status = mRunningCujs.valueAt(i);
+            if (status == REASON_STILL_RUNNING) {
+                mDebugPaint.setColor(Color.BLACK);
+                mDebugPaint.setStrikeThruText(false);
+            } else if (status == REASON_END_NORMAL) {
+                mDebugPaint.setColor(Color.GRAY);
+                mDebugPaint.setStrikeThruText(false);
+            } else {
+                // Cancelled, or otherwise ended for a bad reason
+                mDebugPaint.setColor(Color.RED);
+                mDebugPaint.setStrikeThruText(true);
+            }
+            String cujName = InteractionJankMonitor.getNameOfCuj(mRunningCujs.keyAt(i));
+            canvas.translate(0, cujNameTextHeight);
+            canvas.drawText(cujName, 0, 0, mDebugPaint);
+        }
+        Trace.endSection();
+    }
+}
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 55aa711..4474d4ca 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -52,7 +52,7 @@
     return env;
 }
 
-  struct {
+struct {
     jmethodID onTransactionHang;
 } gTransactionHangCallback;
 
@@ -72,12 +72,14 @@
     }
 
     void onTransactionHang(const std::string& reason) {
-        if (mTransactionHangObject) {
-            JNIEnv* env = getenv(mVm);
-            ScopedLocalRef<jstring> jReason(env, env->NewStringUTF(reason.c_str()));
-            getenv(mVm)->CallVoidMethod(mTransactionHangObject,
-                                        gTransactionHangCallback.onTransactionHang, jReason.get());
+        if (!mTransactionHangObject) {
+            return;
         }
+        JNIEnv* env = getenv(mVm);
+        ScopedLocalRef<jstring> jReason(env, env->NewStringUTF(reason.c_str()));
+        getenv(mVm)->CallVoidMethod(mTransactionHangObject,
+                                    gTransactionHangCallback.onTransactionHang, jReason.get());
+        DieIfException(env, "Uncaught exception in TransactionHangCallback.");
     }
 
 private:
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index 78e2d31..d9152d6 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -122,6 +122,10 @@
     android::GraphicsEnv::getInstance().hintActivityLaunch();
 }
 
+void nativeToggleAngleAsSystemDriver_native(JNIEnv* env, jobject clazz, jboolean enabled) {
+    android::GraphicsEnv::getInstance().nativeToggleAngleAsSystemDriver(enabled);
+}
+
 const JNINativeMethod g_methods[] = {
         {"isDebuggable", "()Z", reinterpret_cast<void*>(isDebuggable_native)},
         {"setDriverPathAndSphalLibraries", "(Ljava/lang/String;Ljava/lang/String;)V",
@@ -143,6 +147,8 @@
         {"setDebugLayersGLES", "(Ljava/lang/String;)V",
          reinterpret_cast<void*>(setDebugLayersGLES_native)},
         {"hintActivityLaunch", "()V", reinterpret_cast<void*>(hintActivityLaunch_native)},
+        {"nativeToggleAngleAsSystemDriver", "(Z)V",
+         reinterpret_cast<void*>(nativeToggleAngleAsSystemDriver_native)},
 };
 
 const char* const kGraphicsEnvironmentName = "android/os/GraphicsEnvironment";
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index e42c6f1..193099b 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -285,6 +285,7 @@
         JNIEnv* env = getenv();
         env->CallVoidMethod(mTransactionCommittedListenerObject,
                             gTransactionCommittedListenerClassInfo.onTransactionCommitted);
+        DieIfException(env, "Uncaught exception in TransactionCommittedListener.");
     }
 
     static void transactionCallbackThunk(void* context, nsecs_t /*latchTime*/,
@@ -325,6 +326,7 @@
     binder::Status onWindowInfosReported() override {
         JNIEnv* env = getenv();
         env->CallVoidMethod(mListener, gRunnableClassInfo.run);
+        DieIfException(env, "Uncaught exception in WindowInfosReportedListener.");
         return binder::Status::ok();
     }
 
@@ -356,6 +358,7 @@
         env->CallVoidMethod(mTrustedPresentationCallback,
                             gTrustedPresentationCallbackClassInfo.onTrustedPresentationChanged,
                             inTrustedPresentationState);
+        DieIfException(env, "Uncaught exception in TrustedPresentationCallback.");
     }
 
     void addCallbackRef(const sp<SurfaceComposerClient::PresentationCallbackRAII>& callbackRef) {
@@ -613,6 +616,12 @@
                            genReleaseCallback(env, releaseCallback));
 }
 
+static void nativeUnsetBuffer(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    transaction->unsetBuffer(ctrl);
+}
+
 static void nativeSetBufferTransform(JNIEnv* env, jclass clazz, jlong transactionObj,
                                      jlong nativeObject, jint transform) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -2195,6 +2204,8 @@
             (void*)nativeSetGeometry },
     {"nativeSetBuffer", "(JJLandroid/hardware/HardwareBuffer;JLjava/util/function/Consumer;)V",
             (void*)nativeSetBuffer },
+    {"nativeUnsetBuffer", "(JJ)V", (void*)nativeUnsetBuffer },
+
     {"nativeSetBufferTransform", "(JJI)V", (void*) nativeSetBufferTransform},
     {"nativeSetDataSpace", "(JJI)V",
             (void*)nativeSetDataSpace },
diff --git a/core/jni/core_jni_helpers.h b/core/jni/core_jni_helpers.h
index b85a425..210dc89 100644
--- a/core/jni/core_jni_helpers.h
+++ b/core/jni/core_jni_helpers.h
@@ -134,6 +134,15 @@
     return env;
 }
 
+static inline void DieIfException(JNIEnv* env, const char* message) {
+    if (env->ExceptionCheck()) {
+        jnihelp::ExpandableString summary;
+        jnihelp::ExpandableStringInitialize(&summary);
+        jnihelp::GetStackTraceOrSummary(env, nullptr, &summary);
+        LOG_ALWAYS_FATAL("%s\n%s", message, summary.data);
+    }
+}
+
 }  // namespace android
 
 #endif  // CORE_JNI_HELPERS
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e216f88..31220b4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2542,7 +2542,7 @@
     <permission android:name="android.permission.TURN_SCREEN_ON"
         android:label="@string/permlab_turnScreenOn"
         android:description="@string/permdesc_turnScreenOn"
-        android:protectionLevel="normal|appop" />
+        android:protectionLevel="signature|privileged|appop" />
 
     <!-- ==================================================== -->
     <!-- Permissions related to changing audio settings   -->
@@ -5418,6 +5418,15 @@
     <permission android:name="android.permission.INSTALL_DPC_PACKAGES"
                 android:protectionLevel="signature|role" />
 
+    <!-- Allows an application to read resolved paths to the APKs (Base and any splits)
+         of a session based install.
+         <p>Not for use by third-party applications.
+         @hide
+    -->
+    <permission android:name="android.permission.READ_INSTALLED_SESSION_PATHS"
+                android:protectionLevel="signature|installer" />
+    <uses-permission android:name="android.permission.READ_INSTALLED_SESSION_PATHS" />
+
     <!-- Allows an application to use System Data Loaders.
          <p>Not for use by third-party applications.
          @hide
@@ -7629,6 +7638,13 @@
     <permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE"
                 android:protectionLevel="signature|module" />
 
+    <!-- @hide Allows the settings app to access GPU service APIs".
+        <p>Not for use by third-party applications.
+        <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.ACCESS_GPU_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- @hide Allows an application to get type of any provider uri.
          <p>Not for use by third-party applications.
          <p>Protection level: signature
diff --git a/core/res/res/drawable/loading_spinner.xml b/core/res/res/drawable/loading_spinner.xml
new file mode 100644
index 0000000..49603d8
--- /dev/null
+++ b/core/res/res/drawable/loading_spinner.xml
@@ -0,0 +1,55 @@
+<!-- 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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+                 xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector android:height="230dp" android:width="230dp" android:viewportHeight="230"
+                android:viewportWidth="230">
+            <group android:name="_R_G">
+                <group android:name="_R_G_L_0_G" android:translateX="100.621"
+                       android:translateY="102.621">
+                    <path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#ffffff"
+                          android:strokeLineCap="round" android:strokeLineJoin="round"
+                          android:strokeWidth="8" android:strokeAlpha="1" android:trimPathStart="0"
+                          android:trimPathEnd="0" android:trimPathOffset="0"
+                          android:pathData=" M14.38 -93.62 C72.88,-93.62 120.38,-46.12 120.38,12.38 C120.38,70.88 72.88,118.38 14.38,118.38 C-44.12,118.38 -91.62,70.88 -91.62,12.38 C-91.62,-46.12 -44.12,-93.62 14.38,-93.62c "/>
+                </group>
+            </group>
+            <group android:name="time_group"/>
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="trimPathEnd" android:duration="350"
+                                android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+                                android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.4,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="translateX" android:duration="517"
+                                android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+                                android:valueType="floatType"/>
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/core/res/res/layout/user_switching_dialog.xml b/core/res/res/layout/user_switching_dialog.xml
index 2e041f5..496179a 100644
--- a/core/res/res/layout/user_switching_dialog.xml
+++ b/core/res/res/layout/user_switching_dialog.xml
@@ -14,17 +14,48 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:id="@+id/content"
+             android:background="?attr/colorBackground"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent">
 
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/message"
-        style="?attr/textAppearanceListItem"
-        android:background="?attr/colorSurface"
+    <LinearLayout
         android:layout_width="wrap_content"
-        android:layout_height="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
         android:gravity="center"
-        android:drawablePadding="12dp"
-        android:drawableTint="?attr/textColorPrimary"
-        android:paddingStart="?attr/dialogPreferredPadding"
-        android:paddingEnd="?attr/dialogPreferredPadding"
-        android:paddingTop="24dp"
-        android:paddingBottom="24dp" />
+        android:orientation="vertical"
+        android:paddingBottom="77dp">
+
+        <RelativeLayout
+            android:layout_width="242dp"
+            android:layout_height="242dp"
+            android:layout_gravity="center">
+
+            <ImageView
+                android:id="@+id/icon"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_margin="26dp" />
+
+            <ImageView
+                android:id="@+id/progress_circular"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_margin="6dp"
+                android:src="@drawable/loading_spinner" />
+
+        </RelativeLayout>
+
+        <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+                  android:id="@+id/message"
+                  style="?attr/textAppearanceListItem"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:textSize="20sp"
+                  android:textAlignment="center"
+                  android:drawableTint="?attr/textColorPrimary" />
+
+    </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5544701..c5f7ea6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1917,7 +1917,8 @@
     <string name="config_defaultNetworkRecommendationProviderPackage" translatable="false"></string>
 
     <!-- The package name of the default search selector app. Must be granted the POST_NOTIFICATIONS
-         permission.
+         permission, and notifications from this app must be given the notification flag
+         FLAG_NO_DISMISS if the notification requests FLAG_ONGOING_EVENT.
     -->
     <string name="config_defaultSearchSelectorPackageName" translatable="false"></string>
 
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index a57a051..fd74185 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -133,6 +133,10 @@
     <string name="config_pointing_ui_package" translatable="false"></string>
     <java-symbol type="string" name="config_pointing_ui_package" />
 
+    <!-- Telephony pointing UI class name to be launched. -->
+    <string name="config_pointing_ui_class" translatable="false"></string>
+    <java-symbol type="string" name="config_pointing_ui_class" />
+
     <!-- Telephony resends received satellite datagram to listener
          if ack is not received within this timeout -->
     <integer name="config_timeout_to_receive_delivered_ack_millis">300000</integer>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 3ea1592..34b4c51 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -91,8 +91,8 @@
         ":BstatsTestApp",
         ":BinderDeathRecipientHelperApp1",
         ":BinderDeathRecipientHelperApp2",
+        ":com.android.cts.helpers.aosp",
     ],
-    required: ["com.android.cts.helpers.aosp"],
 }
 
 // Rules to copy all the test apks to the intermediate raw resource directory
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 8ae6381..c0125af 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -23,14 +23,20 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.Instrumentation;
 import android.content.Context;
+import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.view.HandwritingInitiator;
@@ -38,7 +44,9 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.ViewGroup;
 import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -47,6 +55,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 /**
  * Tests for {@link HandwritingInitiator}
@@ -543,6 +552,111 @@
         assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView1);
     }
 
+    @Test
+    public void startHandwriting_hidesHint() {
+        EditText editText =
+                new EditText(InstrumentationRegistry.getInstrumentation().getTargetContext());
+        editText.setHint("hint");
+        editText.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+
+        verifyEditTextDrawsText(editText, "hint");
+
+        mHandwritingInitiator.onTouchEvent(createStylusEvent(ACTION_DOWN, 0, 0, 0));
+        mHandwritingInitiator.startHandwriting(editText);
+
+        verifyEditTextDrawsText(editText, null);
+    }
+
+    @Test
+    public void startHandwriting_clearFocus_restoresHint() {
+        EditText editText =
+                new EditText(InstrumentationRegistry.getInstrumentation().getTargetContext());
+        editText.setHint("hint");
+        editText.setLayoutParams(new ViewGroup.LayoutParams(1024, 1024));
+        editText.requestFocus();
+
+        verifyEditTextDrawsText(editText, "hint");
+
+        mHandwritingInitiator.onTouchEvent(createStylusEvent(ACTION_DOWN, 0, 0, 0));
+        mHandwritingInitiator.startHandwriting(editText);
+
+        verifyEditTextDrawsText(editText, null);
+
+        editText.clearFocus();
+
+        verifyEditTextDrawsText(editText, "hint");
+    }
+
+    @Test
+    public void startHandwriting_setHint_restoresHint() {
+        EditText editText =
+                new EditText(InstrumentationRegistry.getInstrumentation().getTargetContext());
+        editText.setHint("hint");
+        editText.setLayoutParams(new ViewGroup.LayoutParams(1024, 1024));
+
+        verifyEditTextDrawsText(editText, "hint");
+
+        mHandwritingInitiator.onTouchEvent(createStylusEvent(ACTION_DOWN, 0, 0, 0));
+        mHandwritingInitiator.startHandwriting(editText);
+
+        verifyEditTextDrawsText(editText, null);
+
+        editText.setHint("new hint");
+
+        verifyEditTextDrawsText(editText, "new hint");
+    }
+
+    @Test
+    public void startHandwriting_setText_restoresHint() {
+        EditText editText =
+                new EditText(InstrumentationRegistry.getInstrumentation().getTargetContext());
+        editText.setHint("hint");
+        editText.setLayoutParams(new ViewGroup.LayoutParams(1024, 1024));
+
+        verifyEditTextDrawsText(editText, "hint");
+
+        mHandwritingInitiator.onTouchEvent(createStylusEvent(ACTION_DOWN, 0, 0, 0));
+        mHandwritingInitiator.startHandwriting(editText);
+
+        verifyEditTextDrawsText(editText, null);
+
+        editText.setText("a");
+        editText.setText("");
+
+        verifyEditTextDrawsText(editText, "hint");
+    }
+
+    private void verifyEditTextDrawsText(EditText editText, String text) {
+        editText.measure(
+                View.MeasureSpec.makeMeasureSpec(1024, View.MeasureSpec.AT_MOST),
+                View.MeasureSpec.makeMeasureSpec(1024, View.MeasureSpec.AT_MOST));
+        Canvas canvas = prepareMockCanvas(editText);
+        editText.draw(canvas);
+        if (text != null) {
+            ArgumentCaptor<CharSequence> textCaptor = ArgumentCaptor.forClass(CharSequence.class);
+            verify(canvas).drawText(
+                    textCaptor.capture(), anyInt(), anyInt(), anyFloat(), anyFloat(), any());
+            assertThat(textCaptor.getValue().toString()).isEqualTo(text);
+        } else {
+            verify(canvas, never()).drawText(
+                    any(CharSequence.class), anyInt(), anyInt(), anyFloat(), anyFloat(), any());
+        }
+    }
+
+    private Canvas prepareMockCanvas(View view) {
+        Canvas canvas = mock(Canvas.class);
+        when(canvas.getClipBounds(any())).thenAnswer(invocation -> {
+            Rect outRect = invocation.getArgument(0);
+            outRect.top = 0;
+            outRect.left = 0;
+            outRect.right = view.getMeasuredWidth();
+            outRect.bottom = view.getMeasuredHeight();
+            return true;
+        });
+        return canvas;
+    }
+
     private MotionEvent createStylusEvent(int action, int x, int y, long eventTime) {
         MotionEvent.PointerProperties[] properties = MotionEvent.PointerProperties.createArray(1);
         properties[0].toolType = MotionEvent.TOOL_TYPE_STYLUS;
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index 03d366e6..57a1376 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -46,6 +46,10 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.os.Handler;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
 
@@ -57,6 +61,7 @@
 import com.android.internal.R;
 import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -73,8 +78,14 @@
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityShortcutChooserActivityTest {
     private static final String ONE_HANDED_MODE = "One-Handed mode";
+    private static final String DENY_LABEL = "Deny";
+    private static final String EDIT_LABEL = "Edit shortcuts";
+    private static final String LIST_TITLE_LABEL = "Choose features to use";
     private static final String TEST_LABEL = "TEST_LABEL";
     private static final ComponentName TEST_COMPONENT_NAME = new ComponentName("package", "class");
+    private static final long UI_TIMEOUT_MS = 1000;
+    private UiDevice mDevice;
+    private ActivityScenario<TestAccessibilityShortcutChooserActivity> mScenario;
     private TestAccessibilityShortcutChooserActivity mActivity;
 
     @Rule
@@ -92,6 +103,8 @@
 
     @Before
     public void setUp() throws Exception {
+        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mDevice.wakeUp();
         when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo);
         mResolveInfo.serviceInfo = mServiceInfo;
         mServiceInfo.applicationInfo = mApplicationInfo;
@@ -102,27 +115,36 @@
         when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
                 anyString(), anyInt(), anyInt())).thenReturn(true);
         TestAccessibilityShortcutChooserActivity.setupForTesting(mAccessibilityManagerService);
+        mScenario = ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
+        mScenario.onActivity(activity -> mActivity = activity);
+        mScenario.moveToState(Lifecycle.State.CREATED);
+        mScenario.moveToState(Lifecycle.State.STARTED);
+        mScenario.moveToState(Lifecycle.State.RESUMED);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    @After
+    public void cleanUp() {
+        mScenario.moveToState(Lifecycle.State.DESTROYED);
     }
 
     @Test
     public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist() {
-        final ActivityScenario<TestAccessibilityShortcutChooserActivity> scenario =
-                ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
-        scenario.moveToState(Lifecycle.State.CREATED);
-        scenario.moveToState(Lifecycle.State.STARTED);
-        scenario.moveToState(Lifecycle.State.RESUMED);
+        openShortcutsList();
 
-        onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot(
-                isDialog()).check(matches(isDisplayed()));
-        onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click());
-        onView(withText(TEST_LABEL)).perform(scrollTo(), doubleClick());
-        onView(withId(R.id.accessibility_permission_enable_deny_button)).perform(scrollTo(),
-                click());
+        // Performing the double-click is flaky so retry if needed.
+        for (int attempt = 1; attempt <= 2; attempt++) {
+            onView(withText(TEST_LABEL)).perform(scrollTo(), doubleClick());
+            if (mDevice.wait(Until.hasObject(By.text(DENY_LABEL)), UI_TIMEOUT_MS)) {
+                break;
+            }
+        }
+
+        onView(withText(DENY_LABEL)).perform(scrollTo(), click());
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
         onView(withId(R.id.accessibility_permissionDialog_title)).inRoot(isDialog()).check(
                 doesNotExist());
-        scenario.moveToState(Lifecycle.State.DESTROYED);
     }
 
     @Test
@@ -130,60 +152,42 @@
             throws Exception {
         when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
                 eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt())).thenReturn(false);
-        final ActivityScenario<TestAccessibilityShortcutChooserActivity> scenario =
-                ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
-        scenario.onActivity(activity -> mActivity = activity);
-        scenario.moveToState(Lifecycle.State.CREATED);
-        scenario.moveToState(Lifecycle.State.STARTED);
-        scenario.moveToState(Lifecycle.State.RESUMED);
-
-        onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot(
-                isDialog()).check(matches(isDisplayed()));
-        onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click());
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        openShortcutsList();
 
         onView(withText(TEST_LABEL)).perform(scrollTo(), click());
+
         verify(mAccessibilityManagerService).sendRestrictedDialogIntent(
                 eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt());
-        scenario.moveToState(Lifecycle.State.DESTROYED);
     }
 
     @Test
     public void popEditShortcutMenuList_oneHandedModeEnabled_shouldBeInListView() {
         TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true);
-        final ActivityScenario<TestAccessibilityShortcutChooserActivity> scenario =
-                ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
-        scenario.moveToState(Lifecycle.State.CREATED);
-        scenario.moveToState(Lifecycle.State.STARTED);
-        scenario.moveToState(Lifecycle.State.RESUMED);
+        openShortcutsList();
 
-        onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot(
-                isDialog()).check(matches(isDisplayed()));
-        onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click());
         onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp());
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        mDevice.wait(Until.hasObject(By.text(ONE_HANDED_MODE)), UI_TIMEOUT_MS);
 
         onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(matches(isDisplayed()));
-        scenario.moveToState(Lifecycle.State.DESTROYED);
     }
 
     @Test
     public void popEditShortcutMenuList_oneHandedModeDisabled_shouldNotBeInListView() {
         TestUtils.setOneHandedModeEnabled(this, /* enabled= */ false);
-        final ActivityScenario<TestAccessibilityShortcutChooserActivity> scenario =
-                ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
-        scenario.moveToState(Lifecycle.State.CREATED);
-        scenario.moveToState(Lifecycle.State.STARTED);
-        scenario.moveToState(Lifecycle.State.RESUMED);
+        openShortcutsList();
 
-        onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot(
-                isDialog()).check(matches(isDisplayed()));
-        onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click());
         onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp());
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
         onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(doesNotExist());
-        scenario.moveToState(Lifecycle.State.DESTROYED);
+    }
+
+    private void openShortcutsList() {
+        UiObject2 editButton = mDevice.findObject(By.text(EDIT_LABEL));
+        if (editButton != null) {
+            editButton.click();
+        }
+        mDevice.wait(Until.hasObject(By.textStartsWith(LIST_TITLE_LABEL)), UI_TIMEOUT_MS);
     }
 
     /**
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 549ac58..596f351 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -307,6 +307,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "-1828118576": {
+      "message": "SyncGroup %d: Started %sfor listener: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_SYNC_ENGINE",
+      "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
+    },
     "-1824578273": {
       "message": "Reporting new frame to %s: %s",
       "level": "VERBOSE",
@@ -1489,6 +1495,12 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-741766551": {
+      "message": "Content Recording: Ignoring session on invalid virtual display",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecordingController.java"
+    },
     "-732715767": {
       "message": "Unable to retrieve window container to start recording for display %d",
       "level": "VERBOSE",
@@ -2893,12 +2905,6 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "550717438": {
-      "message": "SyncGroup %d: Started for listener: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_SYNC_ENGINE",
-      "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
-    },
     "556758086": {
       "message": "Applying new update lock state '%s' for %s",
       "level": "DEBUG",
@@ -4129,6 +4135,12 @@
       "group": "WM_DEBUG_ANIM",
       "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
     },
+    "1820873642": {
+      "message": "SyncGroup %d:  Unfinished dependencies: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_SYNC_ENGINE",
+      "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
+    },
     "1822314934": {
       "message": "Expected target rootTask=%s to restored behind rootTask=%s but it is behind rootTask=%s",
       "level": "WARN",
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 56c3068..302c72e 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -42,7 +42,6 @@
 import android.media.MediaCodecList;
 import android.net.Uri;
 import android.os.Build;
-import android.os.SystemProperties;
 import android.os.Trace;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -2069,47 +2068,67 @@
     }
 
     private static boolean sIsP010SupportedForAV1 = false;
-    private static boolean sIsP010SupportedForAV1Initialized = false;
-    private static final Object sIsP010SupportedForAV1Lock = new Object();
+    private static boolean sIsP010SupportedForHEVC = false;
+    private static boolean sIsP010SupportedFlagsInitialized = false;
+    private static final Object sIsP010SupportedLock = new Object();
 
     /**
      * Checks if the device supports decoding 10-bit AV1.
      */
     @SuppressWarnings("AndroidFrameworkCompatChange")  // This is not an app-visible API.
     private static boolean isP010SupportedForAV1() {
-        synchronized (sIsP010SupportedForAV1Lock) {
-            if (sIsP010SupportedForAV1Initialized) {
+        synchronized (sIsP010SupportedLock) {
+            if (sIsP010SupportedFlagsInitialized) {
                 return sIsP010SupportedForAV1;
             }
+            checkP010SupportforAV1HEVC();
+            return sIsP010SupportedForAV1;
+        }
+    }
 
-            sIsP010SupportedForAV1Initialized = true;
-            return sIsP010SupportedForAV1 = isP010SupportedforMime("video/av01");
+    /**
+     * Checks if the device supports decoding 10-bit HEVC.
+     * This method is called by JNI.
+     */
+    @SuppressWarnings("unused")
+    private static boolean isP010SupportedForHEVC() {
+        synchronized (sIsP010SupportedLock) {
+            if (sIsP010SupportedFlagsInitialized) {
+                return sIsP010SupportedForHEVC;
+            }
+            checkP010SupportforAV1HEVC();
+            return sIsP010SupportedForHEVC;
         }
     }
 
     /**
      * Checks if the device supports decoding 10-bit for the given mime type.
      */
-    private static boolean isP010SupportedforMime(String mime) {
+    private static void checkP010SupportforAV1HEVC() {
         MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
         for (MediaCodecInfo mediaCodecInfo : codecList.getCodecInfos()) {
             if (mediaCodecInfo.isEncoder()) {
                 continue;
             }
             for (String mediaType : mediaCodecInfo.getSupportedTypes()) {
-                if (mediaType.equalsIgnoreCase(mime)) {
+                if (mediaType.equalsIgnoreCase("video/av01")
+                        || mediaType.equalsIgnoreCase("video/hevc")) {
                     MediaCodecInfo.CodecCapabilities codecCapabilities =
                         mediaCodecInfo.getCapabilitiesForType(mediaType);
                     for (int i = 0; i < codecCapabilities.colorFormats.length; ++i) {
                         if (codecCapabilities.colorFormats[i]
                             == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010) {
-                            return true;
+                            if (mediaType.equalsIgnoreCase("video/av01")) {
+                                sIsP010SupportedForAV1 = true;
+                            } else {
+                                sIsP010SupportedForHEVC = true;
+                            }
                         }
                     }
                 }
             }
         }
-        return false;
+        sIsP010SupportedFlagsInitialized = true;
     }
 
     /**
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 88373e8..cb3b64c 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -261,8 +261,9 @@
     /**
      * Compressed JPEG format that includes an embedded recovery map.
      *
-     * <p>JPEG compressed main image along with XMP embedded recovery map
-     * following ISO TBD.</p>
+     * <p>JPEG compressed main image along with embedded recovery map following the
+     * <a href="https://developer.android.com/guide/topics/media/hdr-image-format">Ultra HDR
+     * Image format specification</a>.</p>
      */
     public static final int JPEG_R = 0x1005;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 60111aa..f8f8897 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1068,6 +1068,7 @@
                     // We need to be Z ordered on top in order for alpha animations to work.
                     mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true);
                     mExpandedBubble.getExpandedView().setAnimating(true);
+                    mExpandedViewContainer.setVisibility(VISIBLE);
                 }
             }
 
@@ -3116,7 +3117,7 @@
                     mAnimatingOutBubbleBuffer.getColorSpace());
 
             mAnimatingOutSurfaceView.setAlpha(1f);
-            mExpandedViewContainer.setVisibility(View.GONE);
+            mExpandedViewContainer.setVisibility(View.INVISIBLE);
 
             mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
                 post(() -> {
@@ -3146,9 +3147,6 @@
         int[] paddings = mPositioner.getExpandedViewContainerPadding(
                 mStackAnimationController.isStackOnLeftSide(), isOverflowExpanded);
         mExpandedViewContainer.setPadding(paddings[0], paddings[1], paddings[2], paddings[3]);
-        if (mIsExpansionAnimating) {
-            mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
-        }
         if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
             PointF p = mPositioner.getExpandedBubbleXY(getBubbleIndex(mExpandedBubble),
                     getState());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index cbd544c..e732a03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -16,9 +16,12 @@
 
 package com.android.wm.shell.desktopmode;
 
+import android.graphics.Region;
+
 import com.android.wm.shell.common.annotations.ExternalThread;
 
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Interface to interact with desktop mode feature in shell.
@@ -32,7 +35,16 @@
      * @param listener the listener to add.
      * @param callbackExecutor the executor to call the listener on.
      */
-    void addListener(DesktopModeTaskRepository.VisibleTasksListener listener,
+    void addVisibleTasksListener(DesktopModeTaskRepository.VisibleTasksListener listener,
             Executor callbackExecutor);
 
+    /**
+     * Adds a consumer to listen for Desktop task corner changes. This is used for gesture
+     * exclusion. The SparseArray contains a list of four corner resize handles mapped to each
+     * desktop task's taskId. The resize handle Rects are stored in the following order:
+     * left-top, left-bottom, right-top, right-bottom.
+     */
+    default void addDesktopGestureExclusionRegionListener(Consumer<Region> listener,
+            Executor callbackExecutor) { }
+
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 2bdbde1..86ea725 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -34,6 +34,7 @@
 import android.app.WindowConfiguration;
 import android.content.Context;
 import android.database.ContentObserver;
+import android.graphics.Region;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
@@ -69,6 +70,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Handles windowing changes when desktop mode system setting changes
@@ -149,11 +151,21 @@
      * @param listener the listener to add.
      * @param callbackExecutor the executor to call the listener on.
      */
-    public void addListener(DesktopModeTaskRepository.VisibleTasksListener listener,
+    public void addVisibleTasksListener(DesktopModeTaskRepository.VisibleTasksListener listener,
             Executor callbackExecutor) {
         mDesktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor);
     }
 
+    /**
+     * Adds a listener to track changes to corners of desktop mode tasks.
+     * @param listener the listener to add.
+     * @param callbackExecutor the executor to call the listener on.
+     */
+    public void addTaskCornerListener(Consumer<Region> listener,
+            Executor callbackExecutor) {
+        mDesktopModeTaskRepository.setTaskCornerListener(listener, callbackExecutor);
+    }
+
     @VisibleForTesting
     void updateDesktopModeActive(boolean active) {
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active);
@@ -312,6 +324,23 @@
     }
 
     /**
+     * Update corner rects stored for a specific task
+     * @param taskId task to update
+     * @param taskCorners task's new corner handles
+     */
+    public void onTaskCornersChanged(int taskId, Region taskCorners) {
+        mDesktopModeTaskRepository.updateTaskCorners(taskId, taskCorners);
+    }
+
+    /**
+     * Remove corners saved for a task. Likely used due to task closure.
+     * @param taskId task to remove
+     */
+    public void removeCornersForTask(int taskId) {
+        mDesktopModeTaskRepository.removeTaskCorners(taskId);
+    }
+
+    /**
      * Moves a specifc task to the front.
      * @param taskInfo the task to show in front.
      */
@@ -426,10 +455,19 @@
     private final class DesktopModeImpl implements DesktopMode {
 
         @Override
-        public void addListener(DesktopModeTaskRepository.VisibleTasksListener listener,
+        public void addVisibleTasksListener(
+                DesktopModeTaskRepository.VisibleTasksListener listener,
                 Executor callbackExecutor) {
             mMainExecutor.execute(() -> {
-                DesktopModeController.this.addListener(listener, callbackExecutor);
+                DesktopModeController.this.addVisibleTasksListener(listener, callbackExecutor);
+            });
+        }
+
+        @Override
+        public void addDesktopGestureExclusionRegionListener(Consumer<Region> listener,
+                Executor callbackExecutor) {
+            mMainExecutor.execute(() -> {
+                DesktopModeController.this.addTaskCornerListener(listener, callbackExecutor);
             });
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 47342c9..12f8ea2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -16,9 +16,13 @@
 
 package com.android.wm.shell.desktopmode
 
+import android.graphics.Region
 import android.util.ArrayMap
 import android.util.ArraySet
+import android.util.SparseArray
+import androidx.core.util.valueIterator
 import java.util.concurrent.Executor
+import java.util.function.Consumer
 
 /**
  * Keeps track of task data related to desktop mode.
@@ -38,6 +42,10 @@
     private val activeTasksListeners = ArraySet<ActiveTasksListener>()
     // Track visible tasks separately because a task may be part of the desktop but not visible.
     private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
+    // Track corners of desktop tasks, used to determine gesture exclusion
+    private val desktopCorners = SparseArray<Region>()
+    private var desktopGestureExclusionListener: Consumer<Region>? = null
+    private var desktopGestureExclusionExecutor: Executor? = null
 
     /**
      * Add a [ActiveTasksListener] to be notified of updates to active tasks in the repository.
@@ -56,6 +64,28 @@
     }
 
     /**
+     * Add a Consumer which will inform other classes of changes to corners for all Desktop tasks.
+     */
+    fun setTaskCornerListener(cornersListener: Consumer<Region>, executor: Executor) {
+        desktopGestureExclusionListener = cornersListener
+        desktopGestureExclusionExecutor = executor
+        executor.execute {
+            desktopGestureExclusionListener?.accept(calculateDesktopExclusionRegion())
+        }
+    }
+
+    /**
+     * Create a new merged region representative of all corners in all desktop tasks.
+     */
+    private fun calculateDesktopExclusionRegion(): Region {
+        val desktopCornersRegion = Region()
+        desktopCorners.valueIterator().forEach { taskCorners ->
+            desktopCornersRegion.op(taskCorners, Region.Op.UNION)
+        }
+        return desktopCornersRegion
+    }
+
+    /**
      * Remove a previously registered [ActiveTasksListener]
      */
     fun removeActiveTasksListener(activeTasksListener: ActiveTasksListener) {
@@ -167,6 +197,28 @@
     }
 
     /**
+     * Updates the active desktop corners; if desktopCorners has been accepted by
+     * desktopCornersListener, it will be updated in the appropriate classes.
+     */
+    fun updateTaskCorners(taskId: Int, taskCorners: Region) {
+        desktopCorners.put(taskId, taskCorners)
+        desktopGestureExclusionExecutor?.execute {
+            desktopGestureExclusionListener?.accept(calculateDesktopExclusionRegion())
+        }
+    }
+
+    /**
+     * Removes the active desktop corners for the specified task; if desktopCorners has been
+     * accepted by desktopCornersListener, it will be updated in the appropriate classes.
+     */
+    fun removeTaskCorners(taskId: Int) {
+        desktopCorners.delete(taskId)
+        desktopGestureExclusionExecutor?.execute {
+            desktopGestureExclusionListener?.accept(calculateDesktopExclusionRegion())
+        }
+    }
+
+    /**
      * Defines interface for classes that can listen to changes for active tasks in desktop mode.
      */
     interface ActiveTasksListener {
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 0400963..0d56023 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
@@ -27,6 +27,7 @@
 import android.content.Context
 import android.graphics.Point
 import android.graphics.Rect
+import android.graphics.Region
 import android.os.IBinder
 import android.os.SystemProperties
 import android.view.SurfaceControl
@@ -487,6 +488,20 @@
         return 2 * getStatusBarHeight(taskInfo)
     }
 
+    /**
+     * Update the corner region for a specified task
+     */
+    fun onTaskCornersChanged(taskId: Int, corner: Region) {
+        desktopModeTaskRepository.updateTaskCorners(taskId, corner)
+    }
+
+    /**
+     * Remove a previously tracked corner region for a specified task.
+     */
+    fun removeCornersForTask(taskId: Int) {
+        desktopModeTaskRepository.removeTaskCorners(taskId)
+    }
+
 
     /**
      * Adds a listener to find out about changes in the visibility of freeform tasks.
@@ -494,20 +509,47 @@
      * @param listener the listener to add.
      * @param callbackExecutor the executor to call the listener on.
      */
-    fun addListener(listener: VisibleTasksListener, callbackExecutor: Executor) {
+    fun addVisibleTasksListener(listener: VisibleTasksListener, callbackExecutor: Executor) {
         desktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor)
     }
 
+    /**
+     * Adds a listener to track changes to desktop task corners
+     *
+     * @param listener the listener to add.
+     * @param callbackExecutor the executor to call the listener on.
+     */
+    fun setTaskCornerListener(
+            listener: Consumer<Region>,
+            callbackExecutor: Executor
+    ) {
+        desktopModeTaskRepository.setTaskCornerListener(listener, callbackExecutor)
+    }
+
     /** The interface for calls from outside the shell, within the host process. */
     @ExternalThread
     private inner class DesktopModeImpl : DesktopMode {
-        override fun addListener(listener: VisibleTasksListener, callbackExecutor: Executor) {
+        override fun addVisibleTasksListener(
+                listener: VisibleTasksListener,
+                callbackExecutor: Executor
+        ) {
             mainExecutor.execute {
-                this@DesktopTasksController.addListener(listener, callbackExecutor)
+                this@DesktopTasksController.addVisibleTasksListener(listener, callbackExecutor)
+            }
+        }
+
+        override fun addDesktopGestureExclusionRegionListener(
+                listener: Consumer<Region>,
+                callbackExecutor: Executor
+        ) {
+            mainExecutor.execute {
+                this@DesktopTasksController.setTaskCornerListener(listener, callbackExecutor)
             }
         }
     }
 
+
+
     /** The interface for calls from outside the host process. */
     @BinderThread
     private class IDesktopModeImpl(private var controller: DesktopTasksController?) :
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 4f362d4..eb4d2a1 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
@@ -36,6 +36,7 @@
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Slog;
+import android.view.Display;
 import android.view.IRecentsAnimationController;
 import android.view.IRecentsAnimationRunner;
 import android.view.RemoteAnimationTarget;
@@ -132,7 +133,7 @@
     public WindowContainerTransaction handleRequest(IBinder transition,
             TransitionRequestInfo request) {
         // do not directly handle requests. Only entry point should be via startRecentsTransition
-        Slog.e(TAG, "RecentsTransitionHandler.handleRequest: Unexpected transition request");
+        // TODO: Only log an error if the transition is a recents transition
         return null;
     }
 
@@ -219,6 +220,7 @@
         private int mRecentsTaskId = -1;
         private TransitionInfo mInfo = null;
         private boolean mOpeningSeparateHome = false;
+        private boolean mPausingSeparateHome = false;
         private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
         private PictureInPictureSurfaceTransaction mPipTransaction = null;
         private IBinder mTransition = null;
@@ -407,6 +409,10 @@
                         // raise closing (pausing) task to "above" layer so it isn't covered
                         t.setLayer(target.leash, info.getChanges().size() * 3 - i);
                         mPausingTasks.add(new TaskState(change, target.leash));
+                        if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+                            // This can only happen if we have a separate recents/home (3p launcher)
+                            mPausingSeparateHome = true;
+                        }
                         if (taskInfo.pictureInPictureParams != null
                                 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) {
                             mPipTask = taskInfo.token;
@@ -585,6 +591,8 @@
                         final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
                         t.reparent(appearedTargets[i].leash, mInfo.getRoot(rootIdx).getLeash());
                         t.setLayer(appearedTargets[i].leash, layer);
+                        // Hide the animation leash, let listener show it.
+                        t.hide(appearedTargets[i].leash);
                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                                 "  opening new taskId=%d", appearedTargets[i].taskId);
                         mOpeningTasks.add(new TaskState(change, appearedTargets[i].leash));
@@ -612,13 +620,14 @@
             t.apply();
             // not using the incoming anim-only surfaces
             info.releaseAnimSurfaces();
-            if (appearedTargets == null) return;
-            try {
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                        "[%d] RecentsController.merge: calling onTasksAppeared", mInstanceId);
-                mListener.onTasksAppeared(appearedTargets);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Error sending appeared tasks to recents animation", e);
+            if (appearedTargets != null) {
+                try {
+                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                            "[%d] RecentsController.merge: calling onTasksAppeared", mInstanceId);
+                    mListener.onTasksAppeared(appearedTargets);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error sending appeared tasks to recents animation", e);
+                }
             }
             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
         }
@@ -657,6 +666,8 @@
                             mFinishCB != null, enabled);
                     return;
                 }
+                final int displayId = mInfo.getRootCount() > 0 ? mInfo.getRoot(0).getDisplayId()
+                        : Display.DEFAULT_DISPLAY;
                 // transient launches don't receive focus automatically. Since we are taking over
                 // the gesture now, take focus explicitly.
                 // This also moves recents back to top if the user gestured before a switch
@@ -665,7 +676,7 @@
                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                             "[%d] RecentsController.setInputConsumerEnabled: set focus to recents",
                             mInstanceId);
-                    ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId);
+                    ActivityTaskManager.getService().focusTopTask(displayId);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Failed to set focused task", e);
                 }
@@ -700,8 +711,9 @@
                 return;
             }
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                "[%d] RecentsController.finishInner: toHome=%b userLeave=%b willFinishToHome=%b",
-                    mInstanceId, toHome, sendUserLeaveHint, mWillFinishToHome);
+                    "[%d] RecentsController.finishInner: toHome=%b userLeave=%b "
+                            + "willFinishToHome=%b state=%d",
+                    mInstanceId, toHome, sendUserLeaveHint, mWillFinishToHome, mState);
             final Transitions.TransitionFinishCallback finishCB = mFinishCB;
             mFinishCB = null;
 
@@ -712,8 +724,19 @@
                 if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
                 else wct.restoreTransientOrder(mRecentsTask);
             }
-            if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "  returning to app");
+            if (!toHome
+                    // If a recents gesture starts on the 3p launcher, then the 3p launcher is the
+                    // live tile (pausing app). If the gesture is "cancelled" we need to return to
+                    // 3p launcher instead of "task-switching" away from it.
+                    && (!mWillFinishToHome || mPausingSeparateHome)
+                    && mPausingTasks != null && mState == STATE_NORMAL) {
+                if (mPausingSeparateHome) {
+                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                            "  returning to 3p home");
+                } else {
+                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                            "  returning to app");
+                }
                 // The gesture is returning to the pausing-task(s) rather than continuing with
                 // recents, so end the transition by moving the app back to the top (and also
                 // re-showing it's task).
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e4f2724..cca63ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -89,6 +89,9 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Slog;
 import android.view.Choreographer;
@@ -2390,6 +2393,7 @@
             if (!mMainStage.isActive()) return false;
 
             mSplitLayout.setFreezeDividerWindow(false);
+            final StageChangeRecord record = new StageChangeRecord();
             for (int iC = 0; iC < info.getChanges().size(); ++iC) {
                 final TransitionInfo.Change change = info.getChanges().get(iC);
                 if (change.getMode() == TRANSIT_CHANGE
@@ -2405,20 +2409,29 @@
                     if (!stage.containsTask(taskInfo.taskId)) {
                         Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called"
                                 + " with " + taskInfo.taskId + " before startAnimation().");
+                        record.addRecord(stage, true, taskInfo.taskId);
                     }
                 } else if (isClosingType(change.getMode())) {
                     if (stage.containsTask(taskInfo.taskId)) {
+                        record.addRecord(stage, false, taskInfo.taskId);
                         Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
                                 + " with " + taskInfo.taskId + " before startAnimation().");
                     }
                 }
             }
-            if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
+            // If the size of dismissStages == 1, one of the task is closed without prepare pending
+            // transition, which could happen if all activities were finished after finish top
+            // activity in a task, so the trigger task is null when handleRequest.
+            // Note if the size of dismissStages == 2, it's starting a new task, so don't handle it.
+            final ArraySet<StageTaskListener> dismissStages = record.getShouldDismissedStage();
+            if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0
+                    || dismissStages.size() == 1) {
                 Log.e(TAG, "Somehow removed the last task in a stage outside of a proper "
                         + "transition.");
                 final WindowContainerTransaction wct = new WindowContainerTransaction();
-                final int dismissTop = mMainStage.getChildCount() == 0
-                        ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+                final int dismissTop = (dismissStages.size() == 1
+                        && getStageType(dismissStages.valueAt(0)) == STAGE_TYPE_MAIN)
+                        || mMainStage.getChildCount() == 0 ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
                 prepareExitSplitScreen(dismissTop, wct);
                 mSplitTransitions.startDismissTransition(wct, this, dismissTop,
                         EXIT_REASON_UNKNOWN);
@@ -2445,6 +2458,57 @@
                 finishCallback);
     }
 
+    static class StageChangeRecord {
+        static class StageChange {
+            final StageTaskListener mStageTaskListener;
+            final IntArray mAddedTaskId = new IntArray();
+            final IntArray mRemovedTaskId = new IntArray();
+            StageChange(StageTaskListener stage) {
+                mStageTaskListener = stage;
+            }
+
+            boolean shouldDismissStage() {
+                if (mAddedTaskId.size() > 0 || mRemovedTaskId.size() == 0) {
+                    return false;
+                }
+                int removeChildTaskCount = 0;
+                for (int i = mRemovedTaskId.size() - 1; i >= 0; --i) {
+                    if (mStageTaskListener.containsTask(mRemovedTaskId.get(i))) {
+                        ++removeChildTaskCount;
+                    }
+                }
+                return removeChildTaskCount == mStageTaskListener.getChildCount();
+            }
+        }
+        private final ArrayMap<StageTaskListener, StageChange> mChanges = new ArrayMap<>();
+
+        void addRecord(StageTaskListener stage, boolean open, int taskId) {
+            final StageChange next;
+            if (!mChanges.containsKey(stage)) {
+                next = new StageChange(stage);
+                mChanges.put(stage, next);
+            } else {
+                next = mChanges.get(stage);
+            }
+            if (open) {
+                next.mAddedTaskId.add(taskId);
+            } else {
+                next.mRemovedTaskId.add(taskId);
+            }
+        }
+
+        ArraySet<StageTaskListener> getShouldDismissedStage() {
+            final ArraySet<StageTaskListener> dismissTarget = new ArraySet<>();
+            for (int i = mChanges.size() - 1; i >= 0; --i) {
+                final StageChange change = mChanges.valueAt(i);
+                if (change.shouldDismissStage()) {
+                    dismissTarget.add(change.mStageTaskListener);
+                }
+            }
+            return dismissTarget;
+        }
+    }
+
     /** Starts the pending transition animation. */
     public boolean startPendingAnimation(@NonNull IBinder transition,
             @NonNull TransitionInfo info,
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 49a5eac..8fb56fc 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
@@ -36,6 +36,7 @@
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.os.Handler;
 import android.os.IBinder;
@@ -69,6 +70,7 @@
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.TaskCornersListener;
 
 import java.util.Optional;
 import java.util.function.Supplier;
@@ -95,9 +97,11 @@
 
     private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
 
+    private final TaskCornersListener mCornersListener = new TaskCornersListenerImpl();
+
     private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId =
             new SparseArray<>();
-    private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
+    private final DragListenerImpl mDragStartListener = new DragListenerImpl();
     private final InputMonitorFactory mInputMonitorFactory;
     private TaskOperations mTaskOperations;
     private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
@@ -779,6 +783,7 @@
         final DesktopModeTouchEventListener touchEventListener =
                 new DesktopModeTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
+        windowDecoration.setCornersListener(mCornersListener);
         windowDecoration.setDragPositioningCallback(taskPositioner);
         windowDecoration.setDragDetector(touchEventListener.mDragDetector);
         windowDecoration.relayout(taskInfo, startT, finishT,
@@ -786,7 +791,7 @@
         incrementEventReceiverTasks(taskInfo.displayId);
     }
 
-    private class DragStartListenerImpl implements TaskPositioner.DragStartListener {
+    private class DragListenerImpl implements TaskPositioner.DragStartListener {
         @Override
         public void onDragStart(int taskId) {
             mWindowDecorByTaskId.get(taskId).closeHandleMenu();
@@ -798,6 +803,22 @@
             return inputManager.monitorGestureInput("caption-touch", context.getDisplayId());
         }
     }
+
+    private class TaskCornersListenerImpl
+            implements DesktopModeWindowDecoration.TaskCornersListener {
+
+        @Override
+        public void onTaskCornersChanged(int taskId, Region corner) {
+            mDesktopModeController.ifPresent(d -> d.onTaskCornersChanged(taskId, corner));
+            mDesktopTasksController.ifPresent(d -> d.onTaskCornersChanged(taskId, corner));
+        }
+
+        @Override
+        public void onTaskCornersRemoved(int taskId) {
+            mDesktopModeController.ifPresent(d -> d.removeCornersForTask(taskId));
+            mDesktopTasksController.ifPresent(d -> d.removeCornersForTask(taskId));
+        }
+    }
 }
 
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index af3fb0e..f9fdd83 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -17,19 +17,15 @@
 package com.android.wm.shell.windowdecor;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.res.ColorStateList;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.PointF;
+import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.util.Log;
@@ -38,11 +34,6 @@
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.ViewConfiguration;
-import android.widget.Button;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.window.SurfaceSyncGroup;
 import android.window.WindowContainerTransaction;
 
 import com.android.launcher3.icons.IconProvider;
@@ -80,6 +71,7 @@
     private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
             new WindowDecoration.RelayoutResult<>();
 
+    private final Point mPositionInParent = new Point();
     private final PointF mHandleMenuAppInfoPillPosition = new PointF();
     private final PointF mHandleMenuWindowingPillPosition = new PointF();
     private final PointF mHandleMenuMoreActionsPillPosition = new PointF();
@@ -88,10 +80,13 @@
     private AdditionalWindow mHandleMenuAppInfoPill;
     private AdditionalWindow mHandleMenuWindowingPill;
     private AdditionalWindow mHandleMenuMoreActionsPill;
+    private HandleMenu mHandleMenu;
 
     private Drawable mAppIcon;
     private CharSequence mAppName;
 
+    private TaskCornersListener mCornersListener;
+
     private int mMenuWidth;
     private int mMarginMenuTop;
     private int mMarginMenuStart;
@@ -118,29 +113,6 @@
         mSyncQueue = syncQueue;
 
         loadAppInfo();
-        loadHandleMenuDimensions();
-    }
-
-    private void loadHandleMenuDimensions() {
-        final Resources resources = mDecorWindowContext.getResources();
-        mMenuWidth = loadDimensionPixelSize(resources,
-                R.dimen.desktop_mode_handle_menu_width);
-        mMarginMenuTop = loadDimensionPixelSize(resources,
-                R.dimen.desktop_mode_handle_menu_margin_top);
-        mMarginMenuStart = loadDimensionPixelSize(resources,
-                R.dimen.desktop_mode_handle_menu_margin_start);
-        mMarginMenuSpacing = loadDimensionPixelSize(resources,
-                R.dimen.desktop_mode_handle_menu_pill_spacing_margin);
-        mAppInfoPillHeight = loadDimensionPixelSize(resources,
-                R.dimen.desktop_mode_handle_menu_app_info_pill_height);
-        mWindowingPillHeight = loadDimensionPixelSize(resources,
-                R.dimen.desktop_mode_handle_menu_windowing_pill_height);
-        mShadowRadius = loadDimensionPixelSize(resources,
-                R.dimen.desktop_mode_handle_menu_shadow_radius);
-        mCornerRadius = loadDimensionPixelSize(resources,
-                R.dimen.desktop_mode_handle_menu_corner_radius);
-        mMoreActionsPillHeight = loadDimensionPixelSize(resources,
-                R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
     }
 
     @Override
@@ -161,6 +133,10 @@
         mOnCaptionTouchListener = onCaptionTouchListener;
     }
 
+    void setCornersListener(TaskCornersListener cornersListener) {
+        mCornersListener = cornersListener;
+    }
+
     void setDragPositioningCallback(DragPositioningCallback dragPositioningCallback) {
         mDragPositioningCallback = dragPositioningCallback;
     }
@@ -189,20 +165,8 @@
                 taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
         final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
 
-        if (mHandleMenuAppInfoPill != null) {
-            updateHandleMenuPillPositions();
-            startT.setPosition(mHandleMenuAppInfoPill.mWindowSurface,
-                    mHandleMenuAppInfoPillPosition.x, mHandleMenuAppInfoPillPosition.y);
-
-            // Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
-            final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
-            if (shouldShowWindowingPill) {
-                startT.setPosition(mHandleMenuWindowingPill.mWindowSurface,
-                        mHandleMenuWindowingPillPosition.x, mHandleMenuWindowingPillPosition.y);
-            }
-
-            startT.setPosition(mHandleMenuMoreActionsPill.mWindowSurface,
-                    mHandleMenuMoreActionsPillPosition.x, mHandleMenuMoreActionsPillPosition.y);
+        if (isHandleMenuActive()) {
+            mHandleMenu.relayout(startT);
         }
 
         final WindowDecorLinearLayout oldRootView = mResult.mRootView;
@@ -278,12 +242,18 @@
                 .getDimensionPixelSize(R.dimen.freeform_resize_handle);
         final int resize_corner = mResult.mRootView.getResources()
                 .getDimensionPixelSize(R.dimen.freeform_resize_corner);
-        mDragResizeListener.setGeometry(
-                mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
+
+        // If either task geometry or position have changed, update this task's cornersListener
+        if (mDragResizeListener.setGeometry(
+                mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop)
+                || !mTaskInfo.positionInParent.equals(mPositionInParent)) {
+            mCornersListener.onTaskCornersChanged(mTaskInfo.taskId, getGlobalCornersRegion());
+        }
+        mPositionInParent.set(mTaskInfo.positionInParent);
     }
 
     boolean isHandleMenuActive() {
-        return mHandleMenuAppInfoPill != null;
+        return mHandleMenu != null;
     }
 
     private void loadAppInfo() {
@@ -313,136 +283,16 @@
      * Create and display handle menu window
      */
     void createHandleMenu() {
-        final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG);
-        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        updateHandleMenuPillPositions();
-
-        createAppInfoPill(t, ssg);
-
-        // Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
-        final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
-        if (shouldShowWindowingPill) {
-            createWindowingPill(t, ssg);
-        }
-
-        createMoreActionsPill(t, ssg);
-
-        ssg.addTransaction(t);
-        ssg.markSyncReady();
-        setupHandleMenu(shouldShowWindowingPill);
-    }
-
-    private void createAppInfoPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
-        final int x = (int) mHandleMenuAppInfoPillPosition.x;
-        final int y = (int) mHandleMenuAppInfoPillPosition.y;
-        mHandleMenuAppInfoPill = addWindow(
-                R.layout.desktop_mode_window_decor_handle_menu_app_info_pill,
-                "Menu's app info pill",
-                t, ssg, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius);
-    }
-
-    private void createWindowingPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
-        final int x = (int) mHandleMenuWindowingPillPosition.x;
-        final int y = (int) mHandleMenuWindowingPillPosition.y;
-        mHandleMenuWindowingPill = addWindow(
-                R.layout.desktop_mode_window_decor_handle_menu_windowing_pill,
-                "Menu's windowing pill",
-                t, ssg, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius);
-    }
-
-    private void createMoreActionsPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
-        final int x = (int) mHandleMenuMoreActionsPillPosition.x;
-        final int y = (int) mHandleMenuMoreActionsPillPosition.y;
-        mHandleMenuMoreActionsPill = addWindow(
-                R.layout.desktop_mode_window_decor_handle_menu_more_actions_pill,
-                "Menu's more actions pill",
-                t, ssg, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius);
-    }
-
-    private void setupHandleMenu(boolean windowingPillShown) {
-        // App Info pill setup.
-        final View appInfoPillView = mHandleMenuAppInfoPill.mWindowViewHost.getView();
-        final ImageButton collapseBtn = appInfoPillView.findViewById(R.id.collapse_menu_button);
-        final ImageView appIcon = appInfoPillView.findViewById(R.id.application_icon);
-        final TextView appName = appInfoPillView.findViewById(R.id.application_name);
-        collapseBtn.setOnClickListener(mOnCaptionButtonClickListener);
-        appInfoPillView.setOnTouchListener(mOnCaptionTouchListener);
-        appIcon.setImageDrawable(mAppIcon);
-        appName.setText(mAppName);
-
-        // Windowing pill setup.
-        if (windowingPillShown) {
-            final View windowingPillView = mHandleMenuWindowingPill.mWindowViewHost.getView();
-            final ImageButton fullscreenBtn = windowingPillView.findViewById(
-                    R.id.fullscreen_button);
-            final ImageButton splitscreenBtn = windowingPillView.findViewById(
-                    R.id.split_screen_button);
-            final ImageButton floatingBtn = windowingPillView.findViewById(R.id.floating_button);
-            final ImageButton desktopBtn = windowingPillView.findViewById(R.id.desktop_button);
-            fullscreenBtn.setOnClickListener(mOnCaptionButtonClickListener);
-            splitscreenBtn.setOnClickListener(mOnCaptionButtonClickListener);
-            floatingBtn.setOnClickListener(mOnCaptionButtonClickListener);
-            desktopBtn.setOnClickListener(mOnCaptionButtonClickListener);
-            // The button corresponding to the windowing mode that the task is currently in uses a
-            // different color than the others.
-            final ColorStateList activeColorStateList = ColorStateList.valueOf(
-                    mContext.getColor(R.color.desktop_mode_caption_menu_buttons_color_active));
-            final ColorStateList inActiveColorStateList = ColorStateList.valueOf(
-                    mContext.getColor(R.color.desktop_mode_caption_menu_buttons_color_inactive));
-            fullscreenBtn.setImageTintList(
-                    mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
-                            ? activeColorStateList : inActiveColorStateList);
-            splitscreenBtn.setImageTintList(
-                    mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
-                            ? activeColorStateList : inActiveColorStateList);
-            floatingBtn.setImageTintList(mTaskInfo.getWindowingMode() == WINDOWING_MODE_PINNED
-                    ? activeColorStateList : inActiveColorStateList);
-            desktopBtn.setImageTintList(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
-                    ? activeColorStateList : inActiveColorStateList);
-        }
-
-        // More Actions pill setup.
-        final View moreActionsPillView = mHandleMenuMoreActionsPill.mWindowViewHost.getView();
-        final Button closeBtn = moreActionsPillView.findViewById(R.id.close_button);
-        closeBtn.setOnClickListener(mOnCaptionButtonClickListener);
-    }
-
-    /**
-     * Updates the handle menu pills' position variables to reflect their next positions
-     */
-    private void updateHandleMenuPillPositions() {
-        final int menuX, menuY;
-        final int captionWidth = mTaskInfo.getConfiguration()
-                .windowConfiguration.getBounds().width();
-        if (mRelayoutParams.mLayoutResId
-                == R.layout.desktop_mode_app_controls_window_decor) {
-            // Align the handle menu to the left of the caption.
-            menuX = mRelayoutParams.mCaptionX + mMarginMenuStart;
-            menuY = mRelayoutParams.mCaptionY + mMarginMenuTop;
-        } else {
-            // Position the handle menu at the center of the caption.
-            menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2);
-            menuY = mRelayoutParams.mCaptionY + mMarginMenuStart;
-        }
-
-        // App Info pill setup.
-        final int appInfoPillY = menuY;
-        mHandleMenuAppInfoPillPosition.set(menuX, appInfoPillY);
-
-        // Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
-        final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
-
-        final int windowingPillY, moreActionsPillY;
-        if (shouldShowWindowingPill) {
-            windowingPillY = appInfoPillY + mAppInfoPillHeight + mMarginMenuSpacing;
-            mHandleMenuWindowingPillPosition.set(menuX, windowingPillY);
-            moreActionsPillY = windowingPillY + mWindowingPillHeight + mMarginMenuSpacing;
-            mHandleMenuMoreActionsPillPosition.set(menuX, moreActionsPillY);
-        } else {
-            // Just start after the end of the app info pill + margins.
-            moreActionsPillY = appInfoPillY + mAppInfoPillHeight + mMarginMenuSpacing;
-            mHandleMenuMoreActionsPillPosition.set(menuX, moreActionsPillY);
-        }
+        mHandleMenu = new HandleMenu.Builder(this)
+                .setAppIcon(mAppIcon)
+                .setAppName(mAppName)
+                .setOnClickListener(mOnCaptionButtonClickListener)
+                .setOnTouchListener(mOnCaptionTouchListener)
+                .setLayoutId(mRelayoutParams.mLayoutResId)
+                .setCaptionPosition(mRelayoutParams.mCaptionX, mRelayoutParams.mCaptionY)
+                .setWindowingButtonsVisible(DesktopModeStatus.isProto2Enabled())
+                .build();
+        mHandleMenu.show();
     }
 
     /**
@@ -450,14 +300,8 @@
      */
     void closeHandleMenu() {
         if (!isHandleMenuActive()) return;
-        mHandleMenuAppInfoPill.releaseView();
-        mHandleMenuAppInfoPill = null;
-        if (mHandleMenuWindowingPill != null) {
-            mHandleMenuWindowingPill.releaseView();
-            mHandleMenuWindowingPill = null;
-        }
-        mHandleMenuMoreActionsPill.releaseView();
-        mHandleMenuMoreActionsPill = null;
+        mHandleMenu.close();
+        mHandleMenu = null;
     }
 
     @Override
@@ -474,10 +318,6 @@
     void closeHandleMenuIfNeeded(MotionEvent ev) {
         if (!isHandleMenuActive()) return;
 
-        // When this is called before the layout is fully inflated, width will be 0.
-        // Menu is not visible in this scenario, so skip the check if that is the case.
-        if (mHandleMenuAppInfoPill.mWindowViewHost.getView().getWidth() == 0) return;
-
         PointF inputPoint = offsetCaptionLocation(ev);
 
         // If this is called before open_menu_button's onClick, we don't want to close
@@ -487,22 +327,7 @@
                 inputPoint.x,
                 inputPoint.y);
 
-        final boolean pointInAppInfoPill = pointInView(
-                mHandleMenuAppInfoPill.mWindowViewHost.getView(),
-                inputPoint.x - mHandleMenuAppInfoPillPosition.x,
-                inputPoint.y - mHandleMenuAppInfoPillPosition.y);
-        boolean pointInWindowingPill = false;
-        if (mHandleMenuWindowingPill != null) {
-            pointInWindowingPill = pointInView(mHandleMenuWindowingPill.mWindowViewHost.getView(),
-                    inputPoint.x - mHandleMenuWindowingPillPosition.x,
-                    inputPoint.y - mHandleMenuWindowingPillPosition.y);
-        }
-        final boolean pointInMoreActionsPill = pointInView(
-                mHandleMenuMoreActionsPill.mWindowViewHost.getView(),
-                inputPoint.x - mHandleMenuMoreActionsPillPosition.x,
-                inputPoint.y - mHandleMenuMoreActionsPillPosition.y);
-        if (!pointInAppInfoPill && !pointInWindowingPill
-                && !pointInMoreActionsPill && !pointInOpenMenuButton) {
+        if (!mHandleMenu.isValidMenuInput(inputPoint) && !pointInOpenMenuButton) {
             closeHandleMenu();
         }
     }
@@ -559,13 +384,7 @@
             final View handle = caption.findViewById(R.id.caption_handle);
             clickIfPointInView(new PointF(ev.getX(), ev.getY()), handle);
         } else {
-            final View appInfoPill = mHandleMenuAppInfoPill.mWindowViewHost.getView();
-            final ImageButton collapse = appInfoPill.findViewById(R.id.collapse_menu_button);
-            // Translate the input point from display coordinates to the same space as the collapse
-            // button, meaning its parent (app info pill view).
-            final PointF inputPoint = new PointF(ev.getX() - mHandleMenuAppInfoPillPosition.x,
-                    ev.getY() - mHandleMenuAppInfoPillPosition.y);
-            clickIfPointInView(inputPoint, collapse);
+            mHandleMenu.checkClickEvent(ev);
         }
     }
 
@@ -577,7 +396,7 @@
         return false;
     }
 
-    private boolean pointInView(View v, float x, float y) {
+    boolean pointInView(View v, float x, float y) {
         return v != null && v.getLeft() <= x && v.getRight() >= x
                 && v.getTop() <= y && v.getBottom() >= y;
     }
@@ -586,6 +405,7 @@
     public void close() {
         closeDragResizeListener();
         closeHandleMenu();
+        mCornersListener.onTaskCornersRemoved(mTaskInfo.taskId);
         super.close();
     }
 
@@ -598,6 +418,15 @@
                 : R.layout.desktop_mode_focused_window_decor;
     }
 
+    /**
+     * Create a new region out of the corner rects of this task.
+     */
+    Region getGlobalCornersRegion() {
+        Region cornersRegion = mDragResizeListener.getCornersRegion();
+        cornersRegion.translate(mPositionInParent.x, mPositionInParent.y);
+        return cornersRegion;
+    }
+
     static class Factory {
 
         DesktopModeWindowDecoration create(
@@ -620,4 +449,13 @@
                     syncQueue);
         }
     }
+
+    interface TaskCornersListener {
+        /** Inform the implementing class of this task's change in corner resize handles */
+        void onTaskCornersChanged(int taskId, Region corner);
+
+        /** Inform the implementing class that this task no longer needs its corners tracked,
+         * likely due to it closing. */
+        void onTaskCornersRemoved(int taskId);
+    }
 }
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 d5437c7..34c8c08 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
@@ -134,13 +134,14 @@
      * @param cornerSize The size of the resize handle centered in each corner.
      * @param touchSlop The distance in pixels user has to drag with touch for it to register as
      *                  a resize action.
+     * @return whether the geometry has changed or not
      */
-    void setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize,
+    boolean setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize,
             int touchSlop) {
         if (mTaskWidth == taskWidth && mTaskHeight == taskHeight
                 && mResizeHandleThickness == resizeHandleThickness
                 && mCornerSize == cornerSize) {
-            return;
+            return false;
         }
 
         mTaskWidth = taskWidth;
@@ -220,6 +221,19 @@
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
+        return true;
+    }
+
+    /**
+     * Generate a Region that encapsulates all 4 corner handles
+     */
+    Region getCornersRegion() {
+        Region region = new Region();
+        region.union(mLeftTopCornerBounds);
+        region.union(mLeftBottomCornerBounds);
+        region.union(mRightTopCornerBounds);
+        region.union(mRightBottomCornerBounds);
+        return region;
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
new file mode 100644
index 0000000..ed3cca0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -0,0 +1,402 @@
+/*
+ * 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.windowdecor;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.graphics.drawable.Drawable;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.window.SurfaceSyncGroup;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
+
+/**
+ * Handle menu opened when the appropriate button is clicked on.
+ *
+ * Displays up to 3 pills that show the following:
+ * App Info: App name, app icon, and collapse button to close the menu.
+ * Windowing Options(Proto 2 only): Buttons to change windowing modes.
+ * Additional Options: Miscellaneous functions including screenshot and closing task.
+ */
+class HandleMenu {
+    private static final String TAG = "HandleMenu";
+    private final Context mContext;
+    private final WindowDecoration mParentDecor;
+    private WindowDecoration.AdditionalWindow mAppInfoPill;
+    private WindowDecoration.AdditionalWindow mWindowingPill;
+    private WindowDecoration.AdditionalWindow mMoreActionsPill;
+    private final PointF mAppInfoPillPosition = new PointF();
+    private final PointF mWindowingPillPosition = new PointF();
+    private final PointF mMoreActionsPillPosition = new PointF();
+    private final boolean mShouldShowWindowingPill;
+    private final Drawable mAppIcon;
+    private final CharSequence mAppName;
+    private final View.OnClickListener mOnClickListener;
+    private final View.OnTouchListener mOnTouchListener;
+    private final RunningTaskInfo mTaskInfo;
+    private final int mLayoutResId;
+    private final int mCaptionX;
+    private final int mCaptionY;
+    private int mMarginMenuTop;
+    private int mMarginMenuStart;
+    private int mMarginMenuSpacing;
+    private int mMenuWidth;
+    private int mAppInfoPillHeight;
+    private int mWindowingPillHeight;
+    private int mMoreActionsPillHeight;
+    private int mShadowRadius;
+    private int mCornerRadius;
+
+
+    HandleMenu(WindowDecoration parentDecor, int layoutResId, int captionX, int captionY,
+            View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
+            Drawable appIcon, CharSequence appName, boolean shouldShowWindowingPill) {
+        mParentDecor = parentDecor;
+        mContext = mParentDecor.mDecorWindowContext;
+        mTaskInfo = mParentDecor.mTaskInfo;
+        mLayoutResId = layoutResId;
+        mCaptionX = captionX;
+        mCaptionY = captionY;
+        mOnClickListener = onClickListener;
+        mOnTouchListener = onTouchListener;
+        mAppIcon = appIcon;
+        mAppName = appName;
+        mShouldShowWindowingPill = shouldShowWindowingPill;
+        loadHandleMenuDimensions();
+        updateHandleMenuPillPositions();
+    }
+
+    void show() {
+        final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG);
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+
+        createAppInfoPill(t, ssg);
+        if (mShouldShowWindowingPill) {
+            createWindowingPill(t, ssg);
+        }
+        createMoreActionsPill(t, ssg);
+        ssg.addTransaction(t);
+        ssg.markSyncReady();
+        setupHandleMenu();
+    }
+
+    private void createAppInfoPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
+        final int x = (int) mAppInfoPillPosition.x;
+        final int y = (int) mAppInfoPillPosition.y;
+        mAppInfoPill = mParentDecor.addWindow(
+                R.layout.desktop_mode_window_decor_handle_menu_app_info_pill,
+                "Menu's app info pill",
+                t, ssg, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius);
+    }
+
+    private void createWindowingPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
+        final int x = (int) mWindowingPillPosition.x;
+        final int y = (int) mWindowingPillPosition.y;
+        mWindowingPill = mParentDecor.addWindow(
+                R.layout.desktop_mode_window_decor_handle_menu_windowing_pill,
+                "Menu's windowing pill",
+                t, ssg, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius);
+    }
+
+    private void createMoreActionsPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
+        final int x = (int) mMoreActionsPillPosition.x;
+        final int y = (int) mMoreActionsPillPosition.y;
+        mMoreActionsPill = mParentDecor.addWindow(
+                R.layout.desktop_mode_window_decor_handle_menu_more_actions_pill,
+                "Menu's more actions pill",
+                t, ssg, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius);
+    }
+
+    /**
+     * Set up interactive elements and color of this handle menu
+     */
+    private void setupHandleMenu() {
+        // App Info pill setup.
+        final View appInfoPillView = mAppInfoPill.mWindowViewHost.getView();
+        final ImageButton collapseBtn = appInfoPillView.findViewById(R.id.collapse_menu_button);
+        final ImageView appIcon = appInfoPillView.findViewById(R.id.application_icon);
+        final TextView appName = appInfoPillView.findViewById(R.id.application_name);
+        collapseBtn.setOnClickListener(mOnClickListener);
+        appInfoPillView.setOnTouchListener(mOnTouchListener);
+        appIcon.setImageDrawable(mAppIcon);
+        appName.setText(mAppName);
+
+        // Windowing pill setup.
+        if (mShouldShowWindowingPill) {
+            final View windowingPillView = mWindowingPill.mWindowViewHost.getView();
+            final ImageButton fullscreenBtn = windowingPillView.findViewById(
+                    R.id.fullscreen_button);
+            final ImageButton splitscreenBtn = windowingPillView.findViewById(
+                    R.id.split_screen_button);
+            final ImageButton floatingBtn = windowingPillView.findViewById(R.id.floating_button);
+            final ImageButton desktopBtn = windowingPillView.findViewById(R.id.desktop_button);
+            fullscreenBtn.setOnClickListener(mOnClickListener);
+            splitscreenBtn.setOnClickListener(mOnClickListener);
+            floatingBtn.setOnClickListener(mOnClickListener);
+            desktopBtn.setOnClickListener(mOnClickListener);
+            // The button corresponding to the windowing mode that the task is currently in uses a
+            // different color than the others.
+            final ColorStateList activeColorStateList = ColorStateList.valueOf(
+                    mContext.getColor(R.color.desktop_mode_caption_menu_buttons_color_active));
+            final ColorStateList inActiveColorStateList = ColorStateList.valueOf(
+                    mContext.getColor(R.color.desktop_mode_caption_menu_buttons_color_inactive));
+            fullscreenBtn.setImageTintList(
+                    mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                            ? activeColorStateList : inActiveColorStateList);
+            splitscreenBtn.setImageTintList(
+                    mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
+                            ? activeColorStateList : inActiveColorStateList);
+            floatingBtn.setImageTintList(mTaskInfo.getWindowingMode() == WINDOWING_MODE_PINNED
+                    ? activeColorStateList : inActiveColorStateList);
+            desktopBtn.setImageTintList(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+                    ? activeColorStateList : inActiveColorStateList);
+        }
+
+        // More Actions pill setup.
+        final View moreActionsPillView = mMoreActionsPill.mWindowViewHost.getView();
+        final Button closeBtn = moreActionsPillView.findViewById(R.id.close_button);
+        closeBtn.setOnClickListener(mOnClickListener);
+    }
+
+    /**
+     * Updates the handle menu pills' position variables to reflect their next positions
+     */
+    private void updateHandleMenuPillPositions() {
+        final int menuX, menuY;
+        final int captionWidth = mTaskInfo.getConfiguration()
+                .windowConfiguration.getBounds().width();
+        if (mLayoutResId
+                == R.layout.desktop_mode_app_controls_window_decor) {
+            // Align the handle menu to the left of the caption.
+            menuX = mCaptionX + mMarginMenuStart;
+            menuY = mCaptionY + mMarginMenuTop;
+        } else {
+            // Position the handle menu at the center of the caption.
+            menuX = mCaptionX + (captionWidth / 2) - (mMenuWidth / 2);
+            menuY = mCaptionY + mMarginMenuStart;
+        }
+
+        // App Info pill setup.
+        final int appInfoPillY = menuY;
+        mAppInfoPillPosition.set(menuX, appInfoPillY);
+
+        final int windowingPillY, moreActionsPillY;
+        if (mShouldShowWindowingPill) {
+            windowingPillY = appInfoPillY + mAppInfoPillHeight + mMarginMenuSpacing;
+            mWindowingPillPosition.set(menuX, windowingPillY);
+            moreActionsPillY = windowingPillY + mWindowingPillHeight + mMarginMenuSpacing;
+            mMoreActionsPillPosition.set(menuX, moreActionsPillY);
+        } else {
+            // Just start after the end of the app info pill + margins.
+            moreActionsPillY = appInfoPillY + mAppInfoPillHeight + mMarginMenuSpacing;
+            mMoreActionsPillPosition.set(menuX, moreActionsPillY);
+        }
+    }
+
+    /**
+     * Update pill layout, in case task changes have caused positioning to change.
+     * @param t
+     */
+    void relayout(SurfaceControl.Transaction t) {
+        if (mAppInfoPill != null) {
+            updateHandleMenuPillPositions();
+            t.setPosition(mAppInfoPill.mWindowSurface,
+                    mAppInfoPillPosition.x, mAppInfoPillPosition.y);
+            // Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
+            final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
+            if (shouldShowWindowingPill) {
+                t.setPosition(mWindowingPill.mWindowSurface,
+                        mWindowingPillPosition.x, mWindowingPillPosition.y);
+            }
+            t.setPosition(mMoreActionsPill.mWindowSurface,
+                    mMoreActionsPillPosition.x, mMoreActionsPillPosition.y);
+        }
+    }
+    /**
+     * Check a passed MotionEvent if a click has occurred on any button on this caption
+     * Note this should only be called when a regular onClick is not possible
+     * (i.e. the button was clicked through status bar layer)
+     * @param ev the MotionEvent to compare against.
+     */
+    void checkClickEvent(MotionEvent ev) {
+        final View appInfoPill = mAppInfoPill.mWindowViewHost.getView();
+        final ImageButton collapse = appInfoPill.findViewById(R.id.collapse_menu_button);
+        // Translate the input point from display coordinates to the same space as the collapse
+        // button, meaning its parent (app info pill view).
+        final PointF inputPoint = new PointF(ev.getX() - mAppInfoPillPosition.x,
+                ev.getY() - mAppInfoPillPosition.y);
+        if (pointInView(collapse, inputPoint.x, inputPoint.y)) {
+            mOnClickListener.onClick(collapse);
+        }
+    }
+
+    /**
+     * A valid menu input is one of the following:
+     * An input that happens in the menu views.
+     * Any input before the views have been laid out.
+     * @param inputPoint the input to compare against.
+     */
+    boolean isValidMenuInput(PointF inputPoint) {
+        if (!viewsLaidOut()) return true;
+        final boolean pointInAppInfoPill = pointInView(
+                mAppInfoPill.mWindowViewHost.getView(),
+                inputPoint.x - mAppInfoPillPosition.x,
+                inputPoint.y - mAppInfoPillPosition.y);
+        boolean pointInWindowingPill = false;
+        if (mWindowingPill != null) {
+            pointInWindowingPill = pointInView(
+                    mWindowingPill.mWindowViewHost.getView(),
+                    inputPoint.x - mWindowingPillPosition.x,
+                    inputPoint.y - mWindowingPillPosition.y);
+        }
+        final boolean pointInMoreActionsPill = pointInView(
+                mMoreActionsPill.mWindowViewHost.getView(),
+                inputPoint.x - mMoreActionsPillPosition.x,
+                inputPoint.y - mMoreActionsPillPosition.y);
+
+        return pointInAppInfoPill || pointInWindowingPill || pointInMoreActionsPill;
+    }
+
+    private boolean pointInView(View v, float x, float y) {
+        return v != null && v.getLeft() <= x && v.getRight() >= x
+                && v.getTop() <= y && v.getBottom() >= y;
+    }
+
+    /**
+     * Check if the views for handle menu can be seen.
+     * @return
+     */
+    private boolean viewsLaidOut() {
+        return mAppInfoPill.mWindowViewHost.getView().isLaidOut();
+    }
+
+
+    private void loadHandleMenuDimensions() {
+        final Resources resources = mContext.getResources();
+        mMenuWidth = loadDimensionPixelSize(resources,
+                R.dimen.desktop_mode_handle_menu_width);
+        mMarginMenuTop = loadDimensionPixelSize(resources,
+                R.dimen.desktop_mode_handle_menu_margin_top);
+        mMarginMenuStart = loadDimensionPixelSize(resources,
+                R.dimen.desktop_mode_handle_menu_margin_start);
+        mMarginMenuSpacing = loadDimensionPixelSize(resources,
+                R.dimen.desktop_mode_handle_menu_pill_spacing_margin);
+        mAppInfoPillHeight = loadDimensionPixelSize(resources,
+                R.dimen.desktop_mode_handle_menu_app_info_pill_height);
+        mWindowingPillHeight = loadDimensionPixelSize(resources,
+                R.dimen.desktop_mode_handle_menu_windowing_pill_height);
+        mMoreActionsPillHeight = loadDimensionPixelSize(resources,
+                R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
+        mShadowRadius = loadDimensionPixelSize(resources,
+                R.dimen.desktop_mode_handle_menu_shadow_radius);
+        mCornerRadius = loadDimensionPixelSize(resources,
+                R.dimen.desktop_mode_handle_menu_corner_radius);
+    }
+
+    private int loadDimensionPixelSize(Resources resources, int resourceId) {
+        if (resourceId == Resources.ID_NULL) {
+            return 0;
+        }
+        return resources.getDimensionPixelSize(resourceId);
+    }
+
+    void close() {
+        mAppInfoPill.releaseView();
+        mAppInfoPill = null;
+        if (mWindowingPill != null) {
+            mWindowingPill.releaseView();
+            mWindowingPill = null;
+        }
+        mMoreActionsPill.releaseView();
+        mMoreActionsPill = null;
+    }
+
+    static final class Builder {
+        private final WindowDecoration mParent;
+        private CharSequence mName;
+        private Drawable mAppIcon;
+        private View.OnClickListener mOnClickListener;
+        private View.OnTouchListener mOnTouchListener;
+        private int mLayoutId;
+        private int mCaptionX;
+        private int mCaptionY;
+        private boolean mShowWindowingPill;
+
+
+        Builder(@NonNull WindowDecoration parent) {
+            mParent = parent;
+        }
+
+        Builder setAppName(@Nullable CharSequence name) {
+            mName = name;
+            return this;
+        }
+
+        Builder setAppIcon(@Nullable Drawable appIcon) {
+            mAppIcon = appIcon;
+            return this;
+        }
+
+        Builder setOnClickListener(@Nullable View.OnClickListener onClickListener) {
+            mOnClickListener = onClickListener;
+            return this;
+        }
+
+        Builder setOnTouchListener(@Nullable View.OnTouchListener onTouchListener) {
+            mOnTouchListener = onTouchListener;
+            return this;
+        }
+
+        Builder setLayoutId(int layoutId) {
+            mLayoutId = layoutId;
+            return this;
+        }
+
+        Builder setCaptionPosition(int captionX, int captionY) {
+            mCaptionX = captionX;
+            mCaptionY = captionY;
+            return this;
+        }
+
+        Builder setWindowingButtonsVisible(boolean windowingButtonsVisible) {
+            mShowWindowingPill = windowingButtonsVisible;
+            return this;
+        }
+
+        HandleMenu build() {
+            return new HandleMenu(mParent, mLayoutId, mCaptionX, mCaptionY, mOnClickListener,
+                    mOnTouchListener, mAppIcon, mName, mShowWindowingPill);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 9a1b4ff..e772fc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -458,7 +458,7 @@
         SurfaceControlViewHost mWindowViewHost;
         Supplier<SurfaceControl.Transaction> mTransactionSupplier;
 
-        private AdditionalWindow(SurfaceControl surfaceControl,
+        AdditionalWindow(SurfaceControl surfaceControl,
                 SurfaceControlViewHost surfaceControlViewHost,
                 Supplier<SurfaceControl.Transaction> transactionSupplier) {
             mWindowSurface = surfaceControl;
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index c57e6f0..38d17de 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -401,6 +401,14 @@
         decodeColorType = kN32_SkColorType;
     }
 
+    // b/276879147, fallback to RGBA_8888 when decoding HEIF and P010 is not supported.
+    if (decodeColorType == kRGBA_1010102_SkColorType &&
+        codec->getEncodedFormat() == SkEncodedImageFormat::kHEIF &&
+        env->CallStaticBooleanMethod(gImageDecoder_class,
+                                     gImageDecoder_isP010SupportedForHEVCMethodID) == JNI_FALSE) {
+        decodeColorType = kN32_SkColorType;
+    }
+
     sk_sp<SkColorSpace> decodeColorSpace = codec->computeOutputColorSpace(
             decodeColorType, prefColorSpace);
 
diff --git a/libs/hwui/jni/BitmapFactory.h b/libs/hwui/jni/BitmapFactory.h
index 45bffc4..a079cb4 100644
--- a/libs/hwui/jni/BitmapFactory.h
+++ b/libs/hwui/jni/BitmapFactory.h
@@ -1,8 +1,9 @@
 #ifndef _ANDROID_GRAPHICS_BITMAP_FACTORY_H_
 #define _ANDROID_GRAPHICS_BITMAP_FACTORY_H_
 
+#include <SkEncodedImageFormat.h>
+
 #include "GraphicsJNI.h"
-#include "SkEncodedImageFormat.h"
 
 extern jclass gOptions_class;
 extern jfieldID gOptions_justBoundsFieldID;
@@ -26,6 +27,9 @@
 extern jclass gBitmapConfig_class;
 extern jmethodID gBitmapConfig_nativeToConfigMethodID;
 
+extern jclass gImageDecoder_class;
+extern jmethodID gImageDecoder_isP010SupportedForHEVCMethodID;
+
 jstring getMimeTypeAsJavaString(JNIEnv*, SkEncodedImageFormat);
 
 #endif  // _ANDROID_GRAPHICS_BITMAP_FACTORY_H_
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index db1c188..6744c6c 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -25,6 +25,7 @@
 #include <SkCodecAnimation.h>
 #include <SkColorSpace.h>
 #include <SkColorType.h>
+#include <SkEncodedImageFormat.h>
 #include <SkImageInfo.h>
 #include <SkRect.h>
 #include <SkSize.h>
@@ -48,7 +49,8 @@
 
 using namespace android;
 
-static jclass    gImageDecoder_class;
+jclass gImageDecoder_class;
+jmethodID gImageDecoder_isP010SupportedForHEVCMethodID;
 static jclass    gSize_class;
 static jclass    gDecodeException_class;
 static jclass    gCanvas_class;
@@ -298,6 +300,14 @@
         colorType = kN32_SkColorType;
     }
 
+    // b/276879147, fallback to RGBA_8888 when decoding HEIF and P010 is not supported.
+    if (colorType == kRGBA_1010102_SkColorType &&
+        decoder->mCodec->getEncodedFormat() == SkEncodedImageFormat::kHEIF &&
+        env->CallStaticBooleanMethod(gImageDecoder_class,
+                                     gImageDecoder_isP010SupportedForHEVCMethodID) == JNI_FALSE) {
+        colorType = kN32_SkColorType;
+    }
+
     if (!decoder->setOutColorType(colorType)) {
         doThrowISE(env, "Failed to set out color type!");
         return nullptr;
@@ -540,6 +550,8 @@
     gImageDecoder_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder"));
     gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "<init>", "(JIIZZ)V");
     gImageDecoder_postProcessMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "postProcessAndRelease", "(Landroid/graphics/Canvas;)I");
+    gImageDecoder_isP010SupportedForHEVCMethodID =
+            GetStaticMethodIDOrDie(env, gImageDecoder_class, "isP010SupportedForHEVC", "()Z");
 
     gSize_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/util/Size"));
     gSize_constructorMethodID = GetMethodIDOrDie(env, gSize_class, "<init>", "(II)V");
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index 835e4c3..a3cd623 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -20,11 +20,23 @@
 import android.media.projection.IMediaProjectionCallback;
 import android.media.projection.IMediaProjectionWatcherCallback;
 import android.media.projection.MediaProjectionInfo;
+import android.media.projection.ReviewGrantedConsentResult;
 import android.os.IBinder;
 import android.view.ContentRecordingSession;
 
 /** {@hide} */
 interface IMediaProjectionManager {
+    /**
+     * Intent extra indicating if user must review access to the consent token already granted.
+     */
+    const String EXTRA_USER_REVIEW_GRANTED_CONSENT = "extra_media_projection_user_consent_required";
+
+    /**
+     * Intent extra indicating the package attempting to re-use granted consent.
+     */
+    const String EXTRA_PACKAGE_REUSING_GRANTED_CONSENT =
+            "extra_media_projection_package_reusing_consent";
+
     @UnsupportedAppUsage
     boolean hasProjectionPermission(int uid, String packageName);
 
@@ -37,6 +49,21 @@
             boolean permanentGrant);
 
     /**
+     * Returns the current {@link IMediaProjection} instance associated with the given
+     * package, or {@code null} if it is not possible to re-use the current projection.
+     *
+     * <p>Should only be invoked when the user has reviewed consent for a re-used projection token.
+     * Requires that there is a prior session waiting for the user to review consent, and the given
+     * package details match those on the current projection.
+     *
+     * @see {@link #isCurrentProjection}
+     */
+    @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MANAGE_MEDIA_PROJECTION)")
+    IMediaProjection getProjection(int uid, String packageName);
+
+    /**
      * Returns {@code true} if the given {@link IMediaProjection} corresponds to the current
      * projection, or {@code false} otherwise.
      */
@@ -58,7 +85,7 @@
      */
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
-    void requestConsentForInvalidProjection(IMediaProjection projection);
+    void requestConsentForInvalidProjection(in IMediaProjection projection);
 
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
@@ -94,9 +121,32 @@
      *
      * @param incomingSession the nullable incoming content recording session
      * @param projection      the non-null projection the session describes
+     * @throws SecurityException If the provided projection is not current.
      */
   @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
     boolean setContentRecordingSession(in ContentRecordingSession incomingSession,
             in IMediaProjection projection);
+
+    /**
+     * Sets the result of the user reviewing the recording permission, when the host app is re-using
+     * the consent token.
+     *
+     * <p>Ignores the provided result if the given projection is not the current projection.
+     *
+     * <p>Based on the given result:
+     * <ul>
+     *   <li>If UNKNOWN or RECORD_CANCEL, then tear down the recording.</li>
+     *   <li>If RECORD_CONTENT_DISPLAY, then record the default display.</li>
+     *   <li>If RECORD_CONTENT_TASK, record the task indicated by
+     *     {@link IMediaProjection#getLaunchCookie}.</li>
+     * </ul>
+     * @param projection The projection associated with the consent result. Must be the current
+     * projection instance, unless the given result is RECORD_CANCEL.
+     */
+    @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MANAGE_MEDIA_PROJECTION)")
+    void setUserReviewGrantedConsentResult(ReviewGrantedConsentResult consentResult,
+            in @nullable IMediaProjection projection);
 }
diff --git a/media/java/android/media/projection/ReviewGrantedConsentResult.aidl b/media/java/android/media/projection/ReviewGrantedConsentResult.aidl
new file mode 100644
index 0000000..4f25be7
--- /dev/null
+++ b/media/java/android/media/projection/ReviewGrantedConsentResult.aidl
@@ -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 android.media.projection;
+
+/**
+ * Indicates result of user interacting with consent dialog, when their review is required due to
+ * app re-using the token.
+
+ * @hide
+ */
+@Backing(type="int")
+enum ReviewGrantedConsentResult {
+    UNKNOWN = -1,
+    RECORD_CANCEL = 0,
+    RECORD_CONTENT_DISPLAY = 1,
+    RECORD_CONTENT_TASK = 2,
+}
diff --git a/media/java/android/media/soundtrigger/TEST_MAPPING b/media/java/android/media/soundtrigger/TEST_MAPPING
new file mode 100644
index 0000000..3d73795
--- /dev/null
+++ b/media/java/android/media/soundtrigger/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSoundTriggerTestCases"
+    }
+  ]
+}
diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java
index 9a995a0..a396b7e 100644
--- a/media/java/android/media/tv/TvRecordingClient.java
+++ b/media/java/android/media/tv/TvRecordingClient.java
@@ -396,6 +396,12 @@
         }
     }
 
+    // For testing purposes only.
+    /** @hide */
+    public TvInputManager.SessionCallback getSessionCallback() {
+        return mSessionCallback;
+    }
+
     /**
      * Callback used to receive various status updates on the
      * {@link android.media.tv.TvInputService.RecordingSession}
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index d470d4c..aae30df 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -16,13 +16,15 @@
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        style="@style/ScrollViewStyle">
+        style="@style/ScrollViewStyle"
+        android:importantForAccessibility="no">
 
     <LinearLayout
             android:id="@+id/activity_confirmation"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:baselineAligned="false">
+            android:baselineAligned="false"
+            android:importantForAccessibility="no">
 
         <LinearLayout android:id="@+id/association_confirmation"
                       style="@style/ContainerLayout">
@@ -153,7 +155,8 @@
         <RelativeLayout
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:layout_weight="1">
+            android:layout_weight="1"
+            android:importantForAccessibility="noHideDescendants">
 
             <ProgressBar
                 android:id="@+id/spinner_single_device"
diff --git a/packages/CompanionDeviceManager/res/layout/vendor_header.xml b/packages/CompanionDeviceManager/res/layout/vendor_header.xml
index 16a87e5..77c79b3 100644
--- a/packages/CompanionDeviceManager/res/layout/vendor_header.xml
+++ b/packages/CompanionDeviceManager/res/layout/vendor_header.xml
@@ -33,7 +33,7 @@
         android:layout_width="48dp"
         android:layout_height="48dp"
         android:padding="12dp"
-        android:contentDescription="@string/vendor_icon_description" />
+        android:importantForAccessibility="no" />
 
     <TextView
         android:id="@+id/vendor_header_name"
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 256e0eb..74072e9 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -132,11 +132,8 @@
     <!-- Text of the permission sync explanation in the confirmation dialog. [CHAR LIMIT=NONE] -->
     <string name="permission_sync_summary">This may include &lt;strong&gt;Microphone&lt;/strong&gt;, &lt;strong&gt;Camera&lt;/strong&gt;, and &lt;strong&gt;Location access&lt;/strong&gt;, and other sensitive permissions on &lt;strong&gt;<xliff:g id="companion_device_name" example="My Watch">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;You can change these permissions any time in your Settings on &lt;strong&gt;<xliff:g id="companion_device_name" example="My Watch">%1$s</xliff:g>&lt;/strong&gt;.</string>
 
-    <!--Description for vendor icon [CHAR LIMIT=30]-->
-    <string name="vendor_icon_description">App Icon</string>
-
     <!--Description for information icon [CHAR LIMIT=30]-->
-    <string name="vendor_header_button_description">More Information Button</string>
+    <string name="vendor_header_button_description">More Information</string>
 
     <!-- ================= Permissions ================= -->
 
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index b167377..e85190b 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -36,7 +36,7 @@
     </style>
 
     <style name="DescriptionTitle"
-           parent="@android:style/TextAppearance.DeviceDefault.Medium">
+           parent="@android:style/TextAppearance.DeviceDefault">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:gravity">center</item>
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index fd982f5..6ecd328 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -39,8 +39,7 @@
 
     certificate: "platform",
     privileged: true,
-    platform_apis: false,
-    sdk_version: "system_current",
+    platform_apis: true,
     rename_resources_package: false,
     static_libs: [
         "xz-java",
@@ -57,8 +56,7 @@
 
     certificate: "platform",
     privileged: true,
-    platform_apis: false,
-    sdk_version: "system_current",
+    platform_apis: true,
     rename_resources_package: false,
     overrides: ["PackageInstaller"],
 
@@ -77,8 +75,7 @@
 
     certificate: "platform",
     privileged: true,
-    platform_apis: false,
-    sdk_version: "system_current",
+    platform_apis: true,
     rename_resources_package: false,
     overrides: ["PackageInstaller"],
 
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 9ee6fbd..6ccebfd 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -9,6 +9,7 @@
     <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
     <uses-permission android:name="android.permission.DELETE_PACKAGES" />
     <uses-permission android:name="android.permission.READ_INSTALL_SESSIONS" />
+    <uses-permission android:name="android.permission.READ_INSTALLED_SESSION_PATHS" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS" />
     <uses-permission android:name="android.permission.USE_RESERVED_DISK" />
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index c81e75b..3ba2acb 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -375,16 +375,15 @@
             final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
                     -1 /* defaultValue */);
             final SessionInfo info = mInstaller.getSessionInfo(sessionId);
-            final String resolvedBaseCodePath = intent.getStringExtra(
-                    PackageInstaller.EXTRA_RESOLVED_BASE_PATH);
-            if (info == null || !info.isSealed() || resolvedBaseCodePath == null) {
+            String resolvedPath = info.getResolvedBaseApkPath();
+            if (info == null || !info.isSealed() || resolvedPath == null) {
                 Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
                 finish();
                 return;
             }
 
             mSessionId = sessionId;
-            packageSource = Uri.fromFile(new File(resolvedBaseCodePath));
+            packageSource = Uri.fromFile(new File(resolvedPath));
             mOriginatingURI = null;
             mReferrerURI = null;
             mPendingUserActionReason = info.getPendingUserActionReason();
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index 4fd2b5d..e68ef85 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -14,17 +14,27 @@
  * limitations under the License.
  */
 
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
 buildscript {
     ext {
         BUILD_TOOLS_VERSION = "30.0.3"
         MIN_SDK = 21
         TARGET_SDK = 33
         jetpack_compose_version = '1.4.0-beta01'
-        jetpack_compose_compiler_version = '1.4.0'
+        jetpack_compose_compiler_version = '1.4.4'
     }
 }
 plugins {
-    id 'com.android.application' version '8.0.0-beta05' apply false
-    id 'com.android.library' version '8.0.0-beta05' apply false
-    id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
+    id 'com.android.application' version '8.0.0' apply false
+    id 'com.android.library' version '8.0.0' apply false
+    id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
+}
+subprojects {
+    tasks.withType(KotlinCompile).configureEach {
+        kotlinOptions {
+            jvmTarget = "17"
+            freeCompilerArgs = ["-Xjvm-default=all"]
+        }
+    }
 }
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
index 416a403..212aa7b 100644
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -42,12 +42,8 @@
         }
     }
     compileOptions {
-        sourceCompatibility JavaVersion.VERSION_11
-        targetCompatibility JavaVersion.VERSION_11
-    }
-    kotlinOptions {
-        jvmTarget = '11'
-        freeCompilerArgs = ["-Xjvm-default=all"]
+        sourceCompatibility JavaVersion.VERSION_17
+        targetCompatibility JavaVersion.VERSION_17
     }
     buildFeatures {
         compose true
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 9962c93..fb945a3 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -51,10 +51,6 @@
         sourceCompatibility JavaVersion.VERSION_17
         targetCompatibility JavaVersion.VERSION_17
     }
-    kotlinOptions {
-        jvmTarget = '17'
-        freeCompilerArgs = ["-Xjvm-default=all"]
-    }
     buildFeatures {
         compose true
     }
@@ -79,7 +75,7 @@
     api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
     api "androidx.lifecycle:lifecycle-livedata-ktx"
     api "androidx.lifecycle:lifecycle-runtime-compose"
-    api "androidx.navigation:navigation-compose:2.6.0-alpha07"
+    api "androidx.navigation:navigation-compose:2.6.0-alpha08"
     api "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
     api "com.google.android.material:material:1.7.0-alpha03"
     debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
index f0df9a6..8cbf7cc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
@@ -19,7 +19,6 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.selection.selectableGroup
 import androidx.compose.material.icons.Icons
@@ -40,7 +39,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -60,13 +58,17 @@
 
     Box(
         modifier = Modifier
-            .padding(SettingsDimension.itemPadding)
+            .padding(
+                start = SettingsDimension.itemPaddingStart,
+                top = SettingsDimension.itemPaddingAround,
+                end = SettingsDimension.itemPaddingEnd,
+                bottom = SettingsDimension.itemPaddingAround,
+            )
             .selectableGroup(),
     ) {
         val contentPadding = PaddingValues(horizontal = SettingsDimension.itemPaddingEnd)
         Button(
             onClick = { expanded = true },
-            modifier = Modifier.height(36.dp),
             colors = ButtonDefaults.buttonColors(
                 containerColor = SettingsTheme.colorScheme.spinnerHeaderContainer,
                 contentColor = SettingsTheme.colorScheme.onSpinnerHeaderContainer,
@@ -86,7 +88,6 @@
             expanded = expanded,
             onDismissRequest = { expanded = false },
             modifier = Modifier.background(SettingsTheme.colorScheme.spinnerItemContainer),
-            offset = DpOffset(x = 0.dp, y = 4.dp),
         ) {
             for (option in options) {
                 DropdownMenuItem(
@@ -116,7 +117,9 @@
 ) {
     Text(
         text = option?.text ?: "",
-        modifier = modifier.padding(end = SettingsDimension.itemPaddingEnd),
+        modifier = modifier
+            .padding(end = SettingsDimension.itemPaddingEnd)
+            .padding(vertical = SettingsDimension.itemPaddingAround),
         color = color,
         style = MaterialTheme.typography.labelLarge,
     )
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
index e7f7db2..23a9add 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -41,10 +41,6 @@
         sourceCompatibility JavaVersion.VERSION_17
         targetCompatibility JavaVersion.VERSION_17
     }
-    kotlinOptions {
-        jvmTarget = '17'
-        freeCompilerArgs = ["-Xjvm-default=all"]
-    }
     buildFeatures {
         compose true
     }
diff --git a/packages/SettingsLib/res/drawable/dialog_btn_filled.xml b/packages/SettingsLib/res/drawable/dialog_btn_filled.xml
new file mode 100644
index 0000000..14cb1de9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/dialog_btn_filled.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+       android:insetTop="@dimen/dialog_button_vertical_inset"
+       android:insetBottom="@dimen/dialog_button_vertical_inset">
+    <ripple android:color="?android:attr/colorControlHighlight">
+        <item android:id="@android:id/mask">
+            <shape android:shape="rectangle">
+                <solid android:color="@android:color/white"/>
+                <corners android:radius="?android:attr/buttonCornerRadius"/>
+            </shape>
+        </item>
+        <item>
+            <shape android:shape="rectangle">
+                <corners android:radius="?android:attr/buttonCornerRadius"/>
+                <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+                <padding android:left="@dimen/dialog_button_horizontal_padding"
+                         android:top="@dimen/dialog_button_vertical_padding"
+                         android:right="@dimen/dialog_button_horizontal_padding"
+                         android:bottom="@dimen/dialog_button_vertical_padding"/>
+            </shape>
+        </item>
+    </ripple>
+</inset>
diff --git a/packages/SettingsLib/res/drawable/dialog_btn_outline.xml b/packages/SettingsLib/res/drawable/dialog_btn_outline.xml
new file mode 100644
index 0000000..1e77759
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/dialog_btn_outline.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+       android:insetTop="@dimen/dialog_button_vertical_inset"
+       android:insetBottom="@dimen/dialog_button_vertical_inset">
+    <ripple android:color="?android:attr/colorControlHighlight">
+        <item android:id="@android:id/mask">
+            <shape android:shape="rectangle">
+                <solid android:color="@android:color/white"/>
+                <corners android:radius="?android:attr/buttonCornerRadius"/>
+            </shape>
+        </item>
+        <item>
+            <shape android:shape="rectangle">
+                <corners android:radius="?android:attr/buttonCornerRadius"/>
+                <solid android:color="@android:color/transparent"/>
+                <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant"
+                        android:width="1dp"
+                />
+                <padding android:left="@dimen/dialog_button_horizontal_padding"
+                         android:top="@dimen/dialog_button_vertical_padding"
+                         android:right="@dimen/dialog_button_horizontal_padding"
+                         android:bottom="@dimen/dialog_button_vertical_padding"/>
+            </shape>
+        </item>
+    </ripple>
+</inset>
diff --git a/packages/SettingsLib/res/drawable/ic_admin_panel_settings.xml b/packages/SettingsLib/res/drawable/ic_admin_panel_settings.xml
new file mode 100644
index 0000000..3ea8e9e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_admin_panel_settings.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?android:attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M17,17Q17.625,17 18.062,16.562Q18.5,16.125 18.5,15.5Q18.5,14.875 18.062,14.438Q17.625,14 17,14Q16.375,14 15.938,14.438Q15.5,14.875 15.5,15.5Q15.5,16.125 15.938,16.562Q16.375,17 17,17ZM17,20Q17.775,20 18.425,19.637Q19.075,19.275 19.475,18.675Q18.925,18.35 18.3,18.175Q17.675,18 17,18Q16.325,18 15.7,18.175Q15.075,18.35 14.525,18.675Q14.925,19.275 15.575,19.637Q16.225,20 17,20ZM12,22Q8.525,21.125 6.263,18.012Q4,14.9 4,11.1V5L12,2L20,5V10.675Q19.525,10.475 19.025,10.312Q18.525,10.15 18,10.075V6.4L12,4.15L6,6.4V11.1Q6,12.275 6.312,13.45Q6.625,14.625 7.188,15.688Q7.75,16.75 8.55,17.65Q9.35,18.55 10.325,19.15Q10.6,19.95 11.05,20.675Q11.5,21.4 12.075,21.975Q12.05,21.975 12.038,21.988Q12.025,22 12,22ZM17,22Q14.925,22 13.463,20.538Q12,19.075 12,17Q12,14.925 13.463,13.462Q14.925,12 17,12Q19.075,12 20.538,13.462Q22,14.925 22,17Q22,19.075 20.538,20.538Q19.075,22 17,22ZM12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/layout/dialog_with_icon.xml b/packages/SettingsLib/res/layout/dialog_with_icon.xml
new file mode 100644
index 0000000..9081ca5
--- /dev/null
+++ b/packages/SettingsLib/res/layout/dialog_with_icon.xml
@@ -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.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:gravity="center"
+              android:padding="@dimen/grant_admin_dialog_padding"
+              android:paddingBottom="0dp">
+    <ImageView
+        android:id="@+id/dialog_with_icon_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:contentDescription=""/>
+    <TextView
+        android:id="@+id/dialog_with_icon_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        style="@style/DialogWithIconTitle"
+        android:text="@string/user_grant_admin_title"
+        android:textDirection="locale"/>
+    <TextView
+        android:id="@+id/dialog_with_icon_message"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="10dp"
+        android:gravity="center"
+        style="@style/TextAppearanceSmall"
+        android:text=""
+        android:textDirection="locale"/>
+    <LinearLayout
+        android:id="@+id/custom_layout"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:paddingBottom="0dp">
+    </LinearLayout>
+    <LinearLayout
+        android:id="@+id/button_panel"
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:paddingBottom="0dp">
+        <Button
+            android:id="@+id/button_cancel"
+            style="@style/DialogButtonNegative"
+            android:layout_width="wrap_content"
+            android:buttonCornerRadius="28dp"
+            android:layout_height="wrap_content"
+            android:visibility="gone"/>
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1" >
+        </Space>
+        <Button
+            android:id="@+id/button_back"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@style/DialogButtonNegative"
+            android:buttonCornerRadius="40dp"
+            android:clickable="true"
+            android:focusable="true"
+            android:text="Back"
+            android:visibility="gone"
+        />
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="0.05"
+        >
+        </Space>
+        <Button
+            android:id="@+id/button_ok"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@style/DialogButtonPositive"
+            android:clickable="true"
+            android:focusable="true"
+            android:visibility="gone"
+        />
+    </LinearLayout>
+</LinearLayout>
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index dbfd1c2..e9aded0 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -112,4 +112,13 @@
 
     <!-- Size of grant admin privileges dialog padding -->
     <dimen name="grant_admin_dialog_padding">16dp</dimen>
+
+    <dimen name="dialog_button_horizontal_padding">16dp</dimen>
+    <dimen name="dialog_button_vertical_padding">8dp</dimen>
+    <!-- The button will be 48dp tall, but the background needs to be 36dp tall -->
+    <dimen name="dialog_button_vertical_inset">6dp</dimen>
+    <dimen name="dialog_top_padding">24dp</dimen>
+    <dimen name="dialog_bottom_padding">18dp</dimen>
+    <dimen name="dialog_side_padding">24dp</dimen>
+    <dimen name="dialog_button_bar_top_padding">32dp</dimen>
 </resources>
diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml
index cc60382..2584be7 100644
--- a/packages/SettingsLib/res/values/styles.xml
+++ b/packages/SettingsLib/res/values/styles.xml
@@ -83,4 +83,39 @@
         <item name="android:textDirection">locale</item>
         <item name="android:ellipsize">end</item>
     </style>
+
+    <style name="DialogWithIconTitle" parent="@android:TextAppearance.DeviceDefault.Headline">
+        <item name="android:textSize">@dimen/broadcast_dialog_title_text_size</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textDirection">locale</item>
+        <item name="android:ellipsize">end</item>
+    </style>
+
+    <style name="DialogButtonPositive"  xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+        <item name="android:buttonCornerRadius">0dp</item>
+        <item name="android:background">@drawable/dialog_btn_filled</item>
+        <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:lineHeight">20sp</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
+        <item name="android:stateListAnimator">@null</item>
+        <item name="android:minWidth">0dp</item>
+    </style>
+
+    <style name="DialogButtonNegative">
+        <item name="android:buttonCornerRadius">28dp</item>
+        <item name="android:background">@drawable/dialog_btn_outline</item>
+        <item name="android:buttonCornerRadius">28dp</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:lineHeight">20sp</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
+        <item name="android:stateListAnimator">@null</item>
+        <item name="android:minWidth">0dp</item>
+    </style>
+
+    <style name="TextStyleMessage">
+        <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+        <item name="android:textSize">16dp</item>
+    </style>
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java
index 5326e73..daa3616 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java
@@ -32,9 +32,13 @@
     public static final String EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON =
             "extra_power_save_mode_manual_enabled_reason";
 
+    /** Record the event while enabling power save mode manually. */
+    public static final String EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED =
+            "extra_power_save_mode_manual_enabled";
+
     /** Broadcast action to record battery saver manual enabled reason. */
-    public static final String ACTION_SAVER_MANUAL_ENABLED_REASON =
-            "com.android.settingslib.fuelgauge.ACTION_SAVER_MANUAL_ENABLED_REASON";
+    public static final String ACTION_SAVER_STATE_MANUAL_UPDATE =
+            "com.android.settingslib.fuelgauge.ACTION_SAVER_STATE_MANUAL_UPDATE";
 
     /** An interface for the battery saver manual enable reason. */
     @Retention(RetentionPolicy.SOURCE)
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index e28ada4..c9540c7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -16,7 +16,8 @@
 
 package com.android.settingslib.fuelgauge;
 
-import static com.android.settingslib.fuelgauge.BatterySaverLogging.ACTION_SAVER_MANUAL_ENABLED_REASON;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.ACTION_SAVER_STATE_MANUAL_UPDATE;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED;
 import static com.android.settingslib.fuelgauge.BatterySaverLogging.EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON;
 import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason;
 
@@ -152,9 +153,8 @@
                     sendSystemUiBroadcast(context, ACTION_SHOW_AUTO_SAVER_SUGGESTION,
                             confirmationExtras);
                 }
-                recordBatterySaverEnabledReason(context, reason);
             }
-
+            recordBatterySaverEnabledReason(context, enable, reason);
             return true;
         }
         return false;
@@ -185,11 +185,12 @@
         return true;
     }
 
-    private static void recordBatterySaverEnabledReason(Context context,
+    private static void recordBatterySaverEnabledReason(Context context, boolean enable,
             @SaverManualEnabledReason int reason) {
-        final Bundle enabledReasonExtras = new Bundle(1);
+        final Bundle enabledReasonExtras = new Bundle(2);
         enabledReasonExtras.putInt(EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON, reason);
-        sendSystemUiBroadcast(context, ACTION_SAVER_MANUAL_ENABLED_REASON, enabledReasonExtras);
+        enabledReasonExtras.putBoolean(EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED, enable);
+        sendSystemUiBroadcast(context, ACTION_SAVER_STATE_MANUAL_UPDATE, enabledReasonExtras);
     }
 
     private static void sendSystemUiBroadcast(Context context, String action, Bundle extras) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
new file mode 100644
index 0000000..de48814
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
@@ -0,0 +1,277 @@
+/*
+ * 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.settingslib.utils;
+import android.annotation.IntDef;
+import android.annotation.StringRes;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.settingslib.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class is used to create custom dialog with icon, title, message and custom view that are
+ * horizontally centered.
+ */
+public class CustomDialogHelper {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ICON, TITLE, MESSAGE, LAYOUT, BACK_BUTTON, NEGATIVE_BUTTON, POSITIVE_BUTTON})
+    public @interface LayoutComponent {}
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({BACK_BUTTON, NEGATIVE_BUTTON, POSITIVE_BUTTON})
+    public @interface LayoutButton {}
+
+    public static final int ICON = 0;
+    public static final int TITLE = 1;
+    public static final int MESSAGE = 2;
+    public static final int LAYOUT = 3;
+    public static final int BACK_BUTTON = 4;
+    public static final int NEGATIVE_BUTTON = 5;
+    public static final int POSITIVE_BUTTON = 6;
+    private View mDialogContent;
+    private Dialog mDialog;
+    private Context mContext;
+    private LayoutInflater mLayoutInflater;
+    private ImageView mDialogIcon;
+    private TextView mDialogTitle;
+    private TextView mDialogMessage;
+    private LinearLayout mCustomLayout;
+    private Button mPositiveButton;
+    private Button mNegativeButton;
+    private Button mBackButton;
+
+    public CustomDialogHelper(Context context) {
+        mContext = context;
+        mLayoutInflater = LayoutInflater.from(context);
+        mDialogContent = mLayoutInflater.inflate(R.layout.dialog_with_icon, null);
+        mDialogIcon = mDialogContent.findViewById(R.id.dialog_with_icon_icon);
+        mDialogTitle = mDialogContent.findViewById(R.id.dialog_with_icon_title);
+        mDialogMessage = mDialogContent.findViewById(R.id.dialog_with_icon_message);
+        mCustomLayout = mDialogContent.findViewById(R.id.custom_layout);
+        mPositiveButton = mDialogContent.findViewById(R.id.button_ok);
+        mNegativeButton = mDialogContent.findViewById(R.id.button_cancel);
+        mBackButton = mDialogContent.findViewById(R.id.button_back);
+        createDialog();
+    }
+
+    /**
+     * Creates dialog with content defined in constructor.
+     */
+    private void createDialog() {
+        mDialog = new AlertDialog.Builder(mContext)
+                .setView(mDialogContent)
+                .setCancelable(true)
+                .create();
+        mDialog.getWindow()
+                .setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+    }
+
+    /**
+     * Sets title and listener for positive button.
+     */
+    public CustomDialogHelper setPositiveButton(@StringRes int resid,
+            View.OnClickListener onClickListener) {
+        setButton(POSITIVE_BUTTON, resid, onClickListener);
+        return this;
+    }
+
+    /**
+     * Sets positive button text.
+     */
+    public CustomDialogHelper setPositiveButtonText(@StringRes int resid) {
+        mPositiveButton.setText(resid);
+        return this;
+    }
+
+    /**
+     * Sets title and listener for negative button.
+     */
+    public CustomDialogHelper setNegativeButton(@StringRes int resid,
+            View.OnClickListener onClickListener) {
+        setButton(NEGATIVE_BUTTON, resid, onClickListener);
+        return this;
+    }
+
+    /**
+     * Sets negative button text.
+     */
+    public CustomDialogHelper setNegativeButtonText(@StringRes int resid) {
+        mNegativeButton.setText(resid);
+        return this;
+    }
+
+    /**
+     * Sets title and listener for back button.
+     */
+    public CustomDialogHelper setBackButton(@StringRes int resid,
+            View.OnClickListener onClickListener) {
+        setButton(BACK_BUTTON, resid, onClickListener);
+        return this;
+    }
+
+    /**
+     * Sets title for back button.
+     */
+    public CustomDialogHelper setBackButtonText(@StringRes int resid) {
+        mBackButton.setText(resid);
+        return this;
+    }
+
+    private void setButton(@LayoutButton int whichButton, @StringRes int resid,
+            View.OnClickListener listener) {
+        switch (whichButton) {
+            case POSITIVE_BUTTON :
+                mPositiveButton.setText(resid);
+                mPositiveButton.setVisibility(View.VISIBLE);
+                mPositiveButton.setOnClickListener(listener);
+                break;
+            case NEGATIVE_BUTTON:
+                mNegativeButton.setText(resid);
+                mNegativeButton.setVisibility(View.VISIBLE);
+                mNegativeButton.setOnClickListener(listener);
+                break;
+            case BACK_BUTTON:
+                mBackButton.setText(resid);
+                mBackButton.setVisibility(View.VISIBLE);
+                mBackButton.setOnClickListener(listener);
+                break;
+            default:
+                break;
+        }
+    }
+
+
+    /**
+     * Modifies state of button.
+     * //TODO: modify method to allow setting state for any button.
+     */
+    public CustomDialogHelper setButtonEnabled(boolean enabled) {
+        mPositiveButton.setEnabled(enabled);
+        return this;
+    }
+
+    /**
+     * Sets title of the dialog.
+     */
+    public CustomDialogHelper setTitle(@StringRes int resid) {
+        mDialogTitle.setText(resid);
+        return this;
+    }
+
+    /**
+     * Sets message of the dialog.
+     */
+    public CustomDialogHelper setMessage(@StringRes int resid) {
+        mDialogMessage.setText(resid);
+        return this;
+    }
+
+    /**
+     * Sets icon of the dialog.
+     */
+    public CustomDialogHelper setIcon(Drawable icon) {
+        mDialogIcon.setImageDrawable(icon);
+        return this;
+    }
+
+    /**
+     * Removes all views that were previously added to the custom layout part.
+     */
+    public CustomDialogHelper clearCustomLayout() {
+        mCustomLayout.removeAllViews();
+        return this;
+    }
+
+    /**
+     * Hides custom layout.
+     */
+    public void hideCustomLayout() {
+        mCustomLayout.setVisibility(View.GONE);
+    }
+
+    /**
+     * Shows custom layout.
+     */
+    public void showCustomLayout() {
+        mCustomLayout.setVisibility(View.VISIBLE);
+    }
+
+    /**
+     * Adds view to custom layout.
+     */
+    public CustomDialogHelper addCustomView(View view) {
+        mCustomLayout.addView(view);
+        return this;
+    }
+
+    /**
+     * Returns dialog.
+     */
+    public Dialog getDialog() {
+        return mDialog;
+    }
+
+    /**
+     * Sets visibility of layout component.
+     * @param element part of the layout visibility of which is being changed.
+     * @param isVisible true if visibility is set to View.VISIBLE
+     * @return this
+     */
+    public CustomDialogHelper setVisibility(@LayoutComponent int element, boolean isVisible) {
+        int visibility;
+        if (isVisible) {
+            visibility = View.VISIBLE;
+        } else {
+            visibility = View.GONE;
+        }
+        switch (element) {
+            case ICON:
+                mDialogIcon.setVisibility(visibility);
+                break;
+            case TITLE:
+                mDialogTitle.setVisibility(visibility);
+                break;
+            case MESSAGE:
+                mDialogMessage.setVisibility(visibility);
+                break;
+            case BACK_BUTTON:
+                mBackButton.setVisibility(visibility);
+                break;
+            case NEGATIVE_BUTTON:
+                mNegativeButton.setVisibility(visibility);
+                break;
+            case POSITIVE_BUTTON:
+                mPositiveButton.setVisibility(visibility);
+                break;
+            default:
+                break;
+        }
+        return this;
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
index 7a26f76..80301c0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
@@ -16,14 +16,16 @@
 
 package com.android.settingslib.fuelgauge;
 
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.ACTION_SAVER_STATE_MANUAL_UPDATE;
 import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_UNKNOWN;
+import static com.android.settingslib.fuelgauge.BatterySaverUtils.ACTION_SHOW_AUTO_SAVER_SUGGESTION;
+import static com.android.settingslib.fuelgauge.BatterySaverUtils.ACTION_SHOW_START_SAVER_CONFIRMATION;
 import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_NO_SCHEDULE;
 import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
@@ -40,10 +42,13 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 
+import java.util.List;
+
 @RunWith(RobolectricTestRunner.class)
 public class BatterySaverUtilsTest {
     private static final int BATTERY_SAVER_THRESHOLD_1 = 15;
@@ -68,7 +73,7 @@
     }
 
     @Test
-    public void testSetPowerSaveMode_enable_firstCall_needWarning() {
+    public void testSetPowerSaveMode_enableWithWarning_firstCall_needConfirmationWarning() {
         Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null");
         Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
@@ -76,9 +81,12 @@
         assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
                 SAVER_ENABLED_UNKNOWN)).isFalse();
 
-        verify(mMockContext, times(1)).sendBroadcast(any(Intent.class));
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).sendBroadcast(intentCaptor.capture());
         verify(mMockPowerManager, times(0)).setPowerSaveModeEnabled(anyBoolean());
 
+        assertThat(intentCaptor.getValue().getAction()).isEqualTo(
+                ACTION_SHOW_START_SAVER_CONFIRMATION);
         // They shouldn't have changed.
         assertEquals(-1, Secure.getInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, -1));
         assertEquals(-1,
@@ -88,7 +96,7 @@
     }
 
     @Test
-    public void testSetPowerSaveMode_enable_secondCall_needWarning() {
+    public void testSetPowerSaveMode_enableWithWarning_secondCall_expectUpdateIntent() {
         // Already acked.
         Secure.putInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1);
         Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
@@ -97,8 +105,12 @@
         assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
                 SAVER_ENABLED_UNKNOWN)).isTrue();
 
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).sendBroadcast(intentCaptor.capture());
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
 
+        assertThat(intentCaptor.getValue().getAction()).isEqualTo(
+                ACTION_SAVER_STATE_MANUAL_UPDATE);
         assertEquals(1, Secure.getInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, -1));
         assertEquals(1,
                 Secure.getInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, -1));
@@ -107,7 +119,7 @@
     }
 
     @Test
-    public void testSetPowerSaveMode_enable_thridCall_needWarning() {
+    public void testSetPowerSaveMode_enableWithWarning_thirdCall_expectUpdateIntent() {
         // Already acked.
         Secure.putInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1);
         Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
@@ -116,8 +128,12 @@
         assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
                 SAVER_ENABLED_UNKNOWN)).isTrue();
 
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).sendBroadcast(intentCaptor.capture());
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
 
+        assertThat(intentCaptor.getValue().getAction()).isEqualTo(
+                ACTION_SAVER_STATE_MANUAL_UPDATE);
         assertEquals(1, Secure.getInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, -1));
         assertEquals(1,
                 Secure.getInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, -1));
@@ -126,7 +142,31 @@
     }
 
     @Test
-    public void testSetPowerSaveMode_enable_firstCall_noWarning() {
+    public void testSetPowerSaveMode_enableWithWarning_5thCall_needAutoSuggestionWarning() {
+        // Already acked.
+        Secure.putInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1);
+        Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
+        Secure.putInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 3);
+
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(2)).sendBroadcast(intentCaptor.capture());
+        verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
+
+        List<Intent> values = intentCaptor.getAllValues();
+        assertThat(values.get(0).getAction()).isEqualTo(ACTION_SHOW_AUTO_SAVER_SUGGESTION);
+        assertThat(values.get(1).getAction()).isEqualTo(ACTION_SAVER_STATE_MANUAL_UPDATE);
+        assertEquals(1, Secure.getInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, -1));
+        assertEquals(1,
+                Secure.getInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, -1));
+        assertEquals(4,
+                Secure.getInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, -2));
+    }
+
+    @Test
+    public void testSetPowerSaveMode_enableWithoutWarning_expectUpdateIntent() {
         Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null");
         Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
@@ -134,8 +174,12 @@
         assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false,
                 SAVER_ENABLED_UNKNOWN)).isTrue();
 
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).sendBroadcast(intentCaptor.capture());
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
 
+        assertThat(intentCaptor.getValue().getAction()).isEqualTo(
+                ACTION_SAVER_STATE_MANUAL_UPDATE);
         assertEquals(1, Secure.getInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, -1));
         assertEquals(1,
                 Secure.getInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, -1));
@@ -143,43 +187,13 @@
     }
 
     @Test
-    public void testSetPowerSaveMode_disable_firstCall_noWarning() {
-        Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null");
-        Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
-        Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
-
-        // When disabling, needFirstTimeWarning doesn't matter.
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false,
-                SAVER_ENABLED_UNKNOWN)).isTrue();
-
-        verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
-        verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
-
-        assertEquals(-1, Secure.getInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, -1));
-        assertEquals(-1,
-                Secure.getInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, -1));
-        assertEquals(-2,
-                Secure.getInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, -2));
+    public void testSetPowerSaveMode_disableWithoutWarning_expectUpdateIntent() {
+        verifyDisablePowerSaveMode(/* needFirstTimeWarning= */ false);
     }
 
     @Test
-    public void testSetPowerSaveMode_disable_firstCall_needWarning() {
-        Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null");
-        Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
-        Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
-
-        // When disabling, needFirstTimeWarning doesn't matter.
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true,
-                SAVER_ENABLED_UNKNOWN)).isTrue();
-
-        verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
-        verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
-
-        assertEquals(-1, Secure.getInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, -1));
-        assertEquals(-1,
-                Secure.getInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, -1));
-        assertEquals(-2,
-                Secure.getInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, -2));
+    public void testSetPowerSaveMode_disableWithWarning_expectUpdateIntent() {
+        verifyDisablePowerSaveMode(/* needFirstTimeWarning= */ true);
     }
 
     @Test
@@ -256,4 +270,26 @@
                 .isEqualTo(20);
 
     }
+
+    private void verifyDisablePowerSaveMode(boolean needFirstTimeWarning) {
+        Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null");
+        Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
+        Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
+
+        // When disabling, needFirstTimeWarning doesn't matter.
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, needFirstTimeWarning,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).sendBroadcast(intentCaptor.capture());
+        verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
+
+        assertThat(intentCaptor.getValue().getAction()).isEqualTo(
+                ACTION_SAVER_STATE_MANUAL_UPDATE);
+        assertEquals(-1, Secure.getInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, -1));
+        assertEquals(-1,
+                Secure.getInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, -1));
+        assertEquals(-2,
+                Secure.getInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, -2));
+    }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 6a5535d..e4cc9f1 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -100,5 +100,6 @@
         Settings.System.CAMERA_FLASH_NOTIFICATION,
         Settings.System.SCREEN_FLASH_NOTIFICATION,
         Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
+        Settings.System.SMOOTH_DISPLAY
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 85623b2..4b72063 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -226,5 +226,6 @@
         VALIDATORS.put(System.CAMERA_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION_COLOR, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(System.SMOOTH_DISPLAY, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 6d375ac..48259e1 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -46,12 +46,16 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Locale;
+import java.util.Set;
 
 public class SettingsHelper {
     private static final String TAG = "SettingsHelper";
     private static final String SILENT_RINGTONE = "_silent";
     private static final String SETTINGS_REPLACED_KEY = "backup_skip_user_facing_data";
     private static final String SETTING_ORIGINAL_KEY_SUFFIX = "_original";
+    private static final String UNICODE_LOCALE_EXTENSION_FW = "fw";
+    private static final String UNICODE_LOCALE_EXTENSION_MU = "mu";
+    private static final String UNICODE_LOCALE_EXTENSION_NU = "nu";
     private static final float FLOAT_TOLERANCE = 0.01f;
 
     /** See frameworks/base/core/res/res/values/config.xml#config_longPressOnPowerBehavior **/
@@ -97,6 +101,25 @@
         sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_AUTO_ADDED_TILES);
     }
 
+    private static final ArraySet<String> UNICODE_LOCALE_SUPPORTED_EXTENSIONS = new ArraySet<>();
+
+    /**
+     * Current supported extensions are fw (first day of week) and mu (temperature unit) extension.
+     * User can set these extensions in Settings app, and it will be appended to the locale,
+     * for example: zh-Hant-TW-u-fw-mon-mu-celsius. So after the factory reset, these extensions
+     * should be restored as well because they are set by users.
+     * We do not put the nu (numbering system) extension here because it is an Android supported
+     * extension and defined in some particular locales, for example:
+     * ar-Arab-MA-u-nu-arab and ar-Arab-YE-u-nu-latn. See
+     * <code>frameworks/base/core/res/res/values/locale_config.xml</code>
+     * The nu extension should not be appended to the current/restored locale after factory reset
+     * if the current/restored locale does not have it.
+     */
+    static {
+        UNICODE_LOCALE_SUPPORTED_EXTENSIONS.add(UNICODE_LOCALE_EXTENSION_FW);
+        UNICODE_LOCALE_SUPPORTED_EXTENSIONS.add(UNICODE_LOCALE_EXTENSION_MU);
+    }
+
     private interface SettingsLookup {
         public String lookup(ContentResolver resolver, String name, int userHandle);
     }
@@ -500,20 +523,25 @@
             allLocales.put(toFullLocale(locale), locale);
         }
 
+        // After restoring to reset locales, need to get extensions from restored locale. Get the
+        // first restored locale to check its extension.
+        final Locale restoredLocale = restore.isEmpty()
+                ? Locale.ROOT
+                : restore.get(0);
         final ArrayList<Locale> filtered = new ArrayList<>(current.size());
         for (int i = 0; i < current.size(); i++) {
-            final Locale locale = current.get(i);
+            Locale locale = copyExtensionToTargetLocale(restoredLocale, current.get(i));
             allLocales.remove(toFullLocale(locale));
             filtered.add(locale);
         }
 
         for (int i = 0; i < restore.size(); i++) {
-            final Locale locale = allLocales.remove(toFullLocale(restore.get(i)));
-            if (locale != null) {
-                filtered.add(locale);
+            final Locale restoredLocaleWithExtension = copyExtensionToTargetLocale(restoredLocale,
+                    getFilteredLocale(restore.get(i), allLocales));
+            if (restoredLocaleWithExtension != null) {
+                filtered.add(restoredLocaleWithExtension);
             }
         }
-
         if (filtered.size() == current.size()) {
             return current;  // Nothing added to current locale list.
         }
@@ -521,6 +549,45 @@
         return new LocaleList(filtered.toArray(new Locale[filtered.size()]));
     }
 
+    private static Locale copyExtensionToTargetLocale(Locale restoredLocale,
+            Locale targetLocale) {
+        if (!restoredLocale.hasExtensions()) {
+            return targetLocale;
+        }
+
+        if (targetLocale == null) {
+            return null;
+        }
+
+        Locale.Builder builder = new Locale.Builder()
+                .setLocale(targetLocale);
+        Set<String> unicodeLocaleKeys = restoredLocale.getUnicodeLocaleKeys();
+        unicodeLocaleKeys.stream().forEach(key -> {
+            // Copy all supported extensions from restored locales except "nu" extension. The "nu"
+            // extension has been added in #getFilteredLocale(Locale, HashMap<Locale, Locale>)
+            // already, we don't need to add it again.
+            if (UNICODE_LOCALE_SUPPORTED_EXTENSIONS.contains(key)) {
+                builder.setUnicodeLocaleKeyword(key, restoredLocale.getUnicodeLocaleType(key));
+            }
+        });
+        return builder.build();
+    }
+
+    private static Locale getFilteredLocale(Locale restoreLocale,
+            HashMap<Locale, Locale> allLocales) {
+        Locale locale = allLocales.remove(toFullLocale(restoreLocale));
+        if (locale != null) {
+            return locale;
+        }
+
+        Locale filteredLocale = new Locale.Builder()
+                .setLocale(restoreLocale.stripExtensions())
+                .setUnicodeLocaleKeyword(UNICODE_LOCALE_EXTENSION_NU,
+                        restoreLocale.getUnicodeLocaleType(UNICODE_LOCALE_EXTENSION_NU))
+                .build();
+        return allLocales.remove(toFullLocale(filteredLocale));
+    }
+
     /**
      * Sets the locale specified. Input data is the byte representation of comma separated
      * multiple BCP-47 language tags. For backwards compatibility, strings of the form
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 284b06b..d1bd5e6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -34,6 +34,7 @@
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 import static com.android.internal.accessibility.util.AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM;
+import static com.android.internal.display.RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE;
 import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX;
 import static com.android.providers.settings.SettingsState.getTypeFromKey;
 import static com.android.providers.settings.SettingsState.getUserIdFromKey;
@@ -3748,7 +3749,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 218;
+            private static final int SETTINGS_VERSION = 219;
 
             private final int mUserId;
 
@@ -5673,7 +5674,7 @@
                             providers.addAll(Arrays.asList(resources.getStringArray(resourceId)));
                         } catch (Resources.NotFoundException e) {
                             Slog.w(LOG_TAG,
-                                "Get default array Cred Provider not found: " + e.toString());
+                                    "Get default array Cred Provider not found: " + e.toString());
                         }
                         try {
                             final String storedValue = resources.getString(resourceId);
@@ -5682,7 +5683,7 @@
                             }
                         } catch (Resources.NotFoundException e) {
                             Slog.w(LOG_TAG,
-                                "Get default Cred Provider not found: " + e.toString());
+                                    "Get default Cred Provider not found: " + e.toString());
                         }
 
                         if (!providers.isEmpty()) {
@@ -5731,8 +5732,8 @@
                     final Setting currentSetting = secureSettings
                             .getSettingLocked(Settings.Secure.CREDENTIAL_SERVICE);
                     if (currentSetting.isNull()) {
-                        final int resourceId =
-                            com.android.internal.R.array.config_defaultCredentialProviderService;
+                        final int resourceId = com.android.internal.R.array
+                                .config_defaultCredentialProviderService;
                         final Resources resources = getContext().getResources();
                         // If the config has not be defined we might get an exception.
                         final List<String> providers = new ArrayList<>();
@@ -5740,7 +5741,7 @@
                             providers.addAll(Arrays.asList(resources.getStringArray(resourceId)));
                         } catch (Resources.NotFoundException e) {
                             Slog.w(LOG_TAG,
-                                "Get default array Cred Provider not found: " + e.toString());
+                                    "Get default array Cred Provider not found: " + e.toString());
                         }
 
                         if (!providers.isEmpty()) {
@@ -5839,6 +5840,47 @@
                     currentVersion = 218;
                 }
 
+                // v218: Convert Smooth Display and Force Peak Refresh Rate to a boolean
+                if (currentVersion == 218) {
+                    final String peakRefreshRateSettingName = "peak_refresh_rate";
+                    final String minRefreshRateSettingName = "min_refresh_rate";
+
+                    final SettingsState systemSettings = getSystemSettingsLocked(userId);
+                    final Setting peakRefreshRateSetting =
+                            systemSettings.getSettingLocked(peakRefreshRateSettingName);
+                    final Setting minRefreshRateSetting =
+                            systemSettings.getSettingLocked(minRefreshRateSettingName);
+
+                    float peakRefreshRate = DEFAULT_REFRESH_RATE;
+                    float minRefreshRate = 0;
+                    try {
+                        if (!peakRefreshRateSetting.isNull()) {
+                            peakRefreshRate = Float.parseFloat(peakRefreshRateSetting.getValue());
+                        }
+                    } catch (NumberFormatException e) {
+                        // Do nothing. Overwrite with default value.
+                    }
+                    try {
+                        if (!minRefreshRateSetting.isNull()) {
+                            minRefreshRate = Float.parseFloat(minRefreshRateSetting.getValue());
+                        }
+                    } catch (NumberFormatException e) {
+                        // Do nothing. Overwrite with default value.
+                    }
+
+                    systemSettings.deleteSettingLocked(peakRefreshRateSettingName);
+                    systemSettings.deleteSettingLocked(minRefreshRateSettingName);
+
+                    systemSettings.insertSettingLocked(Settings.System.SMOOTH_DISPLAY,
+                            peakRefreshRate > DEFAULT_REFRESH_RATE ? "1" : "0", /* tag= */ null,
+                            /* makeDefault= */ false, SettingsState.SYSTEM_PACKAGE_NAME);
+                    systemSettings.insertSettingLocked(Settings.System.FORCE_PEAK_REFRESH_RATE,
+                            minRefreshRate > 0 ? "1" : "0", /* tag= */ null,
+                            /* makeDefault= */ false, SettingsState.SYSTEM_PACKAGE_NAME);
+
+                    currentVersion = 219;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 706666c..36aa2ac 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -97,8 +97,7 @@
                     Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
                     Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
                     Settings.System.DESKTOP_MODE, // developer setting for internal prototyping
-                    Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
-                    Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities
+                    Settings.System.FORCE_PEAK_REFRESH_RATE, // depends on hardware capabilities
                     Settings.System.SCREEN_BRIGHTNESS_FLOAT,
                     Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
                     Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
index ee76dbf..bc81c44 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
@@ -299,12 +299,42 @@
                         LocaleList.forLanguageTags("en-US"),  // current
                         new String[] { "en-US", "zh-Hans-CN" }));  // supported
 
-        // Old langauge code should be updated.
+        // Old language code should be updated.
         assertEquals(LocaleList.forLanguageTags("en-US,he-IL,id-ID,yi"),
                 SettingsHelper.resolveLocales(
                         LocaleList.forLanguageTags("iw-IL,in-ID,ji"),  // restore
                         LocaleList.forLanguageTags("en-US"),  // current
                         new String[] { "he-IL", "id-ID", "yi" }));  // supported
+
+        // No matter the current locale has "nu" extension or not, if the restored locale has fw
+        // (first day of week) or mu(temperature unit) extension, we should restore fw or mu
+        // extensions as well and append these to restore and current locales.
+        assertEquals(LocaleList.forLanguageTags(
+                "en-US-u-fw-mon-mu-celsius,zh-Hant-TW-u-fw-mon-mu-celsius"),
+                SettingsHelper.resolveLocales(
+                        LocaleList.forLanguageTags("zh-Hant-TW-u-fw-mon-mu-celsius"),  // restore
+                        LocaleList.forLanguageTags("en-US"),  // current
+                        new String[] { "en-US", "zh-Hant-TW" }));  // supported
+
+        // No matter the current locale has "nu" extension or not, if the restored locale has fw
+        // (first day of week) or mu(temperature unit) extension, we should restore fw or mu
+        // extensions as well and append these to restore and current locales.
+        assertEquals(LocaleList.forLanguageTags(
+                "fa-Arab-AF-u-nu-latn-fw-mon-mu-celsius,zh-Hant-TW-u-fw-mon-mu-celsius"),
+                SettingsHelper.resolveLocales(
+                        LocaleList.forLanguageTags("zh-Hant-TW-u-fw-mon-mu-celsius"),  // restore
+                        LocaleList.forLanguageTags("fa-Arab-AF-u-nu-latn"),  // current
+                        new String[] { "fa-Arab-AF-u-nu-latn", "zh-Hant-TW" }));  // supported
+
+        // If the restored locale only has nu extension, we should not restore the nu extensions to
+        // current locales.
+        assertEquals(LocaleList.forLanguageTags("zh-Hant-TW,fa-Arab-AF-u-nu-latn"),
+                SettingsHelper.resolveLocales(
+                        LocaleList.forLanguageTags("fa-Arab-AF-u-nu-latn"),  // restore
+                        LocaleList.forLanguageTags("zh-Hant-TW"),  // current
+                        new String[] { "fa-Arab-AF-u-nu-latn", "zh-Hant-TW" }));  // supported
+
+
     }
 
     @Test
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 62c6c1d..b95a149 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -173,6 +173,7 @@
         "androidx.palette_palette",
         "androidx.legacy_legacy-preference-v14",
         "androidx.leanback_leanback",
+        "androidx.tracing_tracing",
         "androidx.slice_slice-core",
         "androidx.slice_slice-view",
         "androidx.slice_slice-builders",
@@ -227,26 +228,33 @@
 filegroup {
     name: "SystemUI-tests-robolectric-pilots",
     srcs: [
+        /* Keyguard converted tests */
         // data
         "tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt",
         "tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt",
         "tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt",
         "tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt",
         "tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt",
         "tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt",
         "tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt",
         "tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt",
         "tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt",
         "tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt",
         "tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt",
         // domain
         "tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt",
-        "tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt",
         // ui
         "tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt",
@@ -255,8 +263,13 @@
         "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt",
         "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt",
         "tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt",
+        // Keyguard helper
+        "tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt",
+        "tests/src/com/android/systemui/dump/LogBufferHelper.kt",
+        "tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java",
+        "tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt",
 
-        // Biometric
+        /* Biometric converted tests */
         "tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt",
         "tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt",
         "tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 24de487..4652ef1 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -982,6 +982,8 @@
             </intent-filter>
         </activity>
 
+        <service android:name=".notetask.NoteTaskControllerUpdateService" />
+
         <activity
             android:name=".notetask.shortcut.LaunchNoteTaskActivity"
             android:exported="true"
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 5b5871f..8eb012d 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -43,6 +43,7 @@
         "androidx.core_core-ktx",
         "androidx.annotation_annotation",
         "SystemUIShaderLib",
+        "animationlib",
     ],
 
     manifest: "AndroidManifest.xml",
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 296c2ae..2e80379 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -39,9 +39,9 @@
 import android.view.animation.PathInterpolator
 import androidx.annotation.BinderThread
 import androidx.annotation.UiThread
+import com.android.app.animation.Interpolators
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.policy.ScreenDecorationsUtils
-import java.lang.IllegalArgumentException
 import kotlin.math.roundToInt
 
 private const val TAG = "ActivityLaunchAnimator"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 42a8636..48dd08f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -33,10 +33,10 @@
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
 import android.widget.FrameLayout
+import com.android.app.animation.Interpolators
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.jank.InteractionJankMonitor.CujType
 import com.android.systemui.util.registerAnimationOnBackInvoked
-import java.lang.IllegalArgumentException
 import kotlin.math.roundToInt
 
 private const val TAG = "DialogLaunchAnimator"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
deleted file mode 100644
index 9dbb920..0000000
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2016 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.animation;
-
-import android.graphics.Path;
-import android.util.MathUtils;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.BounceInterpolator;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.PathInterpolator;
-
-/**
- * Utility class to receive interpolators from.
- *
- * Make sure that changes made to this class are also reflected in {@link InterpolatorsAndroidX}.
- * Please consider using the androidx dependencies featuring better testability altogether.
- */
-public class Interpolators {
-
-    /*
-     * ============================================================================================
-     * Emphasized interpolators.
-     * ============================================================================================
-     */
-
-    /**
-     * The default emphasized interpolator. Used for hero / emphasized movement of content.
-     */
-    public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
-
-    /**
-     * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
-     * is disappearing e.g. when moving off screen.
-     */
-    public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
-            0.3f, 0f, 0.8f, 0.15f);
-
-    /**
-     * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
-     * is appearing e.g. when coming from off screen
-     */
-    public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
-            0.05f, 0.7f, 0.1f, 1f);
-
-
-    /*
-     * ============================================================================================
-     * Standard interpolators.
-     * ============================================================================================
-     */
-
-    /**
-     * The standard interpolator that should be used on every normal animation
-     */
-    public static final Interpolator STANDARD = new PathInterpolator(
-            0.2f, 0f, 0f, 1f);
-
-    /**
-     * The standard accelerating interpolator that should be used on every regular movement of
-     * content that is disappearing e.g. when moving off screen.
-     */
-    public static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(
-            0.3f, 0f, 1f, 1f);
-
-    /**
-     * The standard decelerating interpolator that should be used on every regular movement of
-     * content that is appearing e.g. when coming from off screen.
-     */
-    public static final Interpolator STANDARD_DECELERATE = new PathInterpolator(
-            0f, 0f, 0f, 1f);
-
-    /*
-     * ============================================================================================
-     * Legacy
-     * ============================================================================================
-     */
-
-    /**
-     * The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN.
-     */
-    public static final Interpolator LEGACY = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
-
-    /**
-     * The default legacy accelerating interpolator as defined in Material 1.
-     * Also known as FAST_OUT_LINEAR_IN.
-     */
-    public static final Interpolator LEGACY_ACCELERATE = new PathInterpolator(0.4f, 0f, 1f, 1f);
-
-    /**
-     * The default legacy decelerating interpolator as defined in Material 1.
-     * Also known as LINEAR_OUT_SLOW_IN.
-     */
-    public static final Interpolator LEGACY_DECELERATE = new PathInterpolator(0f, 0f, 0.2f, 1f);
-
-    /**
-     * Linear interpolator. Often used if the interpolator is for different properties who need
-     * different interpolations.
-     */
-    public static final Interpolator LINEAR = new LinearInterpolator();
-
-    /*
-    * ============================================================================================
-    * Custom interpolators
-    * ============================================================================================
-    */
-
-    public static final Interpolator FAST_OUT_SLOW_IN = LEGACY;
-    public static final Interpolator FAST_OUT_LINEAR_IN = LEGACY_ACCELERATE;
-    public static final Interpolator LINEAR_OUT_SLOW_IN = LEGACY_DECELERATE;
-
-    /**
-     * Like {@link #FAST_OUT_SLOW_IN}, but used in case the animation is played in reverse (i.e. t
-     * goes from 1 to 0 instead of 0 to 1).
-     */
-    public static final Interpolator FAST_OUT_SLOW_IN_REVERSE =
-            new PathInterpolator(0.8f, 0f, 0.6f, 1f);
-    public static final Interpolator SLOW_OUT_LINEAR_IN = new PathInterpolator(0.8f, 0f, 1f, 1f);
-    public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
-    public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
-    public static final Interpolator ACCELERATE = new AccelerateInterpolator();
-    public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
-    public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
-    public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f);
-    public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
-    public static final Interpolator ICON_OVERSHOT_LESS = new PathInterpolator(0.4f, 0f, 0.2f,
-            1.1f);
-    public static final Interpolator PANEL_CLOSE_ACCELERATED = new PathInterpolator(0.3f, 0, 0.5f,
-            1);
-    public static final Interpolator BOUNCE = new BounceInterpolator();
-    /**
-     * For state transitions on the control panel that lives in GlobalActions.
-     */
-    public static final Interpolator CONTROL_STATE = new PathInterpolator(0.4f, 0f, 0.2f,
-            1.0f);
-
-    /**
-     * Interpolator to be used when animating a move based on a click. Pair with enough duration.
-     */
-    public static final Interpolator TOUCH_RESPONSE =
-            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
-
-    /**
-     * Like {@link #TOUCH_RESPONSE}, but used in case the animation is played in reverse (i.e. t
-     * goes from 1 to 0 instead of 0 to 1).
-     */
-    public static final Interpolator TOUCH_RESPONSE_REVERSE =
-            new PathInterpolator(0.9f, 0f, 0.7f, 1f);
-
-    /*
-     * ============================================================================================
-     * Functions / Utilities
-     * ============================================================================================
-     */
-
-    /**
-     * Calculate the amount of overshoot using an exponential falloff function with desired
-     * properties, where the overshoot smoothly transitions at the 1.0f boundary into the
-     * overshoot, retaining its acceleration.
-     *
-     * @param progress a progress value going from 0 to 1
-     * @param overshootAmount the amount > 0 of overshoot desired. A value of 0.1 means the max
-     *                        value of the overall progress will be at 1.1.
-     * @param overshootStart the point in (0,1] where the result should reach 1
-     * @return the interpolated overshoot
-     */
-    public static float getOvershootInterpolation(float progress, float overshootAmount,
-            float overshootStart) {
-        if (overshootAmount == 0.0f || overshootStart == 0.0f) {
-            throw new IllegalArgumentException("Invalid values for overshoot");
-        }
-        float b = MathUtils.log((overshootAmount + 1) / (overshootAmount)) / overshootStart;
-        return MathUtils.max(0.0f,
-                (float) (1.0f - Math.exp(-b * progress)) * (overshootAmount + 1.0f));
-    }
-
-    /**
-     * Similar to {@link #getOvershootInterpolation(float, float, float)} but the overshoot
-     * starts immediately here, instead of first having a section of non-overshooting
-     *
-     * @param progress a progress value going from 0 to 1
-     */
-    public static float getOvershootInterpolation(float progress) {
-        return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress)));
-    }
-
-    // Create the default emphasized interpolator
-    private static PathInterpolator createEmphasizedInterpolator() {
-        Path path = new Path();
-        // Doing the same as fast_out_extra_slow_in
-        path.moveTo(0f, 0f);
-        path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
-        path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
-        return new PathInterpolator(path);
-    }
-}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java b/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
deleted file mode 100644
index 8da87feb..0000000
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
+++ /dev/null
@@ -1,219 +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.systemui.animation;
-
-import android.graphics.Path;
-import android.util.MathUtils;
-
-import androidx.core.animation.AccelerateDecelerateInterpolator;
-import androidx.core.animation.AccelerateInterpolator;
-import androidx.core.animation.BounceInterpolator;
-import androidx.core.animation.DecelerateInterpolator;
-import androidx.core.animation.Interpolator;
-import androidx.core.animation.LinearInterpolator;
-import androidx.core.animation.PathInterpolator;
-
-/**
- * Utility class to receive interpolators from. (androidx compatible version)
- *
- * This is the androidx compatible version of {@link Interpolators}. Make sure that changes made to
- * this class are also reflected in {@link Interpolators}.
- *
- * Using the androidx versions of {@link androidx.core.animation.ValueAnimator} or
- * {@link androidx.core.animation.ObjectAnimator} improves animation testability. This file provides
- * the androidx compatible versions of the interpolators defined in {@link Interpolators}.
- * AnimatorTestRule can be used in Tests to manipulate the animation under test (e.g. artificially
- * advancing the time).
- */
-public class InterpolatorsAndroidX {
-
-    /*
-     * ============================================================================================
-     * Emphasized interpolators.
-     * ============================================================================================
-     */
-
-    /**
-     * The default emphasized interpolator. Used for hero / emphasized movement of content.
-     */
-    public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
-
-    /**
-     * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
-     * is disappearing e.g. when moving off screen.
-     */
-    public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
-            0.3f, 0f, 0.8f, 0.15f);
-
-    /**
-     * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
-     * is appearing e.g. when coming from off screen
-     */
-    public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
-            0.05f, 0.7f, 0.1f, 1f);
-
-
-    /*
-     * ============================================================================================
-     * Standard interpolators.
-     * ============================================================================================
-     */
-
-    /**
-     * The standard interpolator that should be used on every normal animation
-     */
-    public static final Interpolator STANDARD = new PathInterpolator(
-            0.2f, 0f, 0f, 1f);
-
-    /**
-     * The standard accelerating interpolator that should be used on every regular movement of
-     * content that is disappearing e.g. when moving off screen.
-     */
-    public static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(
-            0.3f, 0f, 1f, 1f);
-
-    /**
-     * The standard decelerating interpolator that should be used on every regular movement of
-     * content that is appearing e.g. when coming from off screen.
-     */
-    public static final Interpolator STANDARD_DECELERATE = new PathInterpolator(
-            0f, 0f, 0f, 1f);
-
-    /*
-     * ============================================================================================
-     * Legacy
-     * ============================================================================================
-     */
-
-    /**
-     * The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN.
-     */
-    public static final Interpolator LEGACY = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
-
-    /**
-     * The default legacy accelerating interpolator as defined in Material 1.
-     * Also known as FAST_OUT_LINEAR_IN.
-     */
-    public static final Interpolator LEGACY_ACCELERATE = new PathInterpolator(0.4f, 0f, 1f, 1f);
-
-    /**
-     * The default legacy decelerating interpolator as defined in Material 1.
-     * Also known as LINEAR_OUT_SLOW_IN.
-     */
-    public static final Interpolator LEGACY_DECELERATE = new PathInterpolator(0f, 0f, 0.2f, 1f);
-
-    /**
-     * Linear interpolator. Often used if the interpolator is for different properties who need
-     * different interpolations.
-     */
-    public static final Interpolator LINEAR = new LinearInterpolator();
-
-    /*
-    * ============================================================================================
-    * Custom interpolators
-    * ============================================================================================
-    */
-
-    public static final Interpolator FAST_OUT_SLOW_IN = LEGACY;
-    public static final Interpolator FAST_OUT_LINEAR_IN = LEGACY_ACCELERATE;
-    public static final Interpolator LINEAR_OUT_SLOW_IN = LEGACY_DECELERATE;
-
-    /**
-     * Like {@link #FAST_OUT_SLOW_IN}, but used in case the animation is played in reverse (i.e. t
-     * goes from 1 to 0 instead of 0 to 1).
-     */
-    public static final Interpolator FAST_OUT_SLOW_IN_REVERSE =
-            new PathInterpolator(0.8f, 0f, 0.6f, 1f);
-    public static final Interpolator SLOW_OUT_LINEAR_IN = new PathInterpolator(0.8f, 0f, 1f, 1f);
-    public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
-    public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
-    public static final Interpolator ACCELERATE = new AccelerateInterpolator();
-    public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
-    public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
-    public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f);
-    public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
-    public static final Interpolator ICON_OVERSHOT_LESS = new PathInterpolator(0.4f, 0f, 0.2f,
-            1.1f);
-    public static final Interpolator PANEL_CLOSE_ACCELERATED = new PathInterpolator(0.3f, 0, 0.5f,
-            1);
-    public static final Interpolator BOUNCE = new BounceInterpolator();
-    /**
-     * For state transitions on the control panel that lives in GlobalActions.
-     */
-    public static final Interpolator CONTROL_STATE = new PathInterpolator(0.4f, 0f, 0.2f,
-            1.0f);
-
-    /**
-     * Interpolator to be used when animating a move based on a click. Pair with enough duration.
-     */
-    public static final Interpolator TOUCH_RESPONSE =
-            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
-
-    /**
-     * Like {@link #TOUCH_RESPONSE}, but used in case the animation is played in reverse (i.e. t
-     * goes from 1 to 0 instead of 0 to 1).
-     */
-    public static final Interpolator TOUCH_RESPONSE_REVERSE =
-            new PathInterpolator(0.9f, 0f, 0.7f, 1f);
-
-    /*
-     * ============================================================================================
-     * Functions / Utilities
-     * ============================================================================================
-     */
-
-    /**
-     * Calculate the amount of overshoot using an exponential falloff function with desired
-     * properties, where the overshoot smoothly transitions at the 1.0f boundary into the
-     * overshoot, retaining its acceleration.
-     *
-     * @param progress a progress value going from 0 to 1
-     * @param overshootAmount the amount > 0 of overshoot desired. A value of 0.1 means the max
-     *                        value of the overall progress will be at 1.1.
-     * @param overshootStart the point in (0,1] where the result should reach 1
-     * @return the interpolated overshoot
-     */
-    public static float getOvershootInterpolation(float progress, float overshootAmount,
-            float overshootStart) {
-        if (overshootAmount == 0.0f || overshootStart == 0.0f) {
-            throw new IllegalArgumentException("Invalid values for overshoot");
-        }
-        float b = MathUtils.log((overshootAmount + 1) / (overshootAmount)) / overshootStart;
-        return MathUtils.max(0.0f,
-                (float) (1.0f - Math.exp(-b * progress)) * (overshootAmount + 1.0f));
-    }
-
-    /**
-     * Similar to {@link #getOvershootInterpolation(float, float, float)} but the overshoot
-     * starts immediately here, instead of first having a section of non-overshooting
-     *
-     * @param progress a progress value going from 0 to 1
-     */
-    public static float getOvershootInterpolation(float progress) {
-        return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress)));
-    }
-
-    // Create the default emphasized interpolator
-    private static PathInterpolator createEmphasizedInterpolator() {
-        Path path = new Path();
-        // Doing the same as fast_out_extra_slow_in
-        path.moveTo(0f, 0f);
-        path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
-        path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
-        return new PathInterpolator(path);
-    }
-}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
index 3417ffd..142fd21 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
@@ -28,7 +28,7 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.animation.Interpolator
-import com.android.systemui.animation.Interpolators.LINEAR
+import com.android.app.animation.Interpolators.LINEAR
 import kotlin.math.roundToInt
 
 private const val TAG = "LaunchAnimator"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 3ee97be..9346a2f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -24,12 +24,30 @@
 import android.graphics.Typeface
 import android.graphics.fonts.Font
 import android.text.Layout
+import android.text.TextPaint
 import android.util.LruCache
 
 private const val DEFAULT_ANIMATION_DURATION: Long = 300
 private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
 
 typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
+
+interface TypefaceVariantCache {
+    fun getTypefaceForVariant(fvar: String, targetPaint: TextPaint): Typeface?
+}
+
+class TypefaceVariantCacheImpl() : TypefaceVariantCache {
+    private val cache = LruCache<String, Typeface>(TYPEFACE_CACHE_MAX_ENTRIES)
+    override fun getTypefaceForVariant(fvar: String, targetPaint: TextPaint): Typeface? {
+        cache.get(fvar)?.let {
+            return it
+        }
+
+        targetPaint.fontVariationSettings = fvar
+        return targetPaint.typeface?.also { cache.put(fvar, it) }
+    }
+}
+
 /**
  * This class provides text animation between two styles.
  *
@@ -56,9 +74,19 @@
  * ```
  * </code> </pre>
  */
-class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
+class TextAnimator(
+    layout: Layout,
+    private val invalidateCallback: () -> Unit,
+) {
+    var typefaceCache: TypefaceVariantCache = TypefaceVariantCacheImpl()
+        get() = field
+        set(value) {
+            field = value
+            textInterpolator.typefaceCache = value
+        }
+
     // Following two members are for mutable for testing purposes.
-    public var textInterpolator: TextInterpolator = TextInterpolator(layout)
+    public var textInterpolator: TextInterpolator = TextInterpolator(layout, typefaceCache)
     public var animator: ValueAnimator =
         ValueAnimator.ofFloat(1f).apply {
             duration = DEFAULT_ANIMATION_DURATION
@@ -68,9 +96,7 @@
             }
             addListener(
                 object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
-                        textInterpolator.rebase()
-                    }
+                    override fun onAnimationEnd(animation: Animator?) = textInterpolator.rebase()
                     override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
                 }
             )
@@ -116,8 +142,6 @@
 
     private val fontVariationUtils = FontVariationUtils()
 
-    private val typefaceCache = LruCache<String, Typeface>(TYPEFACE_CACHE_MAX_ENTRIES)
-
     fun updateLayout(layout: Layout) {
         textInterpolator.layout = layout
     }
@@ -220,12 +244,8 @@
         }
 
         if (!fvar.isNullOrBlank()) {
-            textInterpolator.targetPaint.typeface = typefaceCache.get(fvar) ?: run {
-                textInterpolator.targetPaint.fontVariationSettings = fvar
-                textInterpolator.targetPaint.typeface?.also {
-                    typefaceCache.put(fvar, textInterpolator.targetPaint.typeface)
-                }
-            }
+            textInterpolator.targetPaint.typeface =
+                typefaceCache.getTypefaceForVariant(fvar, textInterpolator.targetPaint)
         }
 
         if (color != null) {
@@ -304,7 +324,8 @@
             weight = weight,
             width = width,
             opticalSize = opticalSize,
-            roundness = roundness,)
+            roundness = roundness,
+        )
         setTextStyle(
             fvar = fvar,
             textSize = textSize,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index 23f16f2..a041926 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -28,8 +28,10 @@
 import java.lang.Math.max
 
 /** Provide text style linear interpolation for plain text. */
-class TextInterpolator(layout: Layout) {
-
+class TextInterpolator(
+    layout: Layout,
+    var typefaceCache: TypefaceVariantCache,
+) {
     /**
      * Returns base paint used for interpolation.
      *
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 58ffef2..8e79e3c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -25,6 +25,7 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.animation.Interpolator
+import com.android.app.animation.Interpolators
 import kotlin.math.max
 import kotlin.math.min
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
index f3d8b17..dd32851 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
@@ -19,7 +19,7 @@
 import android.util.DisplayMetrics
 import android.view.animation.Interpolator
 import android.window.BackEvent
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.util.dpToPx
 
 /** Used to convert [BackEvent] into a [BackTransformation]. */
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 387b67d..520c888 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -40,7 +40,8 @@
                 SoftwareBitmapDetector.ISSUE,
                 NonInjectedServiceDetector.ISSUE,
                 StaticSettingsProviderDetector.ISSUE,
-                DemotingTestWithoutBugDetector.ISSUE
+                DemotingTestWithoutBugDetector.ISSUE,
+                TestFunctionNameViolationDetector.ISSUE,
             )
 
     override val api: Int
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/TestFunctionNameViolationDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/TestFunctionNameViolationDetector.kt
new file mode 100644
index 0000000..d91c7e5
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/TestFunctionNameViolationDetector.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.AnnotationInfo
+import com.android.tools.lint.detector.api.AnnotationUsageInfo
+import com.android.tools.lint.detector.api.AnnotationUsageType
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.getParentOfType
+import org.jetbrains.uast.kotlin.KotlinUAnnotation
+import org.jetbrains.uast.kotlin.KotlinUMethod
+
+/**
+ * Detects test function naming violations regarding use of the backtick-wrapped space-allowed
+ * feature of Kotlin functions.
+ */
+class TestFunctionNameViolationDetector : Detector(), SourceCodeScanner {
+
+    override fun applicableAnnotations(): List<String> = listOf(ANNOTATION)
+    override fun isApplicableAnnotationUsage(type: AnnotationUsageType): Boolean = true
+
+    @Suppress("UnstableApiUsage")
+    override fun visitAnnotationUsage(
+        context: JavaContext,
+        element: UElement,
+        annotationInfo: AnnotationInfo,
+        usageInfo: AnnotationUsageInfo,
+    ) {
+        (element as? KotlinUAnnotation)?.getParentOfType(KotlinUMethod::class.java)?.let { method ->
+            if (method.name.contains(" ")) {
+                context.report(
+                    issue = ISSUE,
+                    scope = method.nameIdentifier,
+                    location = context.getLocation(method.nameIdentifier),
+                    message =
+                        "Spaces are not allowed in test names. Use pascalCase_withUnderScores" +
+                            " instead.",
+                )
+            }
+        }
+    }
+
+    companion object {
+        private const val ANNOTATION = "org.junit.Test"
+
+        @JvmStatic
+        val ISSUE =
+            Issue.create(
+                id = "TestFunctionNameViolation",
+                briefDescription = "Spaces not allowed in test function names.",
+                explanation =
+                    """
+                    We don't allow test function names because it leads to issues with our test
+                    harness system (for example, see b/277739595). Please use
+                    pascalCase_withUnderScores instead.
+                """,
+                category = Category.TESTING,
+                priority = 8,
+                severity = Severity.FATAL,
+                implementation =
+                    Implementation(
+                        TestFunctionNameViolationDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE,
+                    ),
+            )
+    }
+}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
new file mode 100644
index 0000000..db73154
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+@Suppress("UnstableApiUsage")
+class TestFunctionNameViolationDetectorTest : SystemUILintDetectorTest() {
+    override fun getDetector(): Detector {
+        return TestFunctionNameViolationDetector()
+    }
+
+    override fun getIssues(): List<Issue> {
+        return listOf(
+            TestFunctionNameViolationDetector.ISSUE,
+        )
+    }
+
+    @Test
+    fun violations() {
+        lint()
+            .files(
+                kotlin(
+                    """
+                    package test.pkg.name
+
+                    import org.junit.Test
+
+                    class MyTest {
+                        @Test
+                        fun `illegal test name - violation should be detected`() {
+                            // some test code here.
+                        }
+
+                        @Test
+                        fun legitimateTestName_doesNotViolate() {
+                            // some test code here.
+                        }
+
+                        fun helperFunction_doesNotViolate() {
+                            // some code.
+                        }
+
+                        fun `helper function - does not violate`() {
+                            // some code.
+                        }
+                    }
+                """
+                        .trimIndent()
+                ),
+                testAnnotationStub,
+            )
+            .issues(
+                TestFunctionNameViolationDetector.ISSUE,
+            )
+            .run()
+            .expectWarningCount(0)
+            .expect(
+                """
+                src/test/pkg/name/MyTest.kt:7: Error: Spaces are not allowed in test names. Use pascalCase_withUnderScores instead. [TestFunctionNameViolation]
+                    fun `illegal test name - violation should be detected`() {
+                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """
+            )
+    }
+
+    companion object {
+        private val testAnnotationStub: TestFile =
+            kotlin(
+                """
+                package org.junit
+
+                import java.lang.annotation.ElementType
+                import java.lang.annotation.Retention
+                import java.lang.annotation.RetentionPolicy
+                import java.lang.annotation.Target
+
+                @Retention(RetentionPolicy.RUNTIME)
+                @Target({ElementType.METHOD})
+                annotation class Test
+            """
+            )
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 86bd5f2..3688f9e 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -29,9 +29,9 @@
 import android.util.AttributeSet
 import android.util.MathUtils
 import android.widget.TextView
+import com.android.app.animation.Interpolators
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.animation.GlyphCallback
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.TextAnimator
 import com.android.systemui.customization.R
 import com.android.systemui.plugins.log.LogBuffer
@@ -648,7 +648,7 @@
         private const val DIGITS_PER_LINE = 2
 
         // How much of "fraction" to spend on canceling the animation, if needed
-        private const val ANIMATION_CANCELLATION_TIME = 0.4f
+        private const val ANIMATION_CANCELLATION_TIME = 0f
 
         // Delays. Each digit's animation should have a slight delay, so we get a nice
         // "stepping" effect. When moving right, the second digit of the hour should move first.
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
similarity index 86%
rename from packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
index bf922bc..08ee602 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
@@ -17,7 +17,9 @@
 
 package com.android.systemui.shared.quickaffordance.shared.model
 
-object KeyguardQuickAffordancePreviewConstants {
+object KeyguardPreviewConstants {
+    const val MESSAGE_ID_HIDE_SMART_SPACE = 1111
+    const val KEY_HIDE_SMART_SPACE = "hide_smart_space"
     const val MESSAGE_ID_SLOT_SELECTED = 1337
     const val KEY_SLOT_ID = "slot_id"
     const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
diff --git a/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml b/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml
index a0ceb81..fe76ba7 100644
--- a/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml
+++ b/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml
@@ -26,7 +26,7 @@
     </item>
     <item>
         <shape android:shape="rectangle">
-            <solid android:color="?androidprv:attr/materialColorOnBackground" />
+            <solid android:color="?androidprv:attr/materialColorSecondaryFixed" />
             <corners android:radius="@dimen/keyguard_affordance_fixed_radius" />
         </shape>
     </item>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9cb8aa0..a2eba81 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -389,7 +389,7 @@
     <dimen name="navigation_key_width">70dp</dimen>
 
     <!-- The width/height of the icon of a navigation button -->
-    <dimen name="navigation_icon_size">32dp</dimen>
+    <dimen name="navigation_icon_size">24dp</dimen>
 
     <!-- The padding on the side of the navigation bar. Must be greater than or equal to
          navigation_extra_key_width -->
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index f59bf8e..64d766d 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -26,8 +26,8 @@
 import android.graphics.Color
 import android.util.AttributeSet
 import android.view.View
+import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
-import com.android.systemui.animation.Interpolators
 
 /** Displays security messages for the keyguard bouncer. */
 open class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index a6c782d..a30cae9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -15,9 +15,9 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.app.animation.Interpolators;
 import com.android.keyguard.dagger.KeyguardStatusViewScope;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.plugins.log.LogLevel;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 0394754..0982030 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -32,9 +32,9 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.ConstraintSet;
 
+import com.android.app.animation.Interpolators;
 import com.android.settingslib.animation.DisappearAnimationUtils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
 
 /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 33bea02..1d7c35d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -45,11 +45,11 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.TextViewInputDisabler;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 /**
  * Displays an alphanumeric (latin-1) key entry for the user to enter
  * an unlock password
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 0a91150..b4ddc9a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -34,9 +34,9 @@
 import android.view.KeyEvent;
 import android.view.View;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index ba5a8c9..78021ad 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -32,7 +32,7 @@
 import static androidx.constraintlayout.widget.ConstraintSet.TOP;
 import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
 
-import static com.android.systemui.animation.InterpolatorsAndroidX.DECELERATE_QUINT;
+import static com.android.app.animation.InterpolatorsAndroidX.DECELERATE_QUINT;
 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
 
 import static java.lang.Integer.max;
@@ -86,6 +86,7 @@
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringAnimation;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
@@ -96,7 +97,6 @@
 import com.android.settingslib.drawable.CircleFramedDrawable;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.shared.system.SysUiStatsLog;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
index c9128e5..96ac8ad 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
@@ -26,9 +26,9 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.animation.AnimationUtils
+import com.android.app.animation.Interpolators
 import com.android.internal.R.interpolator.fast_out_extra_slow_in
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 
 /** Animates constraint layout changes for the security view. */
 class KeyguardSecurityViewTransition : Transition() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 65a7166..b4f124a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -44,11 +44,11 @@
 import androidx.slice.widget.RowContent;
 import androidx.slice.widget.SliceContent;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
 
 import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index edfcb8d..89e7e17 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -40,11 +40,11 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.ConstraintSet;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.keyguard.KeyguardClockSwitch.ClockSize;
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ClockController;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9573913..2c669bb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -396,7 +396,6 @@
     private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
     private boolean mIsDreaming;
     private boolean mLogoutEnabled;
-    private boolean mIsFaceEnrolled;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private int mPostureState = DEVICE_POSTURE_UNKNOWN;
     private FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider;
@@ -2573,16 +2572,6 @@
         }
     }
 
-    private void updateFaceEnrolled(int userId) {
-        final Boolean isFaceEnrolled = isFaceSupported()
-                && mBiometricEnabledForUser.get(userId)
-                && mAuthController.isFaceAuthEnrolled(userId);
-        if (mIsFaceEnrolled != isFaceEnrolled) {
-            mLogger.logFaceEnrolledUpdated(mIsFaceEnrolled, isFaceEnrolled);
-        }
-        mIsFaceEnrolled = isFaceEnrolled;
-    }
-
     private boolean isFaceSupported() {
         return mFaceManager != null && !mFaceSensorProperties.isEmpty();
     }
@@ -2622,10 +2611,17 @@
     }
 
     /**
-     * @return true if there's at least one face enrolled
+     * @return true if there's at least one face enrolled for the given user
+     */
+    private boolean isFaceEnrolled(int userId) {
+        return mAuthController.isFaceAuthEnrolled(userId);
+    }
+
+    /**
+     * @return true if there's at least one face enrolled for the current user
      */
     public boolean isFaceEnrolled() {
-        return mIsFaceEnrolled;
+        return isFaceEnrolled(getCurrentUser());
     }
 
     private final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() {
@@ -3284,14 +3280,13 @@
     @SuppressLint("MissingPermission")
     @VisibleForTesting
     boolean isUnlockWithFingerprintPossible(int userId) {
-        // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
-        boolean newFpEnrolled = isFingerprintSupported()
-                && !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId);
-        Boolean oldFpEnrolled = mIsUnlockWithFingerprintPossible.getOrDefault(userId, false);
-        if (oldFpEnrolled != newFpEnrolled) {
-            mLogger.logFpEnrolledUpdated(userId, oldFpEnrolled, newFpEnrolled);
+        boolean newFpPossible = isFingerprintSupported()
+                && !isFingerprintDisabled(userId) && mAuthController.isFingerprintEnrolled(userId);
+        Boolean oldFpPossible = mIsUnlockWithFingerprintPossible.getOrDefault(userId, false);
+        if (oldFpPossible != newFpPossible) {
+            mLogger.logFpPossibleUpdated(userId, oldFpPossible, newFpPossible);
         }
-        mIsUnlockWithFingerprintPossible.put(userId, newFpEnrolled);
+        mIsUnlockWithFingerprintPossible.put(userId, newFpPossible);
         return mIsUnlockWithFingerprintPossible.get(userId);
     }
 
@@ -3306,24 +3301,13 @@
     /**
      * @deprecated This is being migrated to use modern architecture.
      */
+    @VisibleForTesting
     @Deprecated
-    private boolean isUnlockWithFacePossible(int userId) {
+    public boolean isUnlockWithFacePossible(int userId) {
         if (isFaceAuthInteractorEnabled()) {
             return getFaceAuthInteractor().canFaceAuthRun();
         }
-        return isFaceAuthEnabledForUser(userId) && !isFaceDisabled(userId);
-    }
-
-    /**
-     * If face hardware is available, user has enrolled and enabled auth via setting.
-     *
-     * @deprecated This is being migrated to use modern architecture.
-     */
-    @Deprecated
-    public boolean isFaceAuthEnabledForUser(int userId) {
-        // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
-        updateFaceEnrolled(userId);
-        return mIsFaceEnrolled;
+        return isFaceSupported() && isFaceEnrolled(userId) && !isFaceDisabled(userId);
     }
 
     private void stopListeningForFingerprint() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 651c979..aa652fa 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -21,7 +21,7 @@
 import android.util.Property;
 import android.view.View;
 
-import com.android.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
 import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.plugins.log.LogLevel;
 import com.android.systemui.statusbar.StatusBarState;
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index ad66909..e761123 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -30,7 +30,7 @@
 
 import androidx.annotation.StyleRes;
 
-import com.android.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
 
 /**
  * Provides background color and radius animations for key pad buttons.
diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
index 14810d9..c4ecb39 100644
--- a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
@@ -35,9 +35,9 @@
 
 import androidx.core.graphics.drawable.DrawableCompat;
 
+import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 
 /**
  * This class contains implementation for methods that will be used when user has set a
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 1661806..fe40145 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -630,7 +630,7 @@
         )
     }
 
-    fun logFpEnrolledUpdated(userId: Int, oldValue: Boolean, newValue: Boolean) {
+    fun logFpPossibleUpdated(userId: Int, oldValue: Boolean, newValue: Boolean) {
         logBuffer.log(
             TAG,
             DEBUG,
@@ -639,7 +639,7 @@
                 bool1 = oldValue
                 bool2 = newValue
             },
-            { "Fp enrolled state changed for userId: $int1 old: $bool1, new: $bool2" }
+            { "Fp possible state changed for userId: $int1 old: $bool1, new: $bool2" }
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java b/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
index 12dd8f0..4c16d41c 100644
--- a/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
@@ -16,6 +16,7 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
@@ -23,21 +24,29 @@
 import android.view.View;
 import android.widget.FrameLayout;
 
-import com.android.systemui.statusbar.policy.ConfigurationController;
-
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Custom {@link FrameLayout} that re-inflates when changes to {@link Configuration} happen.
  * Currently supports changes to density, asset path, and locale.
  */
-public class AutoReinflateContainer extends FrameLayout implements
-        ConfigurationController.ConfigurationListener {
+public class AutoReinflateContainer extends FrameLayout {
+
+    private static final Set<Integer> SUPPORTED_CHANGES = Set.of(
+            ActivityInfo.CONFIG_LOCALE,
+            ActivityInfo.CONFIG_UI_MODE,
+            ActivityInfo.CONFIG_ASSETS_PATHS,
+            ActivityInfo.CONFIG_DENSITY,
+            ActivityInfo.CONFIG_FONT_SCALE
+    );
 
     private final List<InflateListener> mInflateListeners = new ArrayList<>();
     private final int mLayout;
 
+    private final Configuration mLastConfig = new Configuration();
+
     public AutoReinflateContainer(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
 
@@ -51,15 +60,14 @@
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        Dependency.get(ConfigurationController.class).addCallback(this);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        Dependency.get(ConfigurationController.class).removeCallback(this);
+    protected void onConfigurationChanged(Configuration newConfig) {
+        int diff = mLastConfig.updateFrom(newConfig);
+        for (int change: SUPPORTED_CHANGES) {
+            if ((diff & change) != 0) {
+                inflateLayout();
+                return;
+            }
+        }
     }
 
     protected void inflateLayoutImpl() {
@@ -80,26 +88,6 @@
         listener.onInflated(getChildAt(0));
     }
 
-    @Override
-    public void onDensityOrFontScaleChanged() {
-        inflateLayout();
-    }
-
-    @Override
-    public void onThemeChanged() {
-        inflateLayout();
-    }
-
-    @Override
-    public void onUiModeChanged() {
-        inflateLayout();
-    }
-
-    @Override
-    public void onLocaleListChanged() {
-        inflateLayout();
-    }
-
     public interface InflateListener {
         /**
          * Called whenever a new view is inflated.
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index ef16a3a..aade71a 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -106,7 +106,6 @@
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.ExtensionController;
@@ -134,14 +133,14 @@
 import com.android.systemui.util.leak.LeakReporter;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 
+import dagger.Lazy;
+
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
 import javax.inject.Named;
 
-import dagger.Lazy;
-
 /**
  * Class to handle ugly dependencies throughout sysui until we determine the
  * long-term dependency injection solution.
@@ -270,7 +269,6 @@
     @Inject Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
     @Inject Lazy<StatusBarWindowController> mTempStatusBarWindowController;
     @Inject Lazy<DarkIconDispatcher> mDarkIconDispatcher;
-    @Inject Lazy<ConfigurationController> mConfigurationController;
     @Inject Lazy<StatusBarIconController> mStatusBarIconController;
     @Inject Lazy<ScreenLifecycle> mScreenLifecycle;
     @Inject Lazy<WakefulnessLifecycle> mWakefulnessLifecycle;
@@ -441,8 +439,6 @@
 
         mProviders.put(DarkIconDispatcher.class, mDarkIconDispatcher::get);
 
-        mProviders.put(ConfigurationController.class, mConfigurationController::get);
-
         mProviders.put(StatusBarIconController.class, mStatusBarIconController::get);
 
         mProviders.put(ScreenLifecycle.class, mScreenLifecycle::get);
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index de82ca0..c1871e0 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -36,7 +36,7 @@
 import android.view.View
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.util.asIndenting
 import java.io.PrintWriter
 
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index a3e7d71..e72ad82 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -34,7 +34,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.settingslib.Utils
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.log.ScreenDecorationsLogger
 import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 2503520..99d4662 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -43,8 +43,8 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.dynamicanimation.animation.SpringForce;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
@@ -374,11 +374,10 @@
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
                 final boolean captured = (mIsSwiping || mLongPressSent || mMenuRowIntercepting);
-                mIsSwiping = false;
-                mTouchedView = null;
                 mLongPressSent = false;
                 mCallback.onLongPressSent(null);
                 mMenuRowIntercepting = false;
+                resetSwipeState();
                 cancelLongPress();
                 if (captured) return true;
                 break;
@@ -491,7 +490,7 @@
                 }
                 if (!mCancelled || wasRemoved) {
                     mCallback.onChildDismissed(animView);
-                    resetSwipeState();
+                    resetSwipeOfView(animView);
                 }
                 if (endAction != null) {
                     endAction.accept(mCancelled);
@@ -546,7 +545,7 @@
 
             if (!cancelled) {
                 updateSwipeProgressFromOffset(animView, canBeDismissed);
-                resetSwipeState();
+                resetSwipeOfView(animView);
             }
             onChildSnappedBack(animView, targetLeft);
         });
@@ -806,9 +805,20 @@
         return mIsSwiping ? mTouchedView : null;
     }
 
+    protected void resetSwipeOfView(View view) {
+        if (getSwipedView() == view) {
+            resetSwipeState();
+        }
+    }
+
     public void resetSwipeState() {
+        View swipedView = getSwipedView();
         mTouchedView = null;
         mIsSwiping = false;
+        if (swipedView != null) {
+            snapChildIfNeeded(swipedView, false, 0);
+            onChildSnappedBack(swipedView, 0);
+        }
     }
 
     private float getTouchSlop(MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 6e8275f..7bfd84e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -346,6 +346,15 @@
             setSystemGestureExclusion();
             mIsVisible = true;
             mCallback.onSettingsPanelVisibilityChanged(/* shown= */ true);
+
+            if (resetPosition) {
+                // We could not put focus on the settings panel automatically
+                // since it is an inactive window. Therefore, we announce the existence of
+                // magnification settings for accessibility when it is opened.
+                mSettingView.announceForAccessibility(
+                        mContext.getResources().getString(
+                                R.string.accessibility_magnification_settings_panel_description));
+            }
         }
         mContext.registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
index d6f0b59..d491975 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
@@ -32,8 +32,8 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 
 /**
  * Visually discloses that contextual data was provided to an assistant.
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 0002ae9..2aac056 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -45,9 +45,9 @@
 import androidx.annotation.StyleRes;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.DualToneHandler;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.policy.BatteryController;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index aeebb01..be585ed 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -59,11 +59,11 @@
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.biometrics.AuthController.ScaleFactorProvider;
 import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
 import com.android.systemui.biometrics.ui.CredentialView;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index e0b9f01..782a10b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -23,14 +23,17 @@
 import android.graphics.Point
 import android.hardware.biometrics.BiometricFingerprintConstants
 import android.hardware.biometrics.BiometricSourceType
+import android.util.DisplayMetrics
 import androidx.annotation.VisibleForTesting
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.keyguard.logging.KeyguardLogger
 import com.android.settingslib.Utils
 import com.android.settingslib.udfps.UdfpsOverlayParams
+import com.android.systemui.CoreStartable
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -39,12 +42,11 @@
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LiftReveal
 import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.statusbar.phone.BiometricUnlockController
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.ViewController
@@ -60,9 +62,8 @@
  *
  * The ripple uses the accent color of the current theme.
  */
-@CentralSurfacesScope
+@SysUISingleton
 class AuthRippleController @Inject constructor(
-    private val centralSurfaces: CentralSurfaces,
     private val sysuiContext: Context,
     private val authController: AuthController,
     private val configurationController: ConfigurationController,
@@ -73,12 +74,15 @@
     private val notificationShadeWindowController: NotificationShadeWindowController,
     private val udfpsControllerProvider: Provider<UdfpsController>,
     private val statusBarStateController: StatusBarStateController,
+    private val displayMetrics: DisplayMetrics,
     private val featureFlags: FeatureFlags,
     private val logger: KeyguardLogger,
     private val biometricUnlockController: BiometricUnlockController,
+    private val lightRevealScrim: LightRevealScrim,
     rippleView: AuthRippleView?
 ) :
     ViewController<AuthRippleView>(rippleView),
+    CoreStartable,
     KeyguardStateController.Callback,
     WakefulnessLifecycle.Observer {
 
@@ -92,6 +96,10 @@
     private var udfpsController: UdfpsController? = null
     private var udfpsRadius: Float = -1f
 
+    override fun start() {
+        init()
+    }
+
     @VisibleForTesting
     public override fun onViewAttached() {
         authController.addCallback(authControllerCallback)
@@ -153,8 +161,8 @@
                         it.y,
                         0,
                         Math.max(
-                                Math.max(it.x, centralSurfaces.displayWidth.toInt() - it.x),
-                                Math.max(it.y, centralSurfaces.displayHeight.toInt() - it.y)
+                                Math.max(it.x, displayMetrics.widthPixels - it.x),
+                                Math.max(it.y, displayMetrics.heightPixels - it.y)
                         )
                 )
                 logger.showingUnlockRippleAt(it.x, it.y, "FP sensor radius: $udfpsRadius")
@@ -168,8 +176,8 @@
                         it.y,
                         0,
                         Math.max(
-                                Math.max(it.x, centralSurfaces.displayWidth.toInt() - it.x),
-                                Math.max(it.y, centralSurfaces.displayHeight.toInt() - it.y)
+                                Math.max(it.x, displayMetrics.widthPixels - it.x),
+                                Math.max(it.y, displayMetrics.heightPixels - it.y)
                         )
                 )
                 logger.showingUnlockRippleAt(it.x, it.y, "Face unlock ripple")
@@ -184,11 +192,10 @@
         // This code path is not used if the KeyguardTransitionRepository is managing the light
         // reveal scrim.
         if (!featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
-            val lightRevealScrim = centralSurfaces.lightRevealScrim
             if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) {
                 circleReveal?.let {
-                    lightRevealScrim?.revealAmount = 0f
-                    lightRevealScrim?.revealEffect = it
+                    lightRevealScrim.revealAmount = 0f
+                    lightRevealScrim.revealEffect = it
                     startLightRevealScrimOnKeyguardFadingAway = true
                 }
             }
@@ -208,8 +215,7 @@
         }
 
         if (keyguardStateController.isKeyguardFadingAway) {
-            val lightRevealScrim = centralSurfaces.lightRevealScrim
-            if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) {
+            if (startLightRevealScrimOnKeyguardFadingAway) {
                 lightRevealScrimAnimator?.cancel()
                 lightRevealScrimAnimator = ValueAnimator.ofFloat(.1f, 1f).apply {
                     interpolator = Interpolators.LINEAR_OUT_SLOW_IN
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index b007134..5ede16d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -28,7 +28,7 @@
 import android.view.View
 import android.view.animation.PathInterpolator
 import com.android.internal.graphics.ColorUtils
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.surfaceeffects.ripple.RippleShader
 
 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index ef7dcb7..1dbafc6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -19,7 +19,7 @@
 import android.graphics.PointF
 import android.graphics.RectF
 import com.android.systemui.Dumpable
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionListener
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index ba8e60a..52db4ab 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -40,9 +40,9 @@
 import androidx.annotation.Nullable;
 import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
 
+import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 
 import com.airbnb.lottie.LottieAnimationView;
 import com.airbnb.lottie.LottieProperty;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index 3b50bbc..eaab75a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -23,11 +23,11 @@
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
index 9847c10..baf8d74 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
@@ -22,9 +22,7 @@
 import com.android.systemui.biometrics.EllipseOverlapDetectorParams
 import com.android.systemui.dagger.SysUISingleton
 import kotlin.math.cos
-import kotlin.math.pow
 import kotlin.math.sin
-import kotlin.math.sqrt
 
 private enum class SensorPixelPosition {
     OUTSIDE, // Pixel that falls outside of sensor circle
@@ -42,8 +40,8 @@
 @SysUISingleton
 class EllipseOverlapDetector(private val params: EllipseOverlapDetectorParams) : OverlapDetector {
     override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean {
-        // First, check if entire ellipse is within the sensor
-        if (isEllipseWithinSensor(touchData, nativeSensorBounds)) {
+        // First, check if touch is within bounding box,
+        if (nativeSensorBounds.contains(touchData.x.toInt(), touchData.y.toInt())) {
             return true
         }
 
@@ -119,28 +117,4 @@
 
         return result <= 1
     }
-
-    /** Returns whether the entire ellipse is contained within the sensor area */
-    private fun isEllipseWithinSensor(
-        touchData: NormalizedTouchData,
-        nativeSensorBounds: Rect
-    ): Boolean {
-        val a2 = (touchData.minor / 2.0).pow(2.0)
-        val b2 = (touchData.major / 2.0).pow(2.0)
-
-        val sin2a = sin(touchData.orientation.toDouble()).pow(2.0)
-        val cos2a = cos(touchData.orientation.toDouble()).pow(2.0)
-
-        val cx = sqrt(a2 * cos2a + b2 * sin2a)
-        val cy = sqrt(a2 * sin2a + b2 * cos2a)
-
-        val ellipseRect =
-            Rect(
-                (-cx + touchData.x).toInt(),
-                (-cy + touchData.y).toInt(),
-                (cx + touchData.x).toInt(),
-                (cy + touchData.y).toInt()
-            )
-        return nativeSensorBounds.contains(ellipseRect)
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
index e2d36dc..9292bd7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -6,8 +6,8 @@
 import android.widget.TextView
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.biometrics.AuthDialog
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.ui.CredentialPasswordView
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 11ef749..7bf8f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -30,9 +30,9 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.surfaceeffects.ripple.RippleShader;
 import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
index 1fa9ac5..1ffbe32 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
@@ -72,7 +72,7 @@
         } catch (PackageManager.NameNotFoundException e) {
             Log.w(TAG, "Package not found: " + mClipboardManager.getPrimaryClipSource(), e);
         }
-        mEditText.setText(clip.getItemAt(0).getText());
+        mEditText.setText(clip.getItemAt(0).getText().toString());
         mEditText.requestFocus();
         mEditText.setSelection(0);
         mSensitive = clip.getDescription().getExtras() != null
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
index 8d0edf8..b447d66 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
@@ -32,7 +32,7 @@
 import androidx.lifecycle.LifecycleObserver
 import androidx.lifecycle.OnLifecycleEvent
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.controls.ui.ControlsUiController
 
 object ControlsAnimations {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 6a9aaf8..e6361f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -50,7 +50,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.internal.graphics.ColorUtils
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.util.concurrency.DelayableExecutor
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index fa36eee..1461135 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -38,7 +38,7 @@
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityNodeInfo
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL
 import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL
 import java.util.IllegalFormatException
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 0be3bb6..f6435a7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -50,6 +50,7 @@
 import android.content.pm.ShortcutManager;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
+import android.graphics.Color;
 import android.hardware.SensorManager;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricManager;
@@ -113,13 +114,13 @@
 import com.android.systemui.dagger.qualifiers.TestHarness;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
+import dagger.Module;
+import dagger.Provides;
+
 import java.util.Optional;
 
 import javax.inject.Singleton;
 
-import dagger.Module;
-import dagger.Provides;
-
 /**
  * Provides Non-SystemUI, Framework-Owned instances to the dependency graph.
  */
@@ -277,7 +278,6 @@
 
     @Provides
     @Singleton
-    @Nullable
     static IVrManager provideIVrManager() {
         return IVrManager.Stub.asInterface(ServiceManager.getService(Context.VR_SERVICE));
     }
@@ -323,7 +323,9 @@
     @Provides
     @Singleton
     static InteractionJankMonitor provideInteractionJankMonitor() {
-        return InteractionJankMonitor.getInstance();
+        InteractionJankMonitor jankMonitor = InteractionJankMonitor.getInstance();
+        jankMonitor.configDebugOverlay(Color.YELLOW, 0.75);
+        return jankMonitor;
     }
 
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index cee8294..dff2c0e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -288,7 +288,7 @@
             INotificationManager notificationManager,
             IDreamManager dreamManager,
             NotificationVisibilityProvider visibilityProvider,
-            NotificationInterruptStateProvider interruptionStateProvider,
+            VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
             CommonNotifCollection notifCollection,
@@ -306,7 +306,7 @@
                 notificationManager,
                 dreamManager,
                 visibilityProvider,
-                interruptionStateProvider,
+                visualInterruptionDecisionProvider,
                 zenModeController,
                 notifUserManager,
                 notifCollection,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index 5b56c04..83f39b5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -24,8 +24,8 @@
 import androidx.core.animation.doOnEnd
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.complication.ComplicationHostViewController
 import com.android.systemui.complication.ComplicationLayoutParams
 import com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTOM
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 15a32d2..c22019e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -33,9 +33,9 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.app.animation.Interpolators;
 import com.android.dream.lowlight.LowLightTransitionCoordinator;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.complication.ComplicationHostViewController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
index 5bbfbda..3ef19b7 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
@@ -16,12 +16,9 @@
 package com.android.systemui.dreams.conditions;
 
 import android.app.DreamManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.text.TextUtils;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.shared.condition.Condition;
 
 import javax.inject.Inject;
@@ -30,48 +27,33 @@
  * {@link DreamCondition} provides a signal when a dream begins and ends.
  */
 public class DreamCondition extends Condition {
-    private final Context mContext;
     private final DreamManager mDreamManager;
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            processIntent(intent);
-        }
-    };
+    private final KeyguardUpdateMonitor mUpdateMonitor;
+
+
+    private final KeyguardUpdateMonitorCallback mUpdateCallback =
+            new KeyguardUpdateMonitorCallback() {
+                @Override
+                public void onDreamingStateChanged(boolean dreaming) {
+                    updateCondition(dreaming);
+                }
+            };
 
     @Inject
-    public DreamCondition(Context context,
-            DreamManager dreamManager) {
-        mContext = context;
+    public DreamCondition(DreamManager dreamManager, KeyguardUpdateMonitor monitor) {
         mDreamManager = dreamManager;
-    }
-
-    private void processIntent(Intent intent) {
-        // In the case of a non-existent sticky broadcast, ignore when there is no intent.
-        if (intent == null) {
-            return;
-        }
-        if (TextUtils.equals(intent.getAction(), Intent.ACTION_DREAMING_STARTED)) {
-            updateCondition(true);
-        } else if (TextUtils.equals(intent.getAction(), Intent.ACTION_DREAMING_STOPPED)) {
-            updateCondition(false);
-        } else {
-            throw new IllegalStateException("unexpected intent:" + intent);
-        }
+        mUpdateMonitor = monitor;
     }
 
     @Override
     protected void start() {
-        final IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_DREAMING_STARTED);
-        filter.addAction(Intent.ACTION_DREAMING_STOPPED);
-        mContext.registerReceiver(mReceiver, filter);
+        mUpdateMonitor.registerCallback(mUpdateCallback);
         updateCondition(mDreamManager.isDreaming());
     }
 
     @Override
     protected void stop() {
-        mContext.unregisterReceiver(mReceiver);
+        mUpdateMonitor.removeCallback(mUpdateCallback);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index aaf4358..2ecb0a0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -87,7 +87,9 @@
         releasedFlag(270682168, "animated_notification_shade_insets")
 
     // TODO(b/268005230): Tracking Bug
-    @JvmField val SENSITIVE_REVEAL_ANIM = unreleasedFlag(268005230, "sensitive_reveal_anim")
+    @JvmField
+    val SENSITIVE_REVEAL_ANIM =
+        unreleasedFlag(268005230, "sensitive_reveal_anim", teamfood = true)
 
     // 200 - keyguard/lockscreen
     // ** Flag retired **
@@ -108,7 +110,7 @@
 
     // TODO(b/275694445): Tracking Bug
     @JvmField
-    val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING = unreleasedFlag(208,
+    val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING = releasedFlag(208,
         "lockscreen_without_secure_lock_when_dreaming")
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index d3b6fc2..5189944 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -97,6 +97,7 @@
 import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.LifecycleRegistry;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
@@ -116,7 +117,6 @@
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.animation.Expandable;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Background;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 2d1b7ae..9844ca0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -36,7 +36,7 @@
 import com.android.internal.R
 import com.android.keyguard.KeyguardClockSwitchController
 import com.android.keyguard.KeyguardViewController
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -792,7 +792,8 @@
             // Translate up from the bottom.
             surfaceBehindMatrix.setTranslate(
                     surfaceBehindRemoteAnimationTarget.localBounds.left.toFloat(),
-                    surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
+                    surfaceBehindRemoteAnimationTarget.localBounds.top.toFloat() +
+                            surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
             )
 
             // Scale up from a point at the center-bottom of the surface.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 7a2013e..99a9bed5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -98,6 +98,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
 import com.android.internal.policy.IKeyguardDismissCallback;
@@ -121,7 +122,6 @@
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.LaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollector;
@@ -1610,8 +1610,9 @@
     }
 
     private void doKeyguardLaterForChildProfilesLocked() {
-        UserManager um = UserManager.get(mContext);
-        for (int profileId : um.getEnabledProfileIds(UserHandle.myUserId())) {
+        for (UserInfo profile : mUserTracker.getUserProfiles()) {
+            if (!profile.isEnabled()) continue;
+            final int profileId = profile.id;
             if (mLockPatternUtils.isSeparateProfileChallengeEnabled(profileId)) {
                 long userTimeout = getLockTimeout(profileId);
                 if (userTimeout == 0) {
@@ -1634,8 +1635,9 @@
     }
 
     private void doKeyguardForChildProfilesLocked() {
-        UserManager um = UserManager.get(mContext);
-        for (int profileId : um.getEnabledProfileIds(UserHandle.myUserId())) {
+        for (UserInfo profile : mUserTracker.getUserProfiles()) {
+            if (!profile.isEnabled()) continue;
+            final int profileId = profile.id;
             if (mLockPatternUtils.isSeparateProfileChallengeEnabled(profileId)) {
                 lockProfile(profileId);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index e6568f2..cde67f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index c2d139c..7e9cbc1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 86f65dde..aca4019 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 3beac0b..fc7bfb4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index b5bcd45..39c630b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 87f3164..0505d37 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 1fbfff9..944adba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 94961cb..d4af381 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
+import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 38b9d50..9d7477c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -16,7 +16,7 @@
 package com.android.systemui.keyguard.ui
 
 import android.view.animation.Interpolator
-import com.android.systemui.animation.Interpolators.LINEAR
+import com.android.app.animation.Interpolators.LINEAR
 import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
 import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index d96609c..c8d37a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -32,11 +32,11 @@
 import androidx.core.view.updateLayoutParams
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.Expandable
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.view.LaunchableLinearLayout
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.binder.IconViewBinder
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 9aecb5d..85fb565 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -39,7 +39,8 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
-import com.android.systemui.shared.quickaffordance.shared.model.KeyguardQuickAffordancePreviewConstants
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedInject
@@ -59,6 +60,7 @@
     private val clockController: ClockEventController,
     private val clockRegistry: ClockRegistry,
     private val broadcastDispatcher: BroadcastDispatcher,
+    private val lockscreenSmartspaceController: LockscreenSmartspaceController,
     @Assisted bundle: Bundle,
 ) {
 
@@ -67,7 +69,7 @@
     private val height: Int = bundle.getInt(KEY_VIEW_HEIGHT)
     private val shouldHighlightSelectedAffordance: Boolean =
         bundle.getBoolean(
-            KeyguardQuickAffordancePreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES,
+            KeyguardPreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES,
             false,
         )
     private val shouldHideClock: Boolean =
@@ -79,6 +81,7 @@
         get() = host.surfacePackage
 
     private var clockView: View? = null
+    private var smartSpaceView: View? = null
 
     private val disposables = mutableSetOf<DisposableHandle>()
     private var isDestroyed = false
@@ -87,7 +90,7 @@
         bottomAreaViewModel.enablePreviewMode(
             initiallySelectedSlotId =
                 bundle.getString(
-                    KeyguardQuickAffordancePreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
+                    KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
                 ),
             shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
         )
@@ -108,9 +111,10 @@
             val rootView = FrameLayout(context)
 
             setUpBottomArea(rootView)
-            if (!shouldHideClock) {
-                setUpClock(rootView)
-            }
+
+            setupSmartspace(rootView)
+
+            setUpClock(rootView)
 
             rootView.measure(
                 View.MeasureSpec.makeMeasureSpec(
@@ -147,9 +151,62 @@
 
     fun destroy() {
         isDestroyed = true
+        lockscreenSmartspaceController.disconnect()
         disposables.forEach { it.dispose() }
     }
 
+    fun hideSmartspace(hide: Boolean) {
+        smartSpaceView?.visibility = if (hide) View.INVISIBLE else View.VISIBLE
+    }
+
+    /**
+     * This sets up and shows a non-interactive smart space
+     *
+     * The top padding is as follows:
+     *    Status bar height + clock top margin + keyguard smart space top offset
+     *
+     * The start padding is as follows:
+     *    Clock padding start + Below clock padding start
+     *
+     * The end padding is as follows:
+     *    Below clock padding end
+     */
+    private fun setupSmartspace(parentView: ViewGroup) {
+        if (!lockscreenSmartspaceController.isEnabled() ||
+                !lockscreenSmartspaceController.isDateWeatherDecoupled()) {
+            return
+        }
+
+        smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView)
+
+        val topPadding: Int = with(context.resources) {
+            getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) +
+                    getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
+                    getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
+        }
+
+        val startPadding: Int = with(context.resources) {
+            getDimensionPixelSize(R.dimen.clock_padding_start) +
+                    getDimensionPixelSize(R.dimen.below_clock_padding_start)
+        }
+
+        val endPadding: Int = context.resources
+                .getDimensionPixelSize(R.dimen.below_clock_padding_end)
+
+        smartSpaceView?.let {
+            it.setPaddingRelative(startPadding, topPadding, endPadding, 0)
+            it.isClickable = false
+
+            parentView.addView(
+                    it,
+                    FrameLayout.LayoutParams(
+                            FrameLayout.LayoutParams.MATCH_PARENT,
+                            FrameLayout.LayoutParams.WRAP_CONTENT,
+                    ),
+            )
+        }
+    }
+
     private fun setUpBottomArea(parentView: ViewGroup) {
         val bottomAreaView =
             LayoutInflater.from(context)
@@ -202,22 +259,48 @@
         disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) })
 
         onClockChanged(parentView)
+
+        updateSmartspaceWithSetupClock()
     }
 
     private fun onClockChanged(parentView: ViewGroup) {
         clockController.clock = clockRegistry.createCurrentClock()
-        clockController.clock
-            ?.largeClock
-            ?.events
-            ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
-        clockView?.let { parentView.removeView(it) }
-        clockView =
-            clockController.clock?.largeClock?.view?.apply {
+
+        if (!shouldHideClock) {
+            val largeClock = clockController.clock?.largeClock
+
+            largeClock
+                ?.events
+                ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
+
+            clockView?.let { parentView.removeView(it) }
+            clockView = largeClock?.view?.apply {
                 if (shouldHighlightSelectedAffordance) {
                     alpha = DIM_ALPHA
                 }
                 parentView.addView(this)
+                visibility = View.VISIBLE
             }
+        } else {
+            clockView?.visibility = View.GONE
+        }
+    }
+
+    /**
+     * Updates smart space after clock is set up. Used to show or hide smartspace with the right
+     * opacity based on the clock after setup.
+     */
+    private fun updateSmartspaceWithSetupClock() {
+        val hasCustomWeatherDataDisplay =
+                clockController
+                        .clock
+                        ?.largeClock
+                        ?.config
+                        ?.hasCustomWeatherDataDisplay == true
+
+        hideSmartspace(hasCustomWeatherDataDisplay)
+
+        smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 6d95882..3869b23 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -29,7 +29,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.shared.quickaffordance.shared.model.KeyguardQuickAffordancePreviewConstants
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -114,13 +114,18 @@
             }
 
             when (message.what) {
-                KeyguardQuickAffordancePreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
+                KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
                     message.data
                         .getString(
-                            KeyguardQuickAffordancePreviewConstants.KEY_SLOT_ID,
+                            KeyguardPreviewConstants.KEY_SLOT_ID,
                         )
                         ?.let { slotId -> renderer.onSlotSelected(slotId = slotId) }
                 }
+                KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE -> {
+                    message.data
+                        .getBoolean(KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE)
+                        .let { hide -> renderer.hideSmartspace(hide) }
+                }
                 else -> requestDestruction(this)
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 8d6545a4..2c9a9b3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
index f16827d..c135786 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index bc9dc4f..c6187dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index a60665a..d3ea89c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index ddce516..6845c55 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index df93d23..68810f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 52d4171..0860c20 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -20,7 +20,10 @@
 import android.content.res.Configuration
 import android.content.res.Resources
 import android.media.projection.IMediaProjection
+import android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT
 import android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION
+import android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL
+import android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_TASK
 import android.os.Binder
 import android.os.Bundle
 import android.os.IBinder
@@ -67,6 +70,11 @@
     private lateinit var controller: MediaProjectionAppSelectorController
     private lateinit var recentsViewController: MediaProjectionRecentsViewController
     private lateinit var component: MediaProjectionAppSelectorComponent
+    // Indicate if we are under the media projection security flow
+    // i.e. when a host app reuses consent token, review the permission and update it to the service
+    private var reviewGrantedConsentRequired = false
+    // If an app is selected, set to true so that we don't send RECORD_CANCEL in onDestroy
+    private var taskSelected = false
 
     override fun getLayoutResource() = R.layout.media_projection_app_selector
 
@@ -85,6 +93,9 @@
             component.personalProfileUserHandle
         )
 
+        reviewGrantedConsentRequired =
+            intent.getBooleanExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, false)
+
         super.onCreate(bundle)
         controller.init()
     }
@@ -149,6 +160,16 @@
     }
 
     override fun onDestroy() {
+        // onDestroy is also called when an app is selected, in that case we only want to send
+        // RECORD_CONTENT_TASK but not RECORD_CANCEL
+        if (!taskSelected) {
+            // TODO(b/272010156): Return result to PermissionActivity and update service there
+            MediaProjectionServiceHelper.setReviewedConsentIfNeeded(
+                RECORD_CANCEL,
+                reviewGrantedConsentRequired,
+                /* projection= */ null
+            )
+        }
         activityLauncher.destroy()
         controller.destroy()
         super.onDestroy()
@@ -163,6 +184,7 @@
     }
 
     override fun returnSelectedApp(launchCookie: IBinder) {
+        taskSelected = true
         if (intent.hasExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER)) {
             // The client requested to return the result in the result receiver instead of
             // activity result, let's send the media projection to the result receiver
@@ -174,7 +196,11 @@
             val captureRegion = MediaProjectionCaptureTarget(launchCookie)
             val data = Bundle().apply { putParcelable(KEY_CAPTURE_TARGET, captureRegion) }
             resultReceiver.send(RESULT_OK, data)
+            // TODO(b/279175710): Ensure consent result is always set here. Skipping this for now
+            //  in ScreenMediaRecorder, since we know the permission grant (projection) is never
+            //  reused in that scenario.
         } else {
+            // TODO(b/272010156): Return result to PermissionActivity and update service there
             // Return the media projection instance as activity result
             val mediaProjectionBinder = intent.getIBinderExtra(EXTRA_MEDIA_PROJECTION)
             val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder)
@@ -185,6 +211,11 @@
             intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder())
             setResult(RESULT_OK, intent)
             setForceSendResultForMediaProjection()
+            MediaProjectionServiceHelper.setReviewedConsentIfNeeded(
+                RECORD_CONTENT_TASK,
+                reviewGrantedConsentRequired,
+                projection
+            )
         }
 
         finish()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index ccddd1d..e217e36 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -16,11 +16,16 @@
 
 package com.android.systemui.media;
 
+import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT;
+import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT;
+import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
+import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
 import static com.android.systemui.screenrecord.ScreenShareOptionKt.ENTIRE_SCREEN;
 import static com.android.systemui.screenrecord.ScreenShareOptionKt.SINGLE_APP;
 
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.AlertDialog;
@@ -30,12 +35,10 @@
 import android.content.pm.PackageManager;
 import android.graphics.Typeface;
 import android.media.projection.IMediaProjection;
-import android.media.projection.IMediaProjectionManager;
 import android.media.projection.MediaProjectionManager;
+import android.media.projection.ReviewGrantedConsentResult;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.text.BidiFormatter;
 import android.text.SpannableString;
@@ -55,10 +58,10 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.Utils;
 
-import javax.inject.Inject;
-
 import dagger.Lazy;
 
+import javax.inject.Inject;
+
 public class MediaProjectionPermissionActivity extends Activity
         implements DialogInterface.OnClickListener {
     private static final String TAG = "MediaProjectionPermissionActivity";
@@ -70,10 +73,13 @@
 
     private String mPackageName;
     private int mUid;
-    private IMediaProjectionManager mService;
 
     private AlertDialog mDialog;
 
+    // Indicates if user must review already-granted consent that the MediaProjection app is
+    // attempting to re-use.
+    private boolean mReviewGrantedConsentRequired = false;
+
     @Inject
     public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
             Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) {
@@ -85,13 +91,23 @@
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
-        mPackageName = getCallingPackage();
-        IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
-        mService = IMediaProjectionManager.Stub.asInterface(b);
+        final Intent launchingIntent = getIntent();
+        mReviewGrantedConsentRequired = launchingIntent.getBooleanExtra(
+                EXTRA_USER_REVIEW_GRANTED_CONSENT, false);
 
+        mPackageName = getCallingPackage();
+
+        // This activity is launched directly by an app, or system server. System server provides
+        // the package name through the intent if so.
         if (mPackageName == null) {
-            finish();
-            return;
+            if (launchingIntent.hasExtra(EXTRA_PACKAGE_REUSING_GRANTED_CONSENT)) {
+                mPackageName = launchingIntent.getStringExtra(
+                        EXTRA_PACKAGE_REUSING_GRANTED_CONSENT);
+            } else {
+                setResult(RESULT_CANCELED);
+                finish(RECORD_CANCEL, /* projection= */ null);
+                return;
+            }
         }
 
         PackageManager packageManager = getPackageManager();
@@ -100,25 +116,36 @@
             aInfo = packageManager.getApplicationInfo(mPackageName, 0);
             mUid = aInfo.uid;
         } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "unable to look up package name", e);
-            finish();
+            Log.e(TAG, "Unable to look up package name", e);
+            setResult(RESULT_CANCELED);
+            finish(RECORD_CANCEL, /* projection= */ null);
             return;
         }
 
         try {
-            if (mService.hasProjectionPermission(mUid, mPackageName)) {
-                setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
-                finish();
+            if (MediaProjectionServiceHelper.hasProjectionPermission(mUid, mPackageName)) {
+                final IMediaProjection projection =
+                        MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName,
+                                mReviewGrantedConsentRequired);
+                // Automatically grant consent if a system-privileged component is recording.
+                final Intent intent = new Intent();
+                intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
+                        projection.asBinder());
+                setResult(RESULT_OK, intent);
+                finish(RECORD_CONTENT_DISPLAY, projection);
                 return;
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error checking projection permissions", e);
-            finish();
+            setResult(RESULT_CANCELED);
+            finish(RECORD_CANCEL, /* projection= */ null);
             return;
         }
 
         if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
             if (showScreenCaptureDisabledDialogIfNeeded()) {
+                setResult(RESULT_CANCELED);
+                finish(RECORD_CANCEL, /* projection= */ null);
                 return;
             }
         }
@@ -178,7 +205,7 @@
                 ScreenShareOption selectedOption =
                         ((MediaProjectionPermissionDialog) mDialog).getSelectedScreenShareOption();
                 grantMediaProjectionPermission(selectedOption.getMode());
-            }, appName);
+            }, () -> finish(RECORD_CANCEL, /* projection= */ null), appName);
         } else {
             AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
                     R.style.Theme_SystemUI_Dialog)
@@ -191,7 +218,6 @@
         }
 
         setUpDialog(mDialog);
-
         mDialog.show();
     }
 
@@ -207,6 +233,12 @@
     public void onClick(DialogInterface dialog, int which) {
         if (which == AlertDialog.BUTTON_POSITIVE) {
             grantMediaProjectionPermission(ENTIRE_SCREEN);
+        } else {
+            if (mDialog != null) {
+                mDialog.dismiss();
+            }
+            setResult(RESULT_CANCELED);
+            finish(RECORD_CANCEL, /* projection= */ null);
         }
     }
 
@@ -240,15 +272,25 @@
     private void grantMediaProjectionPermission(int screenShareMode) {
         try {
             if (screenShareMode == ENTIRE_SCREEN) {
-                setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
+                final IMediaProjection projection =
+                        MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName,
+                                mReviewGrantedConsentRequired);
+                final Intent intent = new Intent();
+                intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
+                        projection.asBinder());
+                setResult(RESULT_OK, intent);
+                finish(RECORD_CONTENT_DISPLAY, projection);
             }
             if (isPartialScreenSharingEnabled() && screenShareMode == SINGLE_APP) {
-                IMediaProjection projection = createProjection(mUid, mPackageName);
-                final Intent intent = new Intent(this, MediaProjectionAppSelectorActivity.class);
+                IMediaProjection projection = MediaProjectionServiceHelper.createOrReuseProjection(
+                        mUid, mPackageName, mReviewGrantedConsentRequired);
+                final Intent intent = new Intent(this,
+                        MediaProjectionAppSelectorActivity.class);
                 intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
                         projection.asBinder());
                 intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
                         getHostUserHandle());
+                intent.putExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, mReviewGrantedConsentRequired);
                 intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
 
                 // Start activity from the current foreground user to avoid creating a separate
@@ -259,11 +301,11 @@
         } catch (RemoteException e) {
             Log.e(TAG, "Error granting projection permission", e);
             setResult(RESULT_CANCELED);
+            finish(RECORD_CANCEL, /* projection= */ null);
         } finally {
             if (mDialog != null) {
                 mDialog.dismiss();
             }
-            finish();
         }
     }
 
@@ -271,22 +313,22 @@
         return UserHandle.getUserHandleForUid(getLaunchedFromUid());
     }
 
-    private IMediaProjection createProjection(int uid, String packageName) throws RemoteException {
-        return mService.createProjection(uid, packageName,
-                MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
+    @Override
+    public void finish() {
+        // Default to cancelling recording when user needs to review consent.
+        finish(RECORD_CANCEL, /* projection= */ null);
     }
 
-    private Intent getMediaProjectionIntent(int uid, String packageName)
-            throws RemoteException {
-        IMediaProjection projection = createProjection(uid, packageName);
-        Intent intent = new Intent();
-        intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
-        return intent;
+    private void finish(@ReviewGrantedConsentResult int consentResult,
+            @Nullable IMediaProjection projection) {
+        MediaProjectionServiceHelper.setReviewedConsentIfNeeded(
+                consentResult, mReviewGrantedConsentRequired, projection);
+        super.finish();
     }
 
     private void onDialogDismissedOrCancelled(DialogInterface dialogInterface) {
         if (!isFinishing()) {
-            finish();
+            finish(RECORD_CANCEL, /* projection= */ null);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionServiceHelper.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionServiceHelper.kt
new file mode 100644
index 0000000..9e616e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionServiceHelper.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.media
+
+import android.content.Context
+import android.media.projection.IMediaProjection
+import android.media.projection.IMediaProjectionManager
+import android.media.projection.MediaProjectionManager
+import android.media.projection.ReviewGrantedConsentResult
+import android.os.RemoteException
+import android.os.ServiceManager
+import android.util.Log
+
+/**
+ * Helper class that handles the media projection service related actions. It simplifies invoking
+ * the MediaProjectionManagerService and updating the permission consent.
+ */
+class MediaProjectionServiceHelper {
+    companion object {
+        private const val TAG = "MediaProjectionServiceHelper"
+        private val service =
+            IMediaProjectionManager.Stub.asInterface(
+                ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE)
+            )
+
+        @JvmStatic
+        @Throws(RemoteException::class)
+        fun hasProjectionPermission(uid: Int, packageName: String) =
+            service.hasProjectionPermission(uid, packageName)
+
+        @JvmStatic
+        @Throws(RemoteException::class)
+        fun createOrReuseProjection(
+            uid: Int,
+            packageName: String,
+            reviewGrantedConsentRequired: Boolean
+        ): IMediaProjection {
+            val existingProjection =
+                if (reviewGrantedConsentRequired) service.getProjection(uid, packageName) else null
+            return existingProjection
+                ?: service.createProjection(
+                    uid,
+                    packageName,
+                    MediaProjectionManager.TYPE_SCREEN_CAPTURE,
+                    false /* permanentGrant */
+                )
+        }
+
+        /**
+         * This method is called when a host app reuses the consent token. If the token is being
+         * used more than once, ask the user to review their consent and send the reviewed result.
+         *
+         * @param consentResult consent result to update
+         * @param reviewGrantedConsentRequired if user must review already-granted consent that the
+         *   host app is attempting to reuse
+         * @param projection projection token associated with the consent result, or null if the
+         *   result is for cancelling.
+         */
+        @JvmStatic
+        fun setReviewedConsentIfNeeded(
+            @ReviewGrantedConsentResult consentResult: Int,
+            reviewGrantedConsentRequired: Boolean,
+            projection: IMediaProjection?
+        ) {
+            // Only send the result to the server, when the user needed to review the re-used
+            // consent token.
+            if (
+                reviewGrantedConsentRequired && consentResult != ReviewGrantedConsentResult.UNKNOWN
+            ) {
+                try {
+                    service.setUserReviewGrantedConsentResult(consentResult, projection)
+                } catch (e: RemoteException) {
+                    // If we are unable to pass back the result, capture continues with blank frames
+                    Log.e(TAG, "Unable to set required consent result for token re-use", e)
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
index 37d956b..e38abc2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
@@ -21,9 +21,9 @@
 import android.text.format.DateUtils
 import androidx.annotation.UiThread
 import androidx.lifecycle.Observer
+import com.android.app.animation.Interpolators
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.media.controls.ui.SquigglyProgress
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
index 3669493..b46ebb2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
@@ -34,10 +34,10 @@
 import android.util.MathUtils
 import android.view.View
 import androidx.annotation.Keep
+import com.android.app.animation.Interpolators
 import com.android.internal.graphics.ColorUtils
 import com.android.internal.graphics.ColorUtils.blendARGB
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 import org.xmlpull.v1.XmlPullParser
 
 private const val BACKGROUND_ANIM_DURATION = 370L
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
index dd5c2bf..937a618 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
@@ -35,9 +35,9 @@
 import android.util.AttributeSet
 import android.util.MathUtils.lerp
 import androidx.annotation.Keep
+import com.android.app.animation.Interpolators
 import com.android.internal.graphics.ColorUtils
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 import org.xmlpull.v1.XmlPullParser
 
 private const val RIPPLE_ANIM_DURATION = 800L
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index ab39442..0aa4349 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -166,6 +166,7 @@
             }
         }
 
+    /** Whether the media card currently has the "expanded" layout */
     @VisibleForTesting
     var currentlyExpanded = true
         set(value) {
@@ -501,6 +502,7 @@
         mediaHostStatesManager.addCallback(
             object : MediaHostStatesManager.Callback {
                 override fun onHostStateChanged(location: Int, mediaHostState: MediaHostState) {
+                    updateUserVisibility()
                     if (location == desiredLocation) {
                         onDesiredLocationChanged(desiredLocation, mediaHostState, animate = false)
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 40027a1..f9d3094 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -72,6 +72,7 @@
 import androidx.annotation.UiThread;
 import androidx.constraintlayout.widget.ConstraintSet;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -82,7 +83,6 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.GhostedViewLaunchAnimatorController;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.bluetooth.BroadcastDialogController;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.dagger.qualifiers.Background;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 54237ce..fe8ebaf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -33,9 +33,9 @@
 import android.view.ViewGroup
 import android.view.ViewGroupOverlay
 import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardViewController
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dreams.DreamOverlayStateController
@@ -257,7 +257,7 @@
             if (value && (isLockScreenShadeVisibleToUser() || isHomeScreenShadeVisibleToUser())) {
                 mediaCarouselController.logSmartspaceImpression(value)
             }
-            mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
+            updateUserVisibility()
         }
 
     /**
@@ -460,8 +460,7 @@
                     ) {
                         mediaCarouselController.logSmartspaceImpression(qsExpanded)
                     }
-                    mediaCarouselController.mediaCarouselScrollHandler.visibleToUser =
-                        isVisibleToUser()
+                    updateUserVisibility()
                 }
 
                 override fun onDozeAmountChanged(linear: Float, eased: Float) {
@@ -480,8 +479,7 @@
                         qsExpanded = false
                         closeGuts()
                     }
-                    mediaCarouselController.mediaCarouselScrollHandler.visibleToUser =
-                        isVisibleToUser()
+                    updateUserVisibility()
                 }
 
                 override fun onExpandedChanged(isExpanded: Boolean) {
@@ -489,8 +487,7 @@
                     if (isHomeScreenShadeVisibleToUser()) {
                         mediaCarouselController.logSmartspaceImpression(qsExpanded)
                     }
-                    mediaCarouselController.mediaCarouselScrollHandler.visibleToUser =
-                        isVisibleToUser()
+                    updateUserVisibility()
                 }
             }
         )
@@ -532,9 +529,7 @@
             }
         )
 
-        mediaCarouselController.updateUserVisibility = {
-            mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
-        }
+        mediaCarouselController.updateUserVisibility = this::updateUserVisibility
         mediaCarouselController.updateHostVisibility = {
             mediaHosts.forEach { it?.updateViewVisibility() }
         }
@@ -1180,11 +1175,15 @@
         return isCrossFadeAnimatorRunning
     }
 
-    /** Returns true when the media card could be visible to the user if existed. */
-    private fun isVisibleToUser(): Boolean {
-        return isLockScreenVisibleToUser() ||
-            isLockScreenShadeVisibleToUser() ||
-            isHomeScreenShadeVisibleToUser()
+    /** Update whether or not the media carousel could be visible to the user */
+    private fun updateUserVisibility() {
+        val shadeVisible =
+            isLockScreenVisibleToUser() ||
+                isLockScreenShadeVisibleToUser() ||
+                isHomeScreenShadeVisibleToUser()
+        val mediaVisible = qsExpanded || hasActiveMediaOrRecommendation
+        mediaCarouselController.mediaCarouselScrollHandler.visibleToUser =
+            shadeVisible && mediaVisible
     }
 
     private fun isLockScreenVisibleToUser(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
index e9b2cf2..583c626 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
@@ -31,8 +31,8 @@
 import android.util.MathUtils.lerpInv
 import android.util.MathUtils.lerpInvSat
 import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
 import com.android.internal.graphics.ColorUtils
-import com.android.systemui.animation.Interpolators
 import kotlin.math.abs
 import kotlin.math.cos
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 78082c3..77ff036 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -36,7 +36,7 @@
 import android.view.View.ACCESSIBILITY_LIVE_REGION_NONE
 import com.android.internal.widget.CachingIconView
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.ui.binder.TintedIconViewBinder
 import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskLabelLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskLabelLoader.kt
index eadcb93..1be8b70 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskLabelLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskLabelLoader.kt
@@ -20,6 +20,7 @@
 import android.content.ComponentName
 import android.content.pm.PackageManager
 import android.os.UserHandle
+import android.util.Log
 import com.android.systemui.dagger.qualifiers.Background
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -36,18 +37,27 @@
     private val packageManager: PackageManager
 ) : RecentTaskLabelLoader {
 
+    private val TAG = "RecentTaskLabelLoader"
+
     override suspend fun loadLabel(
         @UserIdInt userId: Int,
         componentName: ComponentName
     ): CharSequence? =
         withContext(coroutineDispatcher) {
-            val userHandle = UserHandle(userId)
-            val appInfo =
-                packageManager.getApplicationInfo(
-                    componentName.packageName,
-                    PackageManager.ApplicationInfoFlags.of(0 /* no flags */)
-                )
-            val label = packageManager.getApplicationLabel(appInfo)
-            return@withContext packageManager.getUserBadgedLabel(label, userHandle)
+            var badgedLabel: CharSequence? = null
+            try {
+                val appInfo =
+                    packageManager.getApplicationInfoAsUser(
+                        componentName.packageName,
+                        PackageManager.ApplicationInfoFlags.of(0 /* no flags */),
+                        userId
+                    )
+                val label = packageManager.getApplicationLabel(appInfo)
+                val userHandle = UserHandle(userId)
+                badgedLabel = packageManager.getUserBadgedLabel(label, userHandle)
+            } catch (e: PackageManager.NameNotFoundException) {
+                Log.e(TAG, "Unable to get application info", e)
+            }
+            return@withContext badgedLabel
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
index 64f97f2..2d75359 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
@@ -68,7 +68,7 @@
                     }
                     launch {
                         val label = labelLoader.loadLabel(task.userId, component)
-                        root.contentDescription = label
+                        root.contentDescription = label ?: root.context.getString(R.string.unknown)
                     }
                 }
                 launch {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 94f01b8..146b5f5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -58,11 +58,11 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.Utils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
 import com.android.systemui.navigationbar.buttons.ContextualButton;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
index 0218016..10084bd 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.navigationbar.buttons;
 
-import static com.android.systemui.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.LINEAR;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -24,9 +24,6 @@
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
 
-import com.android.systemui.Dependency;
-import com.android.systemui.assist.AssistManager;
-
 import java.util.ArrayList;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index b9ef916..41e3e6d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -89,6 +89,7 @@
 import com.android.systemui.tracing.nano.SystemUiTraceProto;
 import com.android.systemui.util.Assert;
 import com.android.wm.shell.back.BackAnimation;
+import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.pip.Pip;
 
 import java.io.PrintWriter;
@@ -190,6 +191,7 @@
     private final WindowManager mWindowManager;
     private final IWindowManager mWindowManagerService;
     private final Optional<Pip> mPipOptional;
+    private final Optional<DesktopMode> mDesktopModeOptional;
     private final FalsingManager mFalsingManager;
     private final Configuration mLastReportedConfig = new Configuration();
     // Activities which should not trigger Back gesture.
@@ -204,6 +206,7 @@
     private final Rect mPipExcludedBounds = new Rect();
     private final Rect mNavBarOverlayExcludedBounds = new Rect();
     private final Region mExcludeRegion = new Region();
+    private final Region mDesktopModeExcludeRegion = new Region();
     private final Region mUnrestrictedExcludeRegion = new Region();
     private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider;
     private final Provider<BackGestureTfClassifierProvider>
@@ -328,6 +331,9 @@
     private final Consumer<Boolean> mOnIsInPipStateChangedListener =
             (isInPip) -> mIsInPip = isInPip;
 
+    private final Consumer<Region> mDesktopCornersChangedListener =
+            (desktopExcludeRegion) -> mDesktopModeExcludeRegion.set(desktopExcludeRegion);
+
     private final UserTracker.Callback mUserChangedCallback =
             new UserTracker.Callback() {
                 @Override
@@ -352,6 +358,7 @@
             WindowManager windowManager,
             IWindowManager windowManagerService,
             Optional<Pip> pipOptional,
+            Optional<DesktopMode> desktopModeOptional,
             FalsingManager falsingManager,
             Provider<NavigationBarEdgePanel> navigationBarEdgePanelProvider,
             Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
@@ -372,6 +379,7 @@
         mWindowManager = windowManager;
         mWindowManagerService = windowManagerService;
         mPipOptional = pipOptional;
+        mDesktopModeOptional = desktopModeOptional;
         mFalsingManager = falsingManager;
         mNavBarEdgePanelProvider = navigationBarEdgePanelProvider;
         mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
@@ -580,6 +588,9 @@
                     mMainExecutor::execute, mOnPropertiesChangedListener);
             mPipOptional.ifPresent(
                     pip -> pip.setOnIsInPipStateChangedListener(mOnIsInPipStateChangedListener));
+            mDesktopModeOptional.ifPresent(
+                    dm -> dm.addDesktopGestureExclusionRegionListener(
+                            mDesktopCornersChangedListener, mMainExecutor));
 
             try {
                 mWindowManagerService.registerSystemGestureExclusionListener(
@@ -802,11 +813,17 @@
                 mDisplaySize.y - insets.bottom);
     }
 
+    private boolean desktopExcludeRegionContains(int x, int y) {
+        return mDesktopModeExcludeRegion.contains(x, y);
+    }
+
     private boolean isWithinTouchRegion(int x, int y) {
         // If the point is inside the PiP or Nav bar overlay excluded bounds, then ignore the back
         // gesture
         final boolean isInsidePip = mIsInPip && mPipExcludedBounds.contains(x, y);
-        if (isInsidePip || mNavBarOverlayExcludedBounds.contains(x, y)) {
+        final boolean isInDesktopExcludeRegion = desktopExcludeRegionContains(x, y);
+        if (isInsidePip || isInDesktopExcludeRegion
+                || mNavBarOverlayExcludedBounds.contains(x, y)) {
             return false;
         }
 
@@ -1136,6 +1153,7 @@
         pw.println("  mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion);
         pw.println("  mIsInPip=" + mIsInPip);
         pw.println("  mPipExcludedBounds=" + mPipExcludedBounds);
+        pw.println("  mDesktopModeExclusionRegion=" + mDesktopModeExcludeRegion);
         pw.println("  mNavBarOverlayExcludedBounds=" + mNavBarOverlayExcludedBounds);
         pw.println("  mEdgeWidthLeft=" + mEdgeWidthLeft);
         pw.println("  mEdgeWidthRight=" + mEdgeWidthRight);
@@ -1206,6 +1224,7 @@
         private final WindowManager mWindowManager;
         private final IWindowManager mWindowManagerService;
         private final Optional<Pip> mPipOptional;
+        private final Optional<DesktopMode> mDesktopModeOptional;
         private final FalsingManager mFalsingManager;
         private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider;
         private final Provider<BackGestureTfClassifierProvider>
@@ -1227,6 +1246,7 @@
                        WindowManager windowManager,
                        IWindowManager windowManagerService,
                        Optional<Pip> pipOptional,
+                       Optional<DesktopMode> desktopModeOptional,
                        FalsingManager falsingManager,
                        Provider<NavigationBarEdgePanel> navBarEdgePanelProvider,
                        Provider<BackGestureTfClassifierProvider>
@@ -1246,6 +1266,7 @@
             mWindowManager = windowManager;
             mWindowManagerService = windowManagerService;
             mPipOptional = pipOptional;
+            mDesktopModeOptional = desktopModeOptional;
             mFalsingManager = falsingManager;
             mNavBarEdgePanelProvider = navBarEdgePanelProvider;
             mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
@@ -1270,6 +1291,7 @@
                     mWindowManager,
                     mWindowManagerService,
                     mPipOptional,
+                    mDesktopModeOptional,
                     mFalsingManager,
                     mNavBarEdgePanelProvider,
                     mBackGestureTfClassifierProviderProvider,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 590efbb..ff22398 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -48,10 +48,10 @@
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.util.LatencyTracker;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
 import com.android.systemui.settings.DisplayTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index aab898e..8aec0c6 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -284,7 +284,15 @@
 
     /** @see OnRoleHoldersChangedListener */
     fun onRoleHoldersChanged(roleName: String, user: UserHandle) {
-        if (roleName == ROLE_NOTES) updateNoteTaskAsUser(user)
+        if (roleName != ROLE_NOTES) return
+
+        if (user == userTracker.userHandle) {
+            updateNoteTaskAsUser(user)
+        } else {
+            // TODO(b/278729185): Replace fire and forget service with a bounded service.
+            val intent = NoteTaskControllerUpdateService.createIntent(context)
+            context.startServiceAsUser(intent, user)
+        }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt
new file mode 100644
index 0000000..26b35cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.notetask
+
+import android.content.Context
+import android.content.Intent
+import androidx.lifecycle.LifecycleService
+import javax.inject.Inject
+
+/**
+ * A fire & forget service for updating note task shortcuts.
+ *
+ * The main use is to update shortcuts in different user by launching it using `startServiceAsUser`.
+ * The service will open with access to a context from that user, trigger
+ * [NoteTaskController.updateNoteTaskAsUser] and [stopSelf] immediately.
+ *
+ * The fire and forget approach was created due to its simplicity but may use unnecessary resources
+ * by recreating the services. We will investigate its impacts and consider to move to a bounded
+ * services - the implementation is more complex as a bounded service is asynchronous by default.
+ *
+ * TODO(b/278729185): Replace fire and forget service with a bounded service.
+ */
+@InternalNoteTaskApi
+class NoteTaskControllerUpdateService
+@Inject
+constructor(
+    val controller: NoteTaskController,
+) : LifecycleService() {
+
+    override fun onCreate() {
+        super.onCreate()
+        // TODO(b/278729185): Replace fire and forget service with a bounded service.
+        controller.updateNoteTaskAsUser(user)
+        stopSelf()
+    }
+
+    companion object {
+        fun createIntent(context: Context): Intent =
+            Intent(context, NoteTaskControllerUpdateService::class.java)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index 1839dfd..a166393 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
+@file:OptIn(InternalNoteTaskApi::class)
+
 package com.android.systemui.notetask
 
 import android.app.Activity
+import android.app.Service
 import android.app.role.RoleManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -34,6 +37,9 @@
 @Module(includes = [NoteTaskQuickAffordanceModule::class])
 interface NoteTaskModule {
 
+    @[Binds IntoMap ClassKey(NoteTaskControllerUpdateService::class)]
+    fun NoteTaskControllerUpdateService.bindNoteTaskControllerUpdateService(): Service
+
     @[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
     fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 0641eec..a3b901b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -44,6 +44,7 @@
 import android.widget.TextView
 import androidx.annotation.GuardedBy
 import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
 import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
@@ -201,7 +202,7 @@
     @GuardedBy("lock")
     private val appListAdapter: AppListAdapter = AppListAdapter()
 
-    @GuardedBy("lock")
+    /* Only mutate on the background thread */
     private var runningApps: ArrayMap<UserPackage, RunningApp> = ArrayMap()
 
     private val userTrackerCallback = object : UserTracker.Callback {
@@ -374,11 +375,6 @@
     override fun showDialog(expandable: Expandable?) {
         synchronized(lock) {
             if (dialog == null) {
-
-                runningTaskIdentifiers.keys.forEach {
-                    it.updateUiControl()
-                }
-
                 val dialog = SystemUIDialog(context)
                 dialog.setTitle(R.string.fgs_manager_dialog_title)
                 dialog.setMessage(R.string.fgs_manager_dialog_message)
@@ -421,33 +417,53 @@
                     }
                 }
 
-                backgroundExecutor.execute {
-                    synchronized(lock) {
-                        updateAppItemsLocked()
-                    }
-                }
+                updateAppItemsLocked(refreshUiControls = true)
             }
         }
     }
 
     @GuardedBy("lock")
-    private fun updateAppItemsLocked() {
+    private fun updateAppItemsLocked(refreshUiControls: Boolean = false) {
         if (dialog == null) {
-            runningApps.clear()
+            backgroundExecutor.execute {
+                clearRunningApps()
+            }
             return
         }
 
-        val addedPackages = runningTaskIdentifiers.keys.filter {
-            currentProfileIds.contains(it.userId) &&
+        val packagesToStartTime = runningTaskIdentifiers.mapValues { it.value.startTime }
+        val profileIds = currentProfileIds.toSet()
+        backgroundExecutor.execute {
+            updateAppItems(packagesToStartTime, profileIds, refreshUiControls)
+        }
+    }
+
+    /**
+     * Must be called on the background thread.
+     */
+    @WorkerThread
+    private fun updateAppItems(
+        packages: Map<UserPackage, Long>,
+        profileIds: Set<Int>,
+        refreshUiControls: Boolean = true
+    ) {
+        if (refreshUiControls) {
+            packages.forEach { (pkg, _) ->
+                pkg.updateUiControl()
+            }
+        }
+
+        val addedPackages = packages.keys.filter {
+            profileIds.contains(it.userId) &&
                     it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true
         }
-        val removedPackages = runningApps.keys.filter { !runningTaskIdentifiers.containsKey(it) }
+        val removedPackages = runningApps.keys.filter { it !in packages }
 
         addedPackages.forEach {
             val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId)
             runningApps[it] = RunningApp(
                 it.userId, it.packageName,
-                runningTaskIdentifiers[it]!!.startTime, it.uiControl,
+                packages[it]!!, it.uiControl,
                 packageManager.getApplicationLabel(ai),
                 packageManager.getUserBadgedIcon(
                     packageManager.getApplicationIcon(ai), UserHandle.of(it.userId)
@@ -472,6 +488,14 @@
         }
     }
 
+    /**
+     * Must be called on the background thread.
+     */
+    @WorkerThread
+    private fun clearRunningApps() {
+        runningApps.clear()
+    }
+
     private fun stopPackage(userId: Int, packageName: String, timeStarted: Long) {
         logEvent(stopped = true, packageName, userId, timeStarted)
         val userPackageKey = UserPackage(userId, packageName)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index a7aac5a..463c79c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -26,7 +26,7 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.qs.QSTile;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 584d27f8..09cc2c5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -18,9 +18,10 @@
 
 import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
 import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
-import static com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
+import static com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.content.res.Configuration;
@@ -43,10 +44,10 @@
 import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.LifecycleRegistry;
 
+import com.android.app.animation.Interpolators;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.compose.ComposeFacade;
 import com.android.systemui.dump.DumpManager;
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
index db0052a..f63bf07 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
@@ -43,6 +43,7 @@
 ) : SystemUIDialog(context), AdapterView.OnItemSelectedListener {
     private lateinit var dialogTitle: TextView
     private lateinit var startButton: TextView
+    private lateinit var cancelButton: TextView
     private lateinit var warning: TextView
     private lateinit var screenShareModeSpinner: Spinner
     var selectedScreenShareOption: ScreenShareOption = screenShareOptions.first()
@@ -57,7 +58,7 @@
         dialogTitle = findViewById(R.id.screen_share_dialog_title)
         warning = findViewById(R.id.text_warning)
         startButton = findViewById(R.id.button_start)
-        findViewById<TextView>(R.id.button_cancel).setOnClickListener { dismiss() }
+        cancelButton = findViewById(R.id.button_cancel)
         updateIcon()
         initScreenShareOptions()
         createOptionsView(getOptionsViewLayoutId())
@@ -117,6 +118,10 @@
         startButton.setOnClickListener(listener)
     }
 
+    protected fun setCancelButtonOnClickListener(listener: View.OnClickListener?) {
+        cancelButton.setOnClickListener(listener)
+    }
+
     // Create additional options that is shown under the share mode spinner
     // Eg. the audio and tap toggles in SysUI Recorder
     @LayoutRes protected open fun getOptionsViewLayoutId(): Int? = null
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
index c5a82ce1..201557c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
@@ -23,6 +23,7 @@
 class MediaProjectionPermissionDialog(
     context: Context?,
     private val onStartRecordingClicked: Runnable,
+    private val onCancelClicked: Runnable,
     private val appName: String?
 ) : BaseScreenSharePermissionDialog(context, createOptionList(appName), appName) {
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -39,6 +40,10 @@
             onStartRecordingClicked.run()
             dismiss()
         }
+        setCancelButtonOnClickListener {
+            onCancelClicked.run()
+            dismiss()
+        }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 3711a2f..fbf134d 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -103,7 +103,7 @@
     @GuardedBy("callbacks")
     private val callbacks: MutableList<DataItem> = ArrayList()
 
-    fun initialize(startingUser: Int) {
+    open fun initialize(startingUser: Int) {
         if (initialized) {
             return
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
index b445000..5850a84 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
@@ -47,19 +47,36 @@
             viewsIdToTranslate =
                 setOf(
                     ViewIdToTranslate(R.id.quick_settings_panel, START, filterShade),
-                    ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade),
-                    ViewIdToTranslate(R.id.statusIcons, END, filterShade),
-                    ViewIdToTranslate(R.id.privacy_container, END, filterShade),
-                    ViewIdToTranslate(R.id.batteryRemainingIcon, END, filterShade),
-                    ViewIdToTranslate(R.id.carrier_group, END, filterShade),
-                    ViewIdToTranslate(R.id.clock, START, filterShade),
-                    ViewIdToTranslate(R.id.date, START, filterShade)),
+                    ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade)),
             progressProvider = progressProvider)
     }
 
+    private val translateAnimatorStatusBar by lazy {
+        UnfoldConstantTranslateAnimator(
+            viewsIdToTranslate =
+            setOf(
+                ViewIdToTranslate(R.id.statusIcons, END, filterShade),
+                ViewIdToTranslate(R.id.privacy_container, END, filterShade),
+                ViewIdToTranslate(R.id.batteryRemainingIcon, END, filterShade),
+                ViewIdToTranslate(R.id.carrier_group, END, filterShade),
+                ViewIdToTranslate(R.id.clock, START, filterShade),
+                ViewIdToTranslate(R.id.date, START, filterShade)
+            ),
+            progressProvider = progressProvider
+        )
+    }
+
     fun setup(root: ViewGroup) {
         val translationMax =
             context.resources.getDimensionPixelSize(R.dimen.notification_side_paddings).toFloat()
         translateAnimator.init(root, translationMax)
+        val splitShadeStatusBarViewGroup: ViewGroup? =
+            root.findViewById(R.id.split_shade_status_bar)
+        if (splitShadeStatusBarViewGroup != null) {
+            translateAnimatorStatusBar.init(
+                splitShadeStatusBarViewGroup,
+                translationMax
+            )
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index d28ccff..be92bd4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -22,10 +22,10 @@
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
+import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
+import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
-import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
-import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
 import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
 import static com.android.systemui.classifier.Classifier.GENERIC;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -59,8 +59,6 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.hardware.biometrics.SensorLocationInternal;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -90,6 +88,7 @@
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
@@ -113,7 +112,6 @@
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.LaunchAnimator;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.classifier.Classifier;
@@ -224,8 +222,6 @@
 import com.android.systemui.util.time.SystemClock;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
-import kotlin.Unit;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -236,6 +232,8 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
+import kotlin.Unit;
+
 import kotlinx.coroutines.CoroutineDispatcher;
 
 @CentralSurfacesComponent.CentralSurfacesScope
@@ -410,7 +408,8 @@
     private int mDisplayRightInset = 0; // in pixels
     private int mDisplayLeftInset = 0; // in pixels
 
-    private final KeyguardClockPositionAlgorithm
+    @VisibleForTesting
+    KeyguardClockPositionAlgorithm
             mClockPositionAlgorithm =
             new KeyguardClockPositionAlgorithm();
     private final KeyguardClockPositionAlgorithm.Result
@@ -1493,11 +1492,9 @@
                         ? 1.0f : mInterpolatedDarkAmount;
 
         float udfpsAodTopLocation = -1f;
-        if (mUpdateMonitor.isUdfpsEnrolled() && mAuthController.getUdfpsProps().size() > 0) {
-            FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0);
-            final SensorLocationInternal location = props.getLocation();
-            udfpsAodTopLocation = location.sensorLocationY - location.sensorRadius
-                    - mUdfpsMaxYBurnInOffset;
+        if (mUpdateMonitor.isUdfpsEnrolled() && mAuthController.getUdfpsLocation() != null) {
+            udfpsAodTopLocation = mAuthController.getUdfpsLocation().y
+                    - mAuthController.getUdfpsRadius() - mUdfpsMaxYBurnInOffset;
         }
 
         mClockPositionAlgorithm.setup(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index ef14d1c..7a79e85 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -50,6 +50,7 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
@@ -59,7 +60,6 @@
 import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index f0815e9..4131e7d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -34,10 +34,10 @@
 import android.widget.TextView
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.motion.widget.MotionLayout
+import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
 import com.android.systemui.Dumpable
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index b0b9ab2..4c6673c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -17,15 +17,28 @@
 package com.android.systemui.shade
 
 import android.view.LayoutInflater
+import com.android.systemui.CoreStartable
 import com.android.systemui.R
+import com.android.systemui.biometrics.AuthRippleController
+import com.android.systemui.biometrics.AuthRippleView
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import dagger.Binds
 import dagger.Module
 import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 
 /** Module for classes related to the notification shade. */
 @Module
 abstract class ShadeModule {
+
+    @Binds
+    @IntoMap
+    @ClassKey(AuthRippleController::class)
+    abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable
+
     companion object {
         @Provides
         @SysUISingleton
@@ -59,5 +72,22 @@
         ): NotificationPanelView {
             return notificationShadeWindowView.findViewById(R.id.notification_panel)
         }
+
+        @Provides
+        @SysUISingleton
+        fun providesLightRevealScrim(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): LightRevealScrim {
+            return notificationShadeWindowView.findViewById(R.id.light_reveal_scrim)
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesAuthRippleView(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): AuthRippleView? {
+            return notificationShadeWindowView.findViewById(R.id.auth_ripple)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index 63179da..c1ebf12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -18,8 +18,8 @@
 
 import android.view.View;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
index 54b341f..1a32d70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
@@ -38,8 +38,8 @@
 import android.view.animation.Interpolator;
 import android.widget.ImageView;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 9421524..823bb35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -16,7 +16,7 @@
 import android.util.MathUtils.lerp
 import android.view.View
 import android.view.animation.PathInterpolator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
 import com.android.systemui.util.getColorWithAlpha
 import com.android.systemui.util.leak.RotationUtils
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index faf592e..2258968 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -18,7 +18,7 @@
 import com.android.systemui.ExpandHelper
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.biometrics.UdfpsKeyguardViewController
 import com.android.systemui.classifier.Classifier
 import com.android.systemui.classifier.FalsingCollector
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 72ae16e..fb88a96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -44,9 +44,9 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 8dc7842..d37cbcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -33,7 +33,7 @@
 import androidx.dynamicanimation.animation.SpringAnimation
 import androidx.dynamicanimation.animation.SpringForce
 import com.android.systemui.Dumpable
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 7eb63da..5c3bacc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -35,10 +35,10 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 976924a..f9d4f1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -31,7 +31,7 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
index 575f354..f1e51e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
@@ -4,7 +4,7 @@
 import android.content.res.Configuration
 import android.util.MathUtils
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import dagger.assisted.Assisted
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
index 572c0e0..3d574ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
@@ -8,7 +8,7 @@
 import android.view.animation.PathInterpolator
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 7755003..91c08a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -52,10 +52,10 @@
 
 import androidx.core.graphics.ColorUtils;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.util.drawable.DrawableSize;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 79d01b4a..d6a14604 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -42,6 +42,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -49,7 +50,6 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index 2fa27ee..67ab060 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -25,8 +25,8 @@
 import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.TransformState;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index bfc4e9c..eddb683 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -26,7 +26,7 @@
 import android.widget.FrameLayout
 import com.android.internal.annotations.GuardedBy
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
index 0446165..b09b9f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -21,8 +21,8 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.notification.row.HybridNotificationView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
index c22dbf6..785e65d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
@@ -3,7 +3,7 @@
 import android.util.MathUtils
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.animation.LaunchAnimator
 import kotlin.math.min
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
index c22cd1b..5a14200 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -23,13 +23,13 @@
 import android.view.ViewGroup;
 import android.widget.TextView;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.widget.IMessagingLayout;
 import com.android.internal.widget.MessagingGroup;
 import com.android.internal.widget.MessagingImageMessage;
 import com.android.internal.widget.MessagingLinearLayout;
 import com.android.internal.widget.MessagingMessage;
 import com.android.internal.widget.MessagingPropertyAnimator;
-import com.android.systemui.animation.Interpolators;
 
 import java.util.ArrayList;
 import java.util.HashMap;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
index 3fc7b13..a045698 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
@@ -24,8 +24,8 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
 import java.util.function.Consumer;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index fe0b28d..9ba2199 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -21,8 +21,8 @@
 import androidx.annotation.VisibleForTesting
 import androidx.core.animation.ObjectAnimator
 import com.android.systemui.Dumpable
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.animation.InterpolatorsAndroidX
+import com.android.app.animation.Interpolators
+import com.android.app.animation.InterpolatorsAndroidX
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 5d07cac..57d20246 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -24,7 +24,7 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
-import com.android.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
 import com.android.systemui.statusbar.notification.stack.AnimationFilter;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ViewState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index 6deef2e..76ff97d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -383,11 +383,11 @@
     }
 
     fun debugString() = buildString {
-        append("TargetView: ${targetView.hashCode()} ")
-        append("Top: $topRoundness ")
-        append(topRoundnessMap.map { "${it.key} ${it.value}" })
-        append(" Bottom: $bottomRoundness ")
-        append(bottomRoundnessMap.map { "${it.key} ${it.value}" })
+        append("Roundable { ")
+        append("top: { value: $topRoundness, requests: $topRoundnessMap}")
+        append(", ")
+        append("bottom: { value: $bottomRoundness, requests: $bottomRoundnessMap}")
+        append("}")
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index 9f9fba4..90eb630 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -23,11 +23,11 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.widget.MessagingImageMessage;
 import com.android.internal.widget.MessagingPropertyAnimator;
 import com.android.internal.widget.ViewClippingUtil;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.ViewTransformationHelper;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
index dc16274..16f1a45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
@@ -22,7 +22,7 @@
 import android.view.View
 import android.view.ViewGroup
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 
 /**
  * Class to help with fading of view groups without fading one subview
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 0529c94..23b5241 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -38,8 +38,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.dagger.IncomingHeader
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
 import com.android.systemui.statusbar.notification.logKey
 import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
 import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -69,12 +68,12 @@
     private val mSystemClock: SystemClock,
     private val mHeadsUpManager: HeadsUpManager,
     private val mHeadsUpViewBinder: HeadsUpViewBinder,
-    private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider,
+    private val mVisualInterruptionDecisionProvider: VisualInterruptionDecisionProvider,
     private val mRemoteInputManager: NotificationRemoteInputManager,
     private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider,
     private val mFlags: NotifPipelineFlags,
     @IncomingHeader private val mIncomingHeaderController: NodeController,
-    @Main private val mExecutor: DelayableExecutor,
+    @Main private val mExecutor: DelayableExecutor
 ) : Coordinator {
     private val mEntriesBindingUntil = ArrayMap<String, Long>()
     private val mEntriesUpdateTimes = ArrayMap<String, Long>()
@@ -388,18 +387,21 @@
         override fun onEntryAdded(entry: NotificationEntry) {
             // First check whether this notification should launch a full screen intent, and
             // launch it if needed.
-            val fsiDecision = mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)
-            mNotificationInterruptStateProvider.logFullScreenIntentDecision(entry, fsiDecision)
-            if (fsiDecision.shouldLaunch) {
+            val fsiDecision =
+                mVisualInterruptionDecisionProvider.makeUnloggedFullScreenIntentDecision(entry)
+            mVisualInterruptionDecisionProvider.logFullScreenIntentDecision(fsiDecision)
+            if (fsiDecision.shouldInterrupt) {
                 mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
-            } else if (fsiDecision == FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) {
+            } else if (fsiDecision.wouldInterruptWithoutDnd) {
                 // If DND was the only reason this entry was suppressed, note it for potential
                 // reconsideration on later ranking updates.
                 addForFSIReconsideration(entry, mSystemClock.currentTimeMillis())
             }
 
-            // shouldHeadsUp includes check for whether this notification should be filtered
-            val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
+            // makeAndLogHeadsUpDecision includes check for whether this notification should be
+            // filtered
+            val shouldHeadsUpEver =
+                mVisualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(entry).shouldInterrupt
             mPostedEntries[entry.key] = PostedEntry(
                 entry,
                 wasAdded = true,
@@ -420,7 +422,8 @@
          * up again.
          */
         override fun onEntryUpdated(entry: NotificationEntry) {
-            val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
+            val shouldHeadsUpEver =
+                mVisualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(entry).shouldInterrupt
             val shouldHeadsUpAgain = shouldHunAgain(entry)
             val isAlerting = mHeadsUpManager.isAlerting(entry.key)
             val isBinding = isEntryBinding(entry)
@@ -510,26 +513,26 @@
                 // If any of these entries are no longer suppressed, launch the FSI now.
                 if (isCandidateForFSIReconsideration(entry)) {
                     val decision =
-                        mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)
-                    if (decision.shouldLaunch) {
+                        mVisualInterruptionDecisionProvider.makeUnloggedFullScreenIntentDecision(
+                            entry
+                        )
+                    if (decision.shouldInterrupt) {
                         // Log both the launch of the full screen and also that this was via a
                         // ranking update, and finally revoke candidacy for FSI reconsideration
-                        mLogger.logEntryUpdatedToFullScreen(entry.key, decision.name)
-                        mNotificationInterruptStateProvider.logFullScreenIntentDecision(
-                            entry, decision)
+                        mLogger.logEntryUpdatedToFullScreen(entry.key, decision.logReason)
+                        mVisualInterruptionDecisionProvider.logFullScreenIntentDecision(decision)
                         mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
                         mFSIUpdateCandidates.remove(entry.key)
 
                         // if we launch the FSI then this is no longer a candidate for HUN
                         continue
-                    } else if (decision == FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) {
+                    } else if (decision.wouldInterruptWithoutDnd) {
                         // decision has not changed; no need to log
                     } else {
                         // some other condition is now blocking FSI; log that and revoke candidacy
                         // for FSI reconsideration
-                        mLogger.logEntryDisqualifiedFromFullScreen(entry.key, decision.name)
-                        mNotificationInterruptStateProvider.logFullScreenIntentDecision(
-                            entry, decision)
+                        mLogger.logEntryDisqualifiedFromFullScreen(entry.key, decision.logReason)
+                        mVisualInterruptionDecisionProvider.logFullScreenIntentDecision(decision)
                         mFSIUpdateCandidates.remove(entry.key)
                     }
                 }
@@ -539,13 +542,18 @@
                 //   state
                 // - if it is present in PostedEntries and the previous state of shouldHeadsUp
                 //   differs from the updated one
-                val shouldHeadsUpEver = mNotificationInterruptStateProvider.checkHeadsUp(entry,
-                                /* log= */ false)
+                val decision =
+                    mVisualInterruptionDecisionProvider.makeUnloggedHeadsUpDecision(entry)
+                val shouldHeadsUpEver = decision.shouldInterrupt
                 val postedShouldHeadsUpEver = mPostedEntries[entry.key]?.shouldHeadsUpEver ?: false
                 val shouldUpdateEntry = postedShouldHeadsUpEver != shouldHeadsUpEver
 
                 if (shouldUpdateEntry) {
-                    mLogger.logEntryUpdatedByRanking(entry.key, shouldHeadsUpEver)
+                    mLogger.logEntryUpdatedByRanking(
+                        entry.key,
+                        shouldHeadsUpEver,
+                        decision.logReason
+                    )
                     onEntryUpdated(entry)
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index e936559..32c3c66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -61,12 +61,13 @@
         })
     }
 
-    fun logEntryUpdatedByRanking(key: String, shouldHun: Boolean) {
+    fun logEntryUpdatedByRanking(key: String, shouldHun: Boolean, reason: String) {
         buffer.log(TAG, LogLevel.DEBUG, {
             str1 = key
             bool1 = shouldHun
+            str2 = reason
         }, {
-            "updating entry via ranking applied: $str1 updated shouldHeadsUp=$bool1"
+            "updating entry via ranking applied: $str1 updated shouldHeadsUp=$bool1 because $str2"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
index f2216fc..ebba4b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
@@ -36,6 +36,8 @@
         SHOULD_INTERRUPT(shouldInterrupt = true),
         SHOULD_NOT_INTERRUPT(shouldInterrupt = false);
 
+        override val logReason = "unknown"
+
         companion object {
             fun of(booleanDecision: Boolean) =
                 if (booleanDecision) SHOULD_INTERRUPT else SHOULD_NOT_INTERRUPT
@@ -49,6 +51,7 @@
     ) : FullScreenIntentDecision {
         override val shouldInterrupt = originalDecision.shouldLaunch
         override val wouldInterruptWithoutDnd = originalDecision == NO_FSI_SUPPRESSED_ONLY_BY_DND
+        override val logReason = originalDecision.name
     }
 
     override fun addSuppressor(suppressor: NotificationInterruptSuppressor) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
index c0f4fcd..8024016 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
@@ -32,9 +32,12 @@
      * full-screen intent decisions.
      *
      * @property[shouldInterrupt] whether a visual interruption should be triggered
+     * @property[logReason] a log-friendly string explaining the reason for the decision; should be
+     *   used *only* for logging, not decision-making
      */
     interface Decision {
         val shouldInterrupt: Boolean
+        val logReason: String
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 766ad88..f70d5e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -31,12 +31,12 @@
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
 import com.android.settingslib.Utils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 10cdae3..2695410 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -64,6 +64,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -72,7 +73,6 @@
 import com.android.internal.widget.CachingIconView;
 import com.android.internal.widget.CallLayout;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -3649,16 +3649,12 @@
             } else {
                 pw.println("no viewState!!!");
             }
-            pw.println("Roundness: " + getRoundableState().debugString());
+            pw.println(getRoundableState().debugString());
 
             int transientViewCount = mChildrenContainer == null
                     ? 0 : mChildrenContainer.getTransientViewCount();
             if (mIsSummaryWithChildren || transientViewCount > 0) {
-                pw.println();
-                pw.print("ChildrenContainer");
-                pw.print(" visibility: " + mChildrenContainer.getVisibility());
-                pw.print(", alpha: " + mChildrenContainer.getAlpha());
-                pw.print(", translationY: " + mChildrenContainer.getTranslationY());
+                pw.println(mChildrenContainer.debugString());
                 pw.println();
                 List<ExpandableNotificationRow> notificationChildren = getAttachedChildren();
                 pw.print("Children: " + notificationChildren.size() + " {");
@@ -3726,12 +3722,6 @@
     }
 
     @Override
-    public String toString() {
-        String roundableStateDebug = "RoundableState = " + getRoundableState().debugString();
-        return "ExpandableNotificationRow:" + hashCode() + " { " + roundableStateDebug + " }";
-    }
-
-    @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         if (mUseRoundnessSourceTypes) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index b56bae1..7a2bee9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -45,10 +45,10 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 197caa2..9aa50e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -352,7 +352,7 @@
         IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
         super.dump(pw, args);
         DumpUtilsKt.withIncreasedIndent(pw, () -> {
-            pw.println("Roundness: " + getRoundableState().debugString());
+            pw.println(getRoundableState().debugString());
             if (DUMP_VERBOSE) {
                 pw.println("mCustomOutline: " + mCustomOutline + " mOutlineRect: " + mOutlineRect);
                 pw.println("mOutlineAlpha: " + mOutlineAlpha);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 5edff5f..9dbbc58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -32,9 +32,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.Roundable;
 import com.android.systemui.statusbar.notification.RoundableState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
index 49f17b6..6bbeebf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
@@ -17,8 +17,6 @@
 package com.android.systemui.statusbar.notification.row;
 
 import android.annotation.ColorInt;
-import android.annotation.DrawableRes;
-import android.annotation.StringRes;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
@@ -50,8 +48,8 @@
 
     // Footer label
     private TextView mSeenNotifsFooterTextView;
-    private @StringRes int mSeenNotifsFilteredText;
-    private int mUnlockIconSize;
+    private String mSeenNotifsFilteredText;
+    private Drawable mSeenNotifsFilteredIcon;
 
     public FooterView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -87,30 +85,12 @@
         mManageButton = findViewById(R.id.manage_text);
         mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
         updateResources();
-        updateText();
+        updateContent();
         updateColors();
     }
 
-    public void setFooterLabelTextAndIcon(@StringRes int text, @DrawableRes int icon) {
-        mSeenNotifsFilteredText = text;
-        if (mSeenNotifsFilteredText != 0) {
-            mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
-        } else {
-            mSeenNotifsFooterTextView.setText(null);
-        }
-        Drawable drawable;
-        if (icon == 0) {
-            drawable = null;
-        } else {
-            drawable = getResources().getDrawable(icon);
-            drawable.setBounds(0, 0, mUnlockIconSize, mUnlockIconSize);
-        }
-        mSeenNotifsFooterTextView.setCompoundDrawablesRelative(drawable, null, null, null);
-        updateFooterVisibilityMode();
-    }
-
-    private void updateFooterVisibilityMode() {
-        if (mSeenNotifsFilteredText != 0) {
+    public void setFooterLabelVisible(boolean isVisible) {
+        if (isVisible) {
             mManageButton.setVisibility(View.GONE);
             mClearAllButton.setVisibility(View.GONE);
             mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
@@ -141,10 +121,10 @@
             return;
         }
         mShowHistory = showHistory;
-        updateText();
+        updateContent();
     }
 
-    private void updateText() {
+    private void updateContent() {
         if (mShowHistory) {
             mManageButton.setText(mManageNotificationHistoryText);
             mManageButton.setContentDescription(mManageNotificationHistoryText);
@@ -152,6 +132,9 @@
             mManageButton.setText(mManageNotificationText);
             mManageButton.setContentDescription(mManageNotificationText);
         }
+        mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
+        mSeenNotifsFooterTextView
+                .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null);
     }
 
     public boolean isHistoryShown() {
@@ -166,7 +149,7 @@
         mClearAllButton.setContentDescription(
                 mContext.getString(R.string.accessibility_clear_all));
         updateResources();
-        updateText();
+        updateContent();
     }
 
     /**
@@ -190,8 +173,11 @@
         mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
         mManageNotificationHistoryText = getContext()
                 .getString(R.string.manage_notifications_history_text);
-        mUnlockIconSize = getResources()
+        int unlockIconSize = getResources()
                 .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
+        mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
+        mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
+        mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index f21db0b..9bc0333 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -25,7 +25,7 @@
 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
 
-import static com.android.systemui.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index 596bdc0..047db20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -33,9 +33,9 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 8a50f2f..99a7755 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -20,7 +20,7 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 
-import static com.android.systemui.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index bafc474..5a129fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -39,9 +39,9 @@
 import android.widget.FrameLayout;
 import android.widget.FrameLayout.LayoutParams;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index 5f4c926..d5d7f75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -45,11 +45,11 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 5aaf63f..b24cec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -23,8 +23,8 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.animation.Interpolators;
 
 import java.util.function.Consumer;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 9a777ea..84fe9ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -34,10 +34,10 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.widget.CachingIconView;
 import com.android.internal.widget.NotificationExpandButton;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.ViewTransformationHelper;
 import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index 7f3381c..d73bbeb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -22,8 +22,8 @@
 import android.animation.ValueAnimator;
 import android.view.View;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 40f55bd..160a230 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -1546,9 +1546,11 @@
         mUseRoundnessSourceTypes = enabled;
     }
 
-    @Override
-    public String toString() {
-        String roundableStateDebug = "RoundableState = " + getRoundableState().debugString();
-        return "NotificationChildrenContainer:" + hashCode() + " { " + roundableStateDebug + " }";
+    public String debugString() {
+        return TAG + " { "
+                + "visibility: " + getVisibility()
+                + ", alpha: " + getAlpha()
+                + ", translationY: " + getTranslationY()
+                + ", roundableState: " + getRoundableState().debugString() + "}";
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index 0b435fe..9a33a94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -26,7 +26,7 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
-import com.android.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
 import com.android.systemui.statusbar.notification.ShadeViewRefactor;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 587783d..82608b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -75,6 +75,7 @@
 import android.widget.OverScroller;
 import android.widget.ScrollView;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -86,7 +87,6 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
@@ -4189,10 +4189,7 @@
             mCentralSurfaces.resetUserExpandedStates();
             clearTemporaryViews();
             clearUserLockedViews();
-            if (mSwipeHelper.isSwiping()) {
-                mSwipeHelper.resetSwipeState();
-                updateContinuousShadowDrawing();
-            }
+            cancelActiveSwipe();
         }
     }
 
@@ -4264,6 +4261,9 @@
             if (!mIsExpanded) {
                 mGroupExpansionManager.collapseGroups();
                 mExpandHelper.cancelImmediately();
+                if (!mIsExpansionChanging) {
+                    cancelActiveSwipe();
+                }
             }
             updateNotificationAnimationStates();
             updateChronometers();
@@ -4746,13 +4746,7 @@
         mFooterView.setVisible(visible, animate);
         mFooterView.setSecondaryVisible(showDismissView, animate);
         mFooterView.showHistory(showHistory);
-        if (mHasFilteredOutSeenNotifications) {
-            mFooterView.setFooterLabelTextAndIcon(
-                    R.string.unlock_to_see_notif_text,
-                    R.drawable.ic_friction_lock_closed);
-        } else {
-            mFooterView.setFooterLabelTextAndIcon(0, 0);
-        }
+        mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -6119,7 +6113,11 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
+    private void cancelActiveSwipe() {
+        mSwipeHelper.resetSwipeState();
+        updateContinuousShadowDrawing();
+    }
+
     void updateContinuousShadowDrawing() {
         boolean continuousShadowUpdate = mAnimationRunning
                 || mSwipeHelper.isSwiping();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index ee72943..f07dd00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -22,9 +22,9 @@
 import android.util.Property;
 import android.view.View;
 
+import com.android.app.animation.Interpolators;
 import com.android.keyguard.KeyguardSliceView;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.shared.clocks.AnimatableClockView;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarIconView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
index d07da38..f4605be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
@@ -26,9 +26,9 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.NotificationFadeAware.FadeOptimizedNotification;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
index 9dce332..4590712 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -33,9 +33,9 @@
 import android.util.Log;
 import android.view.View;
 
+import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index e66a8b1..e1c8064 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -452,7 +452,7 @@
     protected PhoneStatusBarView mStatusBarView;
     private PhoneStatusBarViewController mPhoneStatusBarViewController;
     private PhoneStatusBarTransitions mStatusBarTransitions;
-    private AuthRippleController mAuthRippleController;
+    private final AuthRippleController mAuthRippleController;
     @WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
     protected final NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarInitializer mStatusBarInitializer;
@@ -460,7 +460,7 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @VisibleForTesting
     DozeServiceHost mDozeServiceHost;
-    private LightRevealScrim mLightRevealScrim;
+    private final LightRevealScrim mLightRevealScrim;
     private PowerButtonReveal mPowerButtonReveal;
 
     private boolean mWakeUpComingFromTouch;
@@ -768,6 +768,7 @@
             ScrimController scrimController,
             Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
+            AuthRippleController authRippleController,
             DozeServiceHost dozeServiceHost,
             PowerManager powerManager,
             ScreenPinningRequest screenPinningRequest,
@@ -810,6 +811,7 @@
             IDreamManager dreamManager,
             Lazy<CameraLauncher> cameraLauncherLazy,
             Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy,
+            LightRevealScrim lightRevealScrim,
             AlternateBouncerInteractor alternateBouncerInteractor,
             UserTracker userTracker,
             Provider<FingerprintManager> fingerprintManager
@@ -866,6 +868,7 @@
         mScreenPinningRequest = screenPinningRequest;
         mDozeScrimController = dozeScrimController;
         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
+        mAuthRippleController = authRippleController;
         mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy;
         mVolumeComponent = volumeComponent;
         mCommandQueue = commandQueue;
@@ -932,6 +935,7 @@
         wiredChargingRippleController.registerCallbacks();
 
         mLightRevealScrimViewModelLazy = lightRevealScrimViewModelLazy;
+        mLightRevealScrim = lightRevealScrim;
 
         // Based on teamfood flag, turn predictive back dispatch on at runtime.
         if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
@@ -1326,8 +1330,6 @@
         });
         mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront);
 
-        mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
-
         if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
             LightRevealScrimViewBinder.bind(
                     mLightRevealScrim, mLightRevealScrimViewModelLazy.get());
@@ -1635,10 +1637,7 @@
 
     private void inflateStatusBarWindow() {
         if (mCentralSurfacesComponent != null) {
-            // Tear down
-            for (CentralSurfacesComponent.Startable s : mCentralSurfacesComponent.getStartables()) {
-                s.stop();
-            }
+            Log.e(TAG, "CentralSurfacesComponent being recreated; this is unexpected.");
         }
         mCentralSurfacesComponent = mCentralSurfacesComponentFactory.create();
         mFragmentService.addFragmentInstantiationProvider(
@@ -1667,8 +1666,6 @@
         mPresenter = mCentralSurfacesComponent.getNotificationPresenter();
         mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter();
         mNotificationShelfController = mCentralSurfacesComponent.getNotificationShelfController();
-        mAuthRippleController = mCentralSurfacesComponent.getAuthRippleController();
-        mAuthRippleController.init();
 
         mHeadsUpManager.addListener(mCentralSurfacesComponent.getStatusBarHeadsUpChangeListener());
 
@@ -1682,11 +1679,6 @@
                 mCentralSurfacesComponent.getCentralSurfacesCommandQueueCallbacks();
         // Connect in to the status bar manager service
         mCommandQueue.addCallback(mCommandQueueCallbacks);
-
-        // Perform all other initialization for CentralSurfacesScope
-        for (CentralSurfacesComponent.Startable s : mCentralSurfacesComponent.getStartables()) {
-            s.start();
-        }
     }
 
     protected void startKeyguard() {
@@ -3648,9 +3640,6 @@
                         /* wakingUp= */ true,
                         mShouldDelayWakeUpAnimation);
 
-                if (!mKeyguardBypassController.getBypassEnabled()) {
-                    mHeadsUpManager.releaseAllImmediately();
-                }
                 updateVisibleToUser();
                 updateIsKeyguard();
                 mDozeServiceHost.stopDozing();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 90a6d0f..c1859b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -23,10 +23,10 @@
 import android.content.res.Resources;
 import android.util.MathUtils;
 
+import com.android.app.animation.Interpolators;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index 9d30cb4..61c1cc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -30,9 +30,9 @@
 
 import androidx.annotation.StyleRes;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.keyguard.KeyguardIndication;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 74ab47f..c17366a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -115,9 +115,7 @@
         val onKeyguard = keyguardUpdateMonitor.isKeyguardVisible &&
                 !statusBarStateController.isDozing
 
-        val userId = KeyguardUpdateMonitor.getCurrentUser()
-        val isFaceEnabled = keyguardUpdateMonitor.isFaceAuthEnabledForUser(userId)
-        val shouldListen = (onKeyguard || bouncerVisible) && isFaceEnabled
+        val shouldListen = (onKeyguard || bouncerVisible) && keyguardUpdateMonitor.isFaceEnrolled
         if (shouldListen != isListening) {
             isListening = shouldListen
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 13566ef..720eeba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -43,9 +43,9 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index e835c5ce..5232fb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -37,12 +37,12 @@
 import androidx.core.animation.AnimatorListenerAdapter;
 import androidx.core.animation.ValueAnimator;
 
+import com.android.app.animation.InterpolatorsAndroidX;
 import com.android.keyguard.CarrierTextController;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.R;
-import com.android.systemui.animation.InterpolatorsAndroidX;
 import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.log.LogLevel;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index 6bf5443..7bc4fc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -24,21 +24,21 @@
 import android.util.MathUtils;
 import android.util.TimeUtils;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.Dumpable;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
 import dagger.assisted.AssistedInject;
 
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+
 /**
  * Class to control all aspects about light bar changes.
  */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index cc4f901..46a2457 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -31,9 +31,9 @@
 import android.util.SparseArray;
 import android.view.ViewTreeObserver.OnPreDrawListener;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 55dc188..560ea8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -15,11 +15,11 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.collection.ArrayMap;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 006a029d..bef422c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -36,10 +36,10 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.collection.ArrayMap;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.stack.AnimationFilter;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
index 5e5317d7..07a6d0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
@@ -29,7 +29,7 @@
 import android.view.animation.AnimationUtils;
 import android.widget.Button;
 
-import com.android.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
 
 public class SettingsButton extends AlphaOptimizedImageView {
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 8fa803e..cdf6652 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -15,7 +15,7 @@
 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF
 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
 import com.android.systemui.DejankUtils
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.WakefulnessLifecycle
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
index d80e1d3..ddb6d93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
@@ -45,7 +45,6 @@
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
-import java.util.Set;
 
 import javax.inject.Named;
 import javax.inject.Scope;
@@ -60,7 +59,6 @@
  * outside the component. Should more items be moved *into* this component to avoid so many getters?
  */
 @Subcomponent(modules = {
-        CentralSurfacesStartableModule.class,
         NotificationStackScrollLayoutListContainerModule.class,
         StatusBarViewModule.class,
         StatusBarNotificationActivityStarterModule.class,
@@ -85,14 +83,6 @@
     @interface CentralSurfacesScope {}
 
     /**
-     * Performs initialization logic after {@link CentralSurfacesComponent} has been constructed.
-     */
-    interface Startable {
-        void start();
-        void stop();
-    }
-
-    /**
      * Creates a {@link NotificationShadeWindowView}.
      */
     NotificationShadeWindowView getNotificationShadeWindowView();
@@ -122,11 +112,6 @@
     LockIconViewController getLockIconViewController();
 
     /**
-     * Creates an AuthRippleViewController. Must be init after creation.
-     */
-    AuthRippleController getAuthRippleController();
-
-    /**
      * Creates a StatusBarHeadsUpChangeListener.
      */
     StatusBarHeadsUpChangeListener getStatusBarHeadsUpChangeListener();
@@ -148,11 +133,6 @@
     @Named(STATUS_BAR_FRAGMENT)
     CollapsedStatusBarFragment createCollapsedStatusBarFragment();
 
-    /**
-     * Set of startables to be run after a CentralSurfacesComponent has been constructed.
-     */
-    Set<Startable> getStartables();
-
     NotificationActivityStarter getNotificationActivityStarter();
 
     NotificationPresenter getNotificationPresenter();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
deleted file mode 100644
index 7ded90f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
+++ /dev/null
@@ -1,28 +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.systemui.statusbar.phone.dagger;
-
-import dagger.Module;
-import dagger.multibindings.Multibinds;
-
-import java.util.Set;
-
-@Module
-interface CentralSurfacesStartableModule {
-    @Multibinds
-    Set<CentralSurfacesComponent.Startable> multibindStartables();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index ef86162..1a943e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -29,7 +29,6 @@
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.battery.BatteryMeterViewController;
-import com.android.systemui.biometrics.AuthRippleView;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
@@ -150,15 +149,6 @@
 
     /** */
     @Provides
-    @CentralSurfacesComponent.CentralSurfacesScope
-    @Nullable
-    public static AuthRippleView getAuthRippleView(
-            NotificationShadeWindowView notificationShadeWindowView) {
-        return notificationShadeWindowView.findViewById(R.id.auth_ripple);
-    }
-
-    /** */
-    @Provides
     @Named(SHADE_HEADER)
     @CentralSurfacesComponent.CentralSurfacesScope
     public static MotionLayout getLargeScreenShadeHeaderBarView(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 453dd1b..831d402 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -44,10 +44,10 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.core.animation.Animator;
 
+import com.android.app.animation.Interpolators;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 673819b..3d811cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -240,7 +240,7 @@
                 || (Build.IS_DEBUGGABLE && DEBUG_AUTH_WITH_ADB && mDebugUnlocked);
         boolean trustManaged = mKeyguardUpdateMonitor.getUserTrustIsManaged(user);
         boolean trusted = mKeyguardUpdateMonitor.getUserHasTrust(user);
-        boolean faceAuthEnabled = mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(user);
+        boolean faceAuthEnabled = mKeyguardUpdateMonitor.isFaceEnrolled();
         boolean changed = secure != mSecure || canDismissLockScreen != mCanDismissLockScreen
                 || trustManaged != mTrustManaged || mTrusted != trusted
                 || mFaceAuthEnabled != faceAuthEnabled;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
index 4dd63be..e1ec94f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
@@ -24,9 +24,9 @@
 
 import androidx.core.graphics.ColorUtils;
 
+import com.android.app.animation.Interpolators;
 import com.android.keyguard.KeyguardConstants;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.qs.tiles.UserDetailItemView;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index 928e011..66b5256 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -31,6 +31,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.app.animation.Interpolators;
 import com.android.keyguard.KeyguardConstants;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -38,7 +39,6 @@
 import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
 import com.android.settingslib.drawable.CircleFramedDrawable;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
index 850a4b4..363b06a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
@@ -21,11 +21,11 @@
 import android.util.Log;
 import android.view.View;
 
+import com.android.app.animation.Interpolators;
 import com.android.keyguard.AlphaOptimizedLinearLayout;
 import com.android.keyguard.KeyguardConstants;
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
-import com.android.systemui.animation.Interpolators;
 
 /**
  * The container for the user switcher on Keyguard.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 403a7e8..e311bad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -73,6 +73,7 @@
 import androidx.core.animation.ObjectAnimator;
 import androidx.core.animation.ValueAnimator;
 
+import com.android.app.animation.InterpolatorsAndroidX;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.internal.logging.UiEvent;
@@ -80,7 +81,6 @@
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.animation.InterpolatorsAndroidX;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
index 1612388..46954b5 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
@@ -18,7 +18,7 @@
 
 import android.view.View
 import android.view.ViewGroup
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.animation.ViewHierarchyAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.children
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index e819f94..4fbbc89 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -34,10 +34,10 @@
 import androidx.annotation.DimenRes
 import androidx.annotation.IdRes
 import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
 import com.android.internal.widget.CachingIconView
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Text.Companion.loadText
diff --git a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
index 64234c2..41c6b5d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
@@ -16,20 +16,22 @@
 
 package com.android.systemui.util
 
-import android.os.Trace
+import android.os.Handler
 import android.os.TraceNameSupplier
+import androidx.tracing.Trace
 
 /**
- * Run a block within a [Trace] section.
- * Calls [Trace.beginSection] before and [Trace.endSection] after the passed block.
+ * Run a block within a [Trace] section. Calls [Trace.beginSection] before and [Trace.endSection]
+ * after the passed block. If tracing is disabled, it will run the block directly to avoid using an
+ * unnecessary try-finally block.
  */
 inline fun <T> traceSection(tag: String, block: () -> T): T =
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
-            Trace.traceBegin(Trace.TRACE_TAG_APP, tag)
+        if (Trace.isEnabled()) {
+            Trace.beginSection(tag)
             try {
                 block()
             } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_APP)
+                Trace.endSection()
             }
         } else {
             block()
@@ -42,8 +44,10 @@
         }
 
         /**
-         * Helper function for creating a Runnable object that implements TraceNameSupplier.
-         * This is useful for posting Runnables to Handlers with meaningful names.
+         * Helper function for creating a [Runnable] that implements [TraceNameSupplier]. This is
+         * useful when posting to a [Handler] so that the [Runnable] has a meaningful name in the
+         * trace. Otherwise, the class name of the [Runnable] is used, which is often something like
+         * `pkg.MyClass$$ExternalSyntheticLambda0`.
          */
         inline fun namedRunnable(tag: String, crossinline block: () -> Unit): Runnable {
             return object : Runnable, TraceNameSupplier {
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
index 5d80292..db4ab7e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
@@ -19,7 +19,7 @@
 import android.animation.ValueAnimator
 import android.graphics.PointF
 import android.util.MathUtils
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 
 /**
  * The fraction after which we start fading in when going from a gone widget to a visible one
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 77210b7..91078dc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -109,6 +109,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -119,7 +120,6 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index a4b093d..a5365fb 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -66,7 +66,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -100,7 +100,7 @@
     private final INotificationManager mNotificationManager;
     private final IDreamManager mDreamManager;
     private final NotificationVisibilityProvider mVisibilityProvider;
-    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+    private final VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider;
     private final NotificationLockscreenUserManager mNotifUserManager;
     private final CommonNotifCollection mCommonNotifCollection;
     private final NotifPipeline mNotifPipeline;
@@ -126,7 +126,7 @@
             INotificationManager notificationManager,
             IDreamManager dreamManager,
             NotificationVisibilityProvider visibilityProvider,
-            NotificationInterruptStateProvider interruptionStateProvider,
+            VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
             CommonNotifCollection notifCollection,
@@ -145,7 +145,7 @@
                     notificationManager,
                     dreamManager,
                     visibilityProvider,
-                    interruptionStateProvider,
+                    visualInterruptionDecisionProvider,
                     zenModeController,
                     notifUserManager,
                     notifCollection,
@@ -169,7 +169,7 @@
             INotificationManager notificationManager,
             IDreamManager dreamManager,
             NotificationVisibilityProvider visibilityProvider,
-            NotificationInterruptStateProvider interruptionStateProvider,
+            VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
             CommonNotifCollection notifCollection,
@@ -185,7 +185,7 @@
         mNotificationManager = notificationManager;
         mDreamManager = dreamManager;
         mVisibilityProvider = visibilityProvider;
-        mNotificationInterruptStateProvider = interruptionStateProvider;
+        mVisualInterruptionDecisionProvider = visualInterruptionDecisionProvider;
         mNotifUserManager = notifUserManager;
         mCommonNotifCollection = notifCollection;
         mNotifPipeline = notifPipeline;
@@ -272,7 +272,7 @@
                     for (NotificationEntry entry : activeEntries) {
                         if (mNotifUserManager.isCurrentProfile(entry.getSbn().getUserId())
                                 && savedBubbleKeys.contains(entry.getKey())
-                                && mNotificationInterruptStateProvider.shouldBubbleUp(entry)
+                                && shouldBubbleUp(entry)
                                 && entry.isBubble()) {
                             result.add(notifToBubbleEntry(entry));
                         }
@@ -416,16 +416,13 @@
     }
 
     void onEntryAdded(NotificationEntry entry) {
-        if (mNotificationInterruptStateProvider.shouldBubbleUp(entry)
-                && entry.isBubble()) {
+        if (shouldBubbleUp(entry) && entry.isBubble()) {
             mBubbles.onEntryAdded(notifToBubbleEntry(entry));
         }
     }
 
     void onEntryUpdated(NotificationEntry entry, boolean fromSystem) {
-        boolean shouldBubble = mNotificationInterruptStateProvider.shouldBubbleUp(entry);
-        mBubbles.onEntryUpdated(notifToBubbleEntry(entry),
-                shouldBubble, fromSystem);
+        mBubbles.onEntryUpdated(notifToBubbleEntry(entry), shouldBubbleUp(entry), fromSystem);
     }
 
     void onEntryRemoved(NotificationEntry entry) {
@@ -438,12 +435,8 @@
         for (int i = 0; i < orderedKeys.length; i++) {
             String key = orderedKeys[i];
             final NotificationEntry entry = mCommonNotifCollection.getEntry(key);
-            BubbleEntry bubbleEntry = entry != null
-                    ? notifToBubbleEntry(entry)
-                    : null;
-            boolean shouldBubbleUp = entry != null
-                    ? mNotificationInterruptStateProvider.shouldBubbleUp(entry)
-                    : false;
+            BubbleEntry bubbleEntry = entry != null ? notifToBubbleEntry(entry) : null;
+            boolean shouldBubbleUp = entry != null ? shouldBubbleUp(entry) : false;
             pendingOrActiveNotif.put(key, new Pair<>(bubbleEntry, shouldBubbleUp));
         }
         mBubbles.onRankingUpdated(rankingMap, pendingOrActiveNotif);
@@ -637,6 +630,10 @@
         }
     }
 
+    private boolean shouldBubbleUp(NotificationEntry e) {
+        return mVisualInterruptionDecisionProvider.makeAndLogBubbleDecision(e).getShouldInterrupt();
+    }
+
     /**
      * Callback for when the BubbleController wants to interact with the notification pipeline to:
      * - Remove a previously bubbled notification
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index e492534..b3e7cb0 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -345,7 +345,8 @@
     }
 
     void initDesktopMode(DesktopMode desktopMode) {
-        desktopMode.addListener(new DesktopModeTaskRepository.VisibleTasksListener() {
+        desktopMode.addVisibleTasksListener(
+                new DesktopModeTaskRepository.VisibleTasksListener() {
             @Override
             public void onVisibilityChanged(boolean hasFreeformTasks) {
                 mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE, hasFreeformTasks)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 2962c14..71246c9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -383,6 +383,7 @@
     }
 
     private void setupFingerprintAuth(boolean isClass3) throws RemoteException {
+        when(mAuthController.isFingerprintEnrolled(anyInt())).thenReturn(true);
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
         mFingerprintSensorProperties = List.of(
@@ -2692,33 +2693,42 @@
     }
     @Test
     public void testFingerprintSensorProperties() throws RemoteException {
+        // GIVEN no fingerprint sensor properties
+        when(mAuthController.isFingerprintEnrolled(anyInt())).thenReturn(true);
         mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
                 new ArrayList<>());
 
+        // THEN fingerprint is not possible
         assertThat(mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(
                 KeyguardUpdateMonitor.getCurrentUser())).isFalse();
 
+        // WHEN there are fingerprint sensor properties
         mFingerprintAuthenticatorsRegisteredCallback
                 .onAllAuthenticatorsRegistered(mFingerprintSensorProperties);
 
-        verifyFingerprintAuthenticateCall();
+        // THEN unlock with fp is possible & fingerprint starts listening
         assertThat(mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(
                 KeyguardUpdateMonitor.getCurrentUser())).isTrue();
+        verifyFingerprintAuthenticateCall();
     }
     @Test
     public void testFaceSensorProperties() throws RemoteException {
+        // GIVEN no face sensor properties
+        when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true);
         mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(new ArrayList<>());
 
-        assertThat(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(
+        // THEN face is not possible
+        assertThat(mKeyguardUpdateMonitor.isUnlockWithFacePossible(
                 KeyguardUpdateMonitor.getCurrentUser())).isFalse();
 
+        // WHEN there are face sensor properties
         mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
-        biometricsEnabledForCurrentUser();
 
+        // THEN face is possible but face does NOT start listening immediately
+        assertThat(mKeyguardUpdateMonitor.isUnlockWithFacePossible(
+                KeyguardUpdateMonitor.getCurrentUser())).isTrue();
         verifyFaceAuthenticateNeverCalled();
         verifyFaceDetectNeverCalled();
-        assertThat(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(
-                KeyguardUpdateMonitor.getCurrentUser())).isTrue();
     }
 
     @Test
@@ -2791,9 +2801,6 @@
     }
 
     private void mockCanBypassLockscreen(boolean canBypass) {
-        // force update the isFaceEnrolled cache:
-        mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(getCurrentUser());
-
         mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
         when(mKeyguardBypassController.canBypass()).thenReturn(canBypass);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
deleted file mode 100644
index 2c680be..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
+++ /dev/null
@@ -1,55 +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.systemui.animation
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import java.lang.reflect.Modifier
-import junit.framework.Assert.assertEquals
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@SmallTest
-@RunWith(JUnit4::class)
-class InterpolatorsAndroidXTest : SysuiTestCase() {
-
-    @Test
-    fun testInterpolatorsAndInterpolatorsAndroidXPublicMethodsAreEqual() {
-        assertEquals(
-            Interpolators::class.java.getPublicMethods(),
-            InterpolatorsAndroidX::class.java.getPublicMethods()
-        )
-    }
-
-    @Test
-    fun testInterpolatorsAndInterpolatorsAndroidXPublicFieldsAreEqual() {
-        assertEquals(
-            Interpolators::class.java.getPublicFields(),
-            InterpolatorsAndroidX::class.java.getPublicFields()
-        )
-    }
-
-    private fun <T> Class<T>.getPublicMethods() =
-        declaredMethods
-            .filter { Modifier.isPublic(it.modifiers) }
-            .map { it.toString().replace(name, "") }
-            .toSet()
-
-    private fun <T> Class<T>.getPublicFields() =
-        fields.filter { Modifier.isPublic(it.modifiers) }.map { it.name }.toSet()
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
index 02d4ecd..063757a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
@@ -31,6 +31,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import java.io.File
@@ -64,6 +65,7 @@
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class TextInterpolatorTest : SysuiTestCase() {
+    lateinit var typefaceCache: TypefaceVariantCache
 
     private fun makeLayout(
         text: String,
@@ -75,11 +77,16 @@
                 .setTextDirection(dir).build()
     }
 
+    @Before
+    fun setup() {
+        typefaceCache = TypefaceVariantCacheImpl()
+    }
+
     @Test
     fun testStartState() {
         val layout = makeLayout(TEXT, PAINT)
 
-        val interp = TextInterpolator(layout)
+        val interp = TextInterpolator(layout, typefaceCache)
         interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
@@ -98,7 +105,7 @@
     fun testEndState() {
         val layout = makeLayout(TEXT, PAINT)
 
-        val interp = TextInterpolator(layout)
+        val interp = TextInterpolator(layout, typefaceCache)
         interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
@@ -116,7 +123,7 @@
     fun testMiddleState() {
         val layout = makeLayout(TEXT, PAINT)
 
-        val interp = TextInterpolator(layout)
+        val interp = TextInterpolator(layout, typefaceCache)
         interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
@@ -138,7 +145,7 @@
     fun testRebase() {
         val layout = makeLayout(TEXT, PAINT)
 
-        val interp = TextInterpolator(layout)
+        val interp = TextInterpolator(layout, typefaceCache)
         interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
@@ -160,7 +167,7 @@
     fun testBidi_LTR() {
         val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.LTR)
 
-        val interp = TextInterpolator(layout)
+        val interp = TextInterpolator(layout, typefaceCache)
         interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
@@ -180,7 +187,7 @@
     fun testBidi_RTL() {
         val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
 
-        val interp = TextInterpolator(layout)
+        val interp = TextInterpolator(layout, typefaceCache)
         interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
@@ -200,7 +207,7 @@
     fun testGlyphCallback_Empty() {
         val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
 
-        val interp = TextInterpolator(layout).apply {
+        val interp = TextInterpolator(layout, typefaceCache).apply {
             glyphFilter = { glyph, progress ->
             }
         }
@@ -222,7 +229,7 @@
     fun testGlyphCallback_Xcoordinate() {
         val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
 
-        val interp = TextInterpolator(layout).apply {
+        val interp = TextInterpolator(layout, typefaceCache).apply {
             glyphFilter = { glyph, progress ->
                 glyph.x += 30f
             }
@@ -247,7 +254,7 @@
     fun testGlyphCallback_Ycoordinate() {
         val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
 
-        val interp = TextInterpolator(layout).apply {
+        val interp = TextInterpolator(layout, typefaceCache).apply {
             glyphFilter = { glyph, progress ->
                 glyph.y += 30f
             }
@@ -272,7 +279,7 @@
     fun testGlyphCallback_TextSize() {
         val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
 
-        val interp = TextInterpolator(layout).apply {
+        val interp = TextInterpolator(layout, typefaceCache).apply {
             glyphFilter = { glyph, progress ->
                 glyph.textSize += 10f
             }
@@ -297,7 +304,7 @@
     fun testGlyphCallback_Color() {
         val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
 
-        val interp = TextInterpolator(layout).apply {
+        val interp = TextInterpolator(layout, typefaceCache).apply {
             glyphFilter = { glyph, progress ->
                 glyph.color = Color.RED
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index 6ab54a3..da9ceb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -19,6 +19,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import com.android.app.animation.Interpolators
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
index f4dacab..213dc87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
@@ -26,6 +26,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
@@ -43,6 +44,7 @@
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
+@RoboPilotTest
 class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
 
     @JvmField
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
index 9f789e4..22ebc7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
@@ -23,6 +23,7 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
@@ -40,6 +41,7 @@
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
+@RoboPilotTest
 class AuthBiometricFingerprintViewTest : SysuiTestCase() {
 
     @JvmField
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 0f20ace..4f24b3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -89,6 +89,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settingslib.udfps.UdfpsUtils;
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
@@ -120,6 +121,7 @@
 @RunWith(AndroidJUnit4.class)
 @RunWithLooper
 @SmallTest
+@RoboPilotTest
 public class AuthControllerTest extends SysuiTestCase {
 
     private static final long REQUEST_ID = 22;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 7d9ccb6..6b5679a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -21,6 +21,7 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import android.util.DisplayMetrics
 import androidx.test.filters.SmallTest
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -35,7 +36,6 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.statusbar.phone.BiometricUnlockController
-import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.leak.RotationUtils
@@ -66,7 +66,6 @@
     private lateinit var staticMockSession: MockitoSession
 
     private lateinit var controller: AuthRippleController
-    @Mock private lateinit var mCentralSurfaces: CentralSurfaces
     @Mock private lateinit var rippleView: AuthRippleView
     @Mock private lateinit var commandRegistry: CommandRegistry
     @Mock private lateinit var configurationController: ConfigurationController
@@ -92,6 +91,8 @@
     @Mock
     private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
 
+    private val displayMetrics = DisplayMetrics()
+
     @Captor
     private lateinit var biometricUnlockListener:
             ArgumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener>
@@ -109,7 +110,6 @@
         `when`(udfpsControllerProvider.get()).thenReturn(udfpsController)
 
         controller = AuthRippleController(
-            mCentralSurfaces,
             context,
             authController,
             configurationController,
@@ -120,13 +120,14 @@
             notificationShadeWindowController,
             udfpsControllerProvider,
             statusBarStateController,
+            displayMetrics,
             featureFlags,
             KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
             biometricUnlockController,
+            lightRevealScrim,
             rippleView,
         )
         controller.init()
-        `when`(mCentralSurfaces.lightRevealScrim).thenReturn(lightRevealScrim)
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java
index 24a13a5..c6315cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java
@@ -41,6 +41,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 
 import kotlin.Unit;
@@ -55,6 +56,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4.class)
 @RunWithLooper(setAsMainLooper = true)
 public class BiometricDisplayListenerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
index 88b6c39..ad9fc95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
@@ -19,6 +19,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.logging.BiometricMessageDeferralLogger
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import org.junit.Assert.assertEquals
@@ -33,6 +34,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class FaceHelpMessageDeferralTest : SysuiTestCase() {
     val threshold = .75f
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index c554af6..e6334cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -52,6 +52,7 @@
 import androidx.test.filters.SmallTest
 import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.SysuiTestableContext
 import com.android.systemui.dump.DumpManager
@@ -90,6 +91,7 @@
 private const val SENSOR_ID = 1
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class SideFpsControllerTest : SysuiTestCase() {
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 1faad80..2747e83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -40,6 +40,7 @@
 import com.android.settingslib.udfps.UdfpsOverlayParams
 import com.android.settingslib.udfps.UdfpsUtils
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dump.DumpManager
@@ -80,6 +81,7 @@
 private const val SENSOR_HEIGHT = 60
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 class UdfpsControllerOverlayTest : SysuiTestCase() {
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 8d8b190..da71188 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -76,6 +76,7 @@
 import com.android.settingslib.udfps.UdfpsOverlayParams;
 import com.android.settingslib.udfps.UdfpsUtils;
 import com.android.systemui.R;
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.biometrics.udfps.InteractionEvent;
@@ -125,6 +126,7 @@
 import javax.inject.Provider;
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4.class)
 @RunWithLooper(setAsMainLooper = true)
 public class UdfpsControllerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
index cd9189b..280bfdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
@@ -27,6 +27,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Test;
@@ -37,6 +38,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@RoboPilotTest
 public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase {
     @Test
     public void testUdfpsBottomSpacerHeightForPortrait() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
index 5239966..1afb223 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
@@ -30,6 +30,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.concurrency.FakeExecution;
 
@@ -40,6 +41,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4.class)
 @RunWithLooper(setAsMainLooper = true)
 public class UdfpsDisplayModeTest extends SysuiTestCase {
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 af3a06b..b5515d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -33,6 +33,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.RoboPilotTest;
 import com.android.systemui.shade.ShadeExpansionListener;
 import com.android.systemui.statusbar.StatusBarState;
 
@@ -40,6 +41,7 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4.class)
 
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index fea9d2d5..8bf32cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -55,6 +56,7 @@
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
+@RoboPilotTest
 @TestableLooper.RunWithLooper
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControllerBaseTest() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt
index 8b374ae..6d55254 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt
@@ -21,6 +21,7 @@
 import android.view.MotionEvent
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.UdfpsController.UdfpsOverlayController
 import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -39,6 +40,7 @@
 import org.mockito.junit.MockitoJUnit
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class UdfpsShellTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index f075967..d11c965 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
@@ -27,6 +27,7 @@
 import androidx.test.filters.SmallTest
 import com.android.settingslib.udfps.UdfpsOverlayParams
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -49,6 +50,7 @@
 private const val SENSOR_RADIUS = 10
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class UdfpsViewTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
index 4b41537..fb3c185 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
@@ -61,7 +61,7 @@
         @JvmStatic
         fun data(): List<TestCase> =
             listOf(
-                    genTestCases(
+                    genPositiveTestCases(
                         innerXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()),
                         innerYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY()),
                         outerXs = listOf(SENSOR.left - 1, SENSOR.right + 1),
@@ -70,9 +70,7 @@
                         major = 300f,
                         expected = true
                     ),
-                    genTestCases(
-                        innerXs = listOf(SENSOR.left, SENSOR.right),
-                        innerYs = listOf(SENSOR.top, SENSOR.bottom),
+                    genNegativeTestCase(
                         outerXs = listOf(SENSOR.left - 1, SENSOR.right + 1),
                         outerYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1),
                         minor = 100f,
@@ -107,7 +105,7 @@
 
 private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 400 /* bottom */)
 
-private fun genTestCases(
+private fun genPositiveTestCases(
     innerXs: List<Int>,
     innerYs: List<Int>,
     outerXs: List<Int>,
@@ -122,3 +120,15 @@
         }
     }
 }
+
+private fun genNegativeTestCase(
+    outerXs: List<Int>,
+    outerYs: List<Int>,
+    minor: Float,
+    major: Float,
+    expected: Boolean
+): List<EllipseOverlapDetectorTest.TestCase> {
+    return outerXs.flatMap { x ->
+        outerYs.map { y -> EllipseOverlapDetectorTest.TestCase(x, y, minor, major, expected) }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
index 58eb7d4..e1c54976 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
@@ -18,7 +18,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
@@ -26,13 +25,13 @@
 import static org.mockito.Mockito.when;
 
 import android.app.DreamManager;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.shared.condition.Condition;
 
@@ -55,6 +54,9 @@
     @Mock
     DreamManager mDreamManager;
 
+    @Mock
+    KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -66,7 +68,7 @@
     @Test
     public void testInitialDreamingState() {
         when(mDreamManager.isDreaming()).thenReturn(true);
-        final DreamCondition condition = new DreamCondition(mContext, mDreamManager);
+        final DreamCondition condition = new DreamCondition(mDreamManager, mKeyguardUpdateMonitor);
         condition.addCallback(mCallback);
 
         verify(mCallback).onConditionChanged(eq(condition));
@@ -79,7 +81,7 @@
     @Test
     public void testInitialNonDreamingState() {
         when(mDreamManager.isDreaming()).thenReturn(false);
-        final DreamCondition condition = new DreamCondition(mContext, mDreamManager);
+        final DreamCondition condition = new DreamCondition(mDreamManager, mKeyguardUpdateMonitor);
         condition.addCallback(mCallback);
 
         verify(mCallback, never()).onConditionChanged(eq(condition));
@@ -91,15 +93,21 @@
      */
     @Test
     public void testChange() {
-        final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        final ArgumentCaptor<KeyguardUpdateMonitorCallback> callbackCaptor =
+                ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
         when(mDreamManager.isDreaming()).thenReturn(true);
-        final DreamCondition condition = new DreamCondition(mContext, mDreamManager);
+        final DreamCondition condition = new DreamCondition(mDreamManager, mKeyguardUpdateMonitor);
         condition.addCallback(mCallback);
-        verify(mContext).registerReceiver(receiverCaptor.capture(), any());
+        verify(mKeyguardUpdateMonitor).registerCallback(callbackCaptor.capture());
+
         clearInvocations(mCallback);
-        receiverCaptor.getValue().onReceive(mContext, new Intent(Intent.ACTION_DREAMING_STOPPED));
+        callbackCaptor.getValue().onDreamingStateChanged(false);
         verify(mCallback).onConditionChanged(eq(condition));
         assertThat(condition.isConditionMet()).isFalse();
+
+        clearInvocations(mCallback);
+        callbackCaptor.getValue().onDreamingStateChanged(true);
+        verify(mCallback).onConditionChanged(eq(condition));
+        assertThat(condition.isConditionMet()).isTrue();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index e20d3af..cfee3b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -23,6 +23,7 @@
 import android.content.pm.PackageManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.camera.CameraGestureHelper
 import com.android.systemui.settings.UserTracker
@@ -42,6 +43,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class CameraQuickAffordanceConfigTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index c326a86..d84a4f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -26,6 +26,7 @@
 import androidx.test.filters.SmallTest
 import com.android.settingslib.notification.EnableZenModeDialog
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
@@ -60,6 +61,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
index 0fb181d..13d1e64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.data.quickaffordance
 
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.animation.Expandable
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
 import kotlinx.coroutines.flow.Flow
@@ -24,6 +25,7 @@
 import kotlinx.coroutines.yield
 
 /** Fake implementation of a quick affordance data source. */
+@RoboPilotTest
 class FakeKeyguardQuickAffordanceConfig(
     override val key: String,
     override val pickerName: String = key,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
index 292d067..b6dffff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.statusbar.policy.FlashlightController
@@ -41,6 +42,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index f8cb408..b46d996 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -19,6 +19,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
@@ -45,6 +46,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@RoboPilotTest
 @RunWith(Parameterized::class)
 class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
index 26f0cdb..2fd4947 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Expandable
 import com.android.systemui.controls.controller.ControlsController
@@ -40,6 +41,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 9a18ba8..9200d72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -23,6 +23,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
@@ -48,6 +49,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
index 6989f44..bad4b36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
@@ -23,6 +23,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.UserFileManager
@@ -51,6 +52,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
index a1c9f87..0797d07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
@@ -21,6 +21,7 @@
 import android.os.UserHandle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.shared.customization.data.content.FakeCustomizationProviderClient
@@ -43,6 +44,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardQuickAffordanceRemoteUserSelectionManagerTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
index c38827a..d8c0341 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
@@ -19,7 +19,9 @@
 
 import android.content.Context
 import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
@@ -37,7 +39,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.Mock
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
@@ -45,7 +46,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class MuteQuickAffordanceConfigTest : SysuiTestCase() {
 
     private lateinit var underTest: MuteQuickAffordanceConfig
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index faf18d3..26c0ea4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -20,6 +20,7 @@
 import android.content.Intent
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
@@ -39,6 +40,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 952882d..111b8e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -23,6 +23,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.Expandable
@@ -48,6 +49,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
index a9b9c90..1414bac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.camera.CameraIntentsWrapper
 import com.android.systemui.coroutines.collectLastValue
@@ -44,6 +45,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class VideoCameraQuickAffordanceConfigTest : SysuiTestCase() {
 
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 726728a..1bab817 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
@@ -24,8 +24,8 @@
 import android.content.pm.UserInfo
 import android.hardware.biometrics.BiometricManager
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
@@ -33,6 +33,7 @@
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.coroutines.collectLastValue
@@ -69,8 +70,9 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class BiometricSettingsRepositoryTest : SysuiTestCase() {
     private lateinit var underTest: BiometricSettingsRepository
 
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
index fa40fc4..b50cf73 100644
--- 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
@@ -29,6 +29,7 @@
 import android.hardware.face.FaceSensorProperties
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.os.CancellationSignal
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.InstanceId.fakeInstanceId
 import com.android.internal.logging.UiEventLogger
@@ -36,6 +37,7 @@
 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.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.FlowValue
 import com.android.systemui.coroutines.collectLastValue
@@ -81,7 +83,6 @@
 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
@@ -98,7 +99,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
     private lateinit var underTest: DeviceEntryFaceAuthRepositoryImpl
 
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 e57b044..264328b 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
@@ -18,9 +18,11 @@
 
 import android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT
 import android.hardware.biometrics.BiometricSourceType
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.coroutines.collectLastValue
@@ -34,7 +36,6 @@
 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.Captor
 import org.mockito.Mock
@@ -44,7 +45,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var dumpManager: DumpManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
index bd6b7a8..7eb8a26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.shared.model.DevicePosture
@@ -29,7 +31,6 @@
 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.Captor
 import org.mockito.Mock
@@ -38,7 +39,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class DevicePostureRepositoryTest : SysuiTestCase() {
     private lateinit var underTest: DevicePostureRepository
     private lateinit var testScope: TestScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 12b8261..8dc04bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
@@ -54,6 +55,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index b53a434..4b4c7e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.common.shared.model.Position
@@ -63,6 +64,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardRepositoryImplTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index d0bfaa9..a17b596 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -23,8 +23,9 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
@@ -47,6 +48,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 @FlakyTest(bugId = 270760395)
 class KeyguardTransitionRepositoryTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
index 9daf3f3..f974577 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
@@ -17,7 +17,9 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.graphics.Point
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -31,11 +33,11 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(JUnit4::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class LightRevealScrimRepositoryTest : SysuiTestCase() {
     private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
     private lateinit var underTest: LightRevealScrimRepositoryImpl
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
index a181137..bf3c73a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.logging.TrustRepositoryLogger
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.plugins.log.LogBuffer
@@ -43,6 +44,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class TrustRepositoryTest : SysuiTestCase() {
     @Mock private lateinit var trustManager: TrustManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
index e7e5969..2180a8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -19,6 +19,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.ViewMediatorCallback
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
@@ -43,6 +44,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class AlternateBouncerInteractorTest : SysuiTestCase() {
     private lateinit var underTest: AlternateBouncerInteractor
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 68d694a..0d695aa 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
@@ -20,6 +20,7 @@
 import android.app.StatusBarManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlags
@@ -39,6 +40,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardInteractorTest : SysuiTestCase() {
     private lateinit var commandQueue: FakeCommandQueue
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
index 8a0cf4f..dfef947 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlags
@@ -48,6 +49,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardLongPressInteractorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 23f0523..6e21c00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -23,6 +23,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.common.shared.model.ContentDescription
@@ -71,6 +72,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 503687d..d66e420 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.systemui.RoboPilotTest
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -38,6 +39,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class KeyguardTransitionInteractorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 359854b..4440946 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository
@@ -37,6 +38,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class LightRevealScrimInteractorTest : SysuiTestCase() {
     private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
index f86ac79..2b135cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
@@ -19,6 +19,7 @@
 import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import org.junit.Before
 import org.junit.Test
@@ -28,6 +29,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class PrimaryBouncerCallbackInteractorTest : SysuiTestCase() {
     private val mPrimaryBouncerCallbackInteractor = PrimaryBouncerCallbackInteractor()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index edac468..e35e971 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
@@ -39,6 +40,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() {
     private lateinit var repository: FakeKeyguardBouncerRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
index a5b78b74..3efe382 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.keyguard.ui
 
 import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index 706154e..cdd06ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -36,6 +37,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
     private lateinit var underTest: DreamingToLockscreenTransitionViewModel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index b15ce10..40511a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -35,6 +36,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class GoneToDreamingTransitionViewModelTest : SysuiTestCase() {
     private lateinit var underTest: GoneToDreamingTransitionViewModel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index 9cd2220..0e9c99e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.keyguard.DismissCallbackRegistry
@@ -44,6 +45,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class KeyguardBouncerViewModelTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index d94c108..c98058d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -35,6 +36,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() {
     private lateinit var underTest: LockscreenToDreamingTransitionViewModel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index 12ec24d..031b7fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -35,6 +36,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() {
     private lateinit var underTest: LockscreenToOccludedTransitionViewModel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index efa5f0c..c7ff882 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -35,6 +36,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() {
     private lateinit var underTest: OccludedToLockscreenTransitionViewModel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 98794fd..db251a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -41,6 +42,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
     private lateinit var underTest: PrimaryBouncerToGoneTransitionViewModel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index a72634b..1a00ac2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -110,6 +110,7 @@
     lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener>
     @Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener>
     @Captor lateinit var keyguardCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
+    @Captor lateinit var hostStateCallback: ArgumentCaptor<MediaHostStatesManager.Callback>
 
     private val clock = FakeSystemClock()
     private lateinit var mediaCarouselController: MediaCarouselController
@@ -143,6 +144,7 @@
         verify(visualStabilityProvider)
             .addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
         verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCallback))
+        verify(mediaHostStatesManager).addCallback(capture(hostStateCallback))
         whenever(mediaControlPanelFactory.get()).thenReturn(panel)
         whenever(panel.mediaViewController).thenReturn(mediaViewController)
         whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
@@ -832,4 +834,16 @@
         // Verify that seekbar listening attribute in media control panel is set to false.
         verify(panel, times(MediaPlayerData.players().size)).listening = false
     }
+
+    @Test
+    fun testOnHostStateChanged_updateVisibility() {
+        var stateUpdated = false
+        mediaCarouselController.updateUserVisibility = { stateUpdated = true }
+
+        // When the host state updates
+        hostStateCallback.value!!.onHostStateChanged(LOCATION_QS, mediaHostState)
+
+        // Then the carousel visibility is updated
+        assertTrue(stateUpdated)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index eb78ded..2ce236d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -470,6 +470,21 @@
             )
     }
 
+    @Test
+    fun testQsExpandedChanged_noQqsMedia() {
+        // When we are looking at QQS with active media
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+        whenever(statusBarStateController.isExpanded).thenReturn(true)
+
+        // When there is no longer any active media
+        whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
+        mediaHierarchyManager.qsExpanded = false
+
+        // Then the carousel is set to not visible
+        verify(mediaCarouselScrollHandler).visibleToUser = false
+        assertThat(mediaCarouselScrollHandler.visibleToUser).isFalse()
+    }
+
     private fun enableSplitShade() {
         context
             .getOrCreateTestableResources()
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 7dc622b..55f221d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -620,6 +620,38 @@
         }
     }
 
+    // region onRoleHoldersChanged
+    @Test
+    fun onRoleHoldersChanged_notNotesRole_doNothing() {
+        val user = UserHandle.of(0)
+
+        createNoteTaskController(isEnabled = true).onRoleHoldersChanged("NOT_NOTES", user)
+
+        verifyZeroInteractions(context)
+    }
+
+    @Test
+    fun onRoleHoldersChanged_notesRole_sameUser_shouldUpdateShortcuts() {
+        val user = userTracker.userHandle
+        val controller = spy(createNoteTaskController())
+        doNothing().whenever(controller).updateNoteTaskAsUser(any())
+
+        controller.onRoleHoldersChanged(ROLE_NOTES, user)
+
+        verify(controller).updateNoteTaskAsUser(user)
+    }
+
+    @Test
+    fun onRoleHoldersChanged_notesRole_differentUser_shouldUpdateShortcutsInUserProcess() {
+        // FakeUserTracker will default to UserHandle.SYSTEM.
+        val user = UserHandle.CURRENT
+
+        createNoteTaskController(isEnabled = true).onRoleHoldersChanged(ROLE_NOTES, user)
+
+        verify(context).startServiceAsUser(any(), eq(user))
+    }
+    // endregion
+
     // region updateNoteTaskAsUser
     @Test
     fun updateNoteTaskAsUser_withNotesRole_withShortcuts_shouldUpdateShortcuts() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt
index db6fc13..38a666e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt
@@ -37,6 +37,7 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -54,10 +55,12 @@
 
     @Mock private lateinit var parent: ViewGroup
 
+    @Mock private lateinit var splitShadeStatusBar: ViewGroup
+
     @Mock private lateinit var statusBarStateController: StatusBarStateController
 
     private lateinit var underTest: NotificationPanelUnfoldAnimationController
-    private lateinit var progressListener: TransitionProgressListener
+    private lateinit var progressListeners: List<TransitionProgressListener>
     private var xTranslationMax = 0f
 
     @Before
@@ -73,10 +76,13 @@
                 statusBarStateController,
                 progressProvider
             )
+        whenever(parent.findViewById<ViewGroup>(R.id.split_shade_status_bar)).thenReturn(
+            splitShadeStatusBar
+        )
         underTest.setup(parent)
 
-        verify(progressProvider).addCallback(capture(progressListenerCaptor))
-        progressListener = progressListenerCaptor.value
+        verify(progressProvider, atLeastOnce()).addCallback(capture(progressListenerCaptor))
+        progressListeners = progressListenerCaptor.allValues
     }
 
     @Test
@@ -86,16 +92,16 @@
         val view = View(context)
         whenever(parent.findViewById<View>(R.id.quick_settings_panel)).thenReturn(view)
 
-        progressListener.onTransitionStarted()
+        onTransitionStarted()
         assertThat(view.translationX).isZero()
 
-        progressListener.onTransitionProgress(0f)
+        onTransitionProgress(0f)
         assertThat(view.translationX).isZero()
 
-        progressListener.onTransitionProgress(0.5f)
+        onTransitionProgress(0.5f)
         assertThat(view.translationX).isZero()
 
-        progressListener.onTransitionFinished()
+        onTransitionFinished()
         assertThat(view.translationX).isZero()
     }
 
@@ -106,16 +112,16 @@
         val view = View(context)
         whenever(parent.findViewById<View>(R.id.quick_settings_panel)).thenReturn(view)
 
-        progressListener.onTransitionStarted()
+        onTransitionStarted()
         assertThat(view.translationX).isZero()
 
-        progressListener.onTransitionProgress(0f)
+        onTransitionProgress(0f)
         assertThat(view.translationX).isEqualTo(xTranslationMax)
 
-        progressListener.onTransitionProgress(0.5f)
+        onTransitionProgress(0.5f)
         assertThat(view.translationX).isEqualTo(0.5f * xTranslationMax)
 
-        progressListener.onTransitionFinished()
+        onTransitionFinished()
         assertThat(view.translationX).isZero()
     }
 
@@ -126,16 +132,88 @@
         val view = View(context)
         whenever(parent.findViewById<View>(R.id.quick_settings_panel)).thenReturn(view)
 
-        progressListener.onTransitionStarted()
+        onTransitionStarted()
         assertThat(view.translationX).isZero()
 
-        progressListener.onTransitionProgress(0f)
+        onTransitionProgress(0f)
         assertThat(view.translationX).isEqualTo(xTranslationMax)
 
-        progressListener.onTransitionProgress(0.5f)
+        onTransitionProgress(0.5f)
         assertThat(view.translationX).isEqualTo(0.5f * xTranslationMax)
 
-        progressListener.onTransitionFinished()
+        onTransitionFinished()
         assertThat(view.translationX).isZero()
     }
+
+    @Test
+    fun whenInKeyguardState_statusBarViewDoesNotMove() {
+        whenever(statusBarStateController.getState()).thenReturn(KEYGUARD)
+
+        val view = View(context)
+        whenever(splitShadeStatusBar.findViewById<View>(R.id.date)).thenReturn(view)
+
+        onTransitionStarted()
+        assertThat(view.translationX).isZero()
+
+        onTransitionProgress(0f)
+        assertThat(view.translationX).isZero()
+
+        onTransitionProgress(0.5f)
+        assertThat(view.translationX).isZero()
+
+        onTransitionFinished()
+        assertThat(view.translationX).isZero()
+    }
+
+    @Test
+    fun whenInShadeState_statusBarViewDoesMove() {
+        whenever(statusBarStateController.getState()).thenReturn(SHADE)
+
+        val view = View(context)
+        whenever(splitShadeStatusBar.findViewById<View>(R.id.date)).thenReturn(view)
+
+        onTransitionStarted()
+        assertThat(view.translationX).isZero()
+
+        onTransitionProgress(0f)
+        assertThat(view.translationX).isEqualTo(xTranslationMax)
+
+        onTransitionProgress(0.5f)
+        assertThat(view.translationX).isEqualTo(0.5f * xTranslationMax)
+
+        onTransitionFinished()
+        assertThat(view.translationX).isZero()
+    }
+
+    @Test
+    fun whenInShadeLockedState_statusBarViewDoesMove() {
+        whenever(statusBarStateController.getState()).thenReturn(SHADE_LOCKED)
+
+        val view = View(context)
+        whenever(splitShadeStatusBar.findViewById<View>(R.id.date)).thenReturn(view)
+        onTransitionStarted()
+        assertThat(view.translationX).isZero()
+
+        onTransitionProgress(0f)
+        assertThat(view.translationX).isEqualTo(xTranslationMax)
+
+        onTransitionProgress(0.5f)
+        assertThat(view.translationX).isEqualTo(0.5f * xTranslationMax)
+
+        onTransitionFinished()
+        assertThat(view.translationX).isZero()
+    }
+
+    private fun onTransitionStarted() {
+        progressListeners.forEach { it.onTransitionStarted() }
+    }
+
+    private fun onTransitionProgress(progress: Float) {
+        progressListeners.forEach { it.onTransitionProgress(progress) }
+    }
+
+    private fun onTransitionFinished() {
+        progressListeners.forEach { it.onTransitionFinished() }
+    }
+
 }
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 068d933..f870631 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -305,6 +305,7 @@
     @Mock protected ActivityStarter mActivityStarter;
     @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
 
+    protected final int mMaxUdfpsBurnInOffsetY = 5;
     protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
     protected KeyguardInteractor mKeyguardInteractor;
     protected NotificationPanelViewController.TouchHandler mTouchHandler;
@@ -365,6 +366,8 @@
         when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
         mDisplayMetrics.density = 100;
         when(mResources.getBoolean(R.bool.config_enableNotificationShadeDrag)).thenReturn(true);
+        when(mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y))
+                .thenReturn(mMaxUdfpsBurnInOffsetY);
         when(mResources.getDimensionPixelSize(R.dimen.notifications_top_padding_split_shade))
                 .thenReturn(NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE);
         when(mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 600fb5c..48e0b53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -29,6 +29,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -44,6 +45,7 @@
 
 import android.animation.Animator;
 import android.animation.ValueAnimator;
+import android.graphics.Point;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
@@ -61,6 +63,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -251,6 +254,43 @@
     }
 
     @Test
+    public void testOnDozeAmountChanged_positionClockAndNotificationsUsesUdfpsLocation() {
+        // GIVEN UDFPS is enrolled and we're on the keyguard
+        final Point udfpsLocationCenter = new Point(0, 100);
+        final float udfpsRadius = 10f;
+        when(mUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
+        when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocationCenter);
+        when(mAuthController.getUdfpsRadius()).thenReturn(udfpsRadius);
+        mNotificationPanelViewController.getStatusBarStateListener().onStateChanged(KEYGUARD);
+
+        // WHEN the doze amount changes
+        mNotificationPanelViewController.mClockPositionAlgorithm = mock(
+                KeyguardClockPositionAlgorithm.class);
+        mNotificationPanelViewController.getStatusBarStateListener().onDozeAmountChanged(1f, 1f);
+
+        // THEN the clock positions accounts for the UDFPS location & its worst case burn in
+        final float udfpsTop = udfpsLocationCenter.y - udfpsRadius - mMaxUdfpsBurnInOffsetY;
+        verify(mNotificationPanelViewController.mClockPositionAlgorithm).setup(
+                anyInt(),
+                anyFloat(),
+                anyInt(),
+                anyInt(),
+                anyInt(),
+                /* darkAmount */ eq(1f),
+                anyFloat(),
+                anyBoolean(),
+                anyInt(),
+                anyFloat(),
+                anyInt(),
+                anyBoolean(),
+                /* udfpsTop */ eq(udfpsTop),
+                anyFloat(),
+                anyBoolean()
+        );
+    }
+
+
+    @Test
     public void testSetExpandedHeight() {
         mNotificationPanelViewController.setExpandedHeight(200);
         assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index 9fe75ab..20da8a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -33,9 +33,9 @@
 import androidx.constraintlayout.motion.widget.MotionLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index a1168f8..f0abf2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -32,9 +32,9 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.stack.AnimationFilter;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ViewState;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 67128ff..283efe2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -38,8 +38,10 @@
 import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.DecisionImpl
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.FullScreenIntentDecisionImpl
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
 import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback
 import com.android.systemui.statusbar.phone.NotificationGroupTestHelper
 import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -52,6 +54,7 @@
 import com.android.systemui.util.time.FakeSystemClock
 import java.util.ArrayList
 import java.util.function.Consumer
+import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -86,7 +89,7 @@
     private val logger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true)
     private val headsUpManager: HeadsUpManager = mock()
     private val headsUpViewBinder: HeadsUpViewBinder = mock()
-    private val notificationInterruptStateProvider: NotificationInterruptStateProvider = mock()
+    private val visualInterruptionDecisionProvider: VisualInterruptionDecisionProvider = mock()
     private val remoteInputManager: NotificationRemoteInputManager = mock()
     private val endLifetimeExtension: OnEndLifetimeExtensionCallback = mock()
     private val headerController: NodeController = mock()
@@ -114,7 +117,7 @@
             systemClock,
             headsUpManager,
             headsUpViewBinder,
-            notificationInterruptStateProvider,
+            visualInterruptionDecisionProvider,
             remoteInputManager,
             launchFullScreenIntentProvider,
             flags,
@@ -168,8 +171,11 @@
         groupChild2 = helper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250)
         groupChild3 = helper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150)
 
+        // Set the default HUN decision
+        setDefaultShouldHeadsUp(false)
+
         // Set the default FSI decision
-        setShouldFullScreen(any(), FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
+        setDefaultShouldFullScreen(FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
     }
 
     @Test
@@ -1006,31 +1012,59 @@
         verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(entry)
     }
 
-    private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
-        whenever(notificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should)
-        whenever(notificationInterruptStateProvider.checkHeadsUp(eq(entry), any()))
-                .thenReturn(should)
+    private fun setDefaultShouldHeadsUp(should: Boolean) {
+        whenever(visualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(any()))
+            .thenReturn(DecisionImpl.of(should))
+        whenever(visualInterruptionDecisionProvider.makeUnloggedHeadsUpDecision(any()))
+            .thenReturn(DecisionImpl.of(should))
     }
 
-    private fun setShouldFullScreen(entry: NotificationEntry, decision: FullScreenIntentDecision) {
-        whenever(notificationInterruptStateProvider.getFullScreenIntentDecision(entry))
-            .thenReturn(decision)
+    private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
+        whenever(visualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(entry))
+            .thenReturn(DecisionImpl.of(should))
+        whenever(visualInterruptionDecisionProvider.makeUnloggedHeadsUpDecision(entry))
+            .thenReturn(DecisionImpl.of(should))
+    }
+
+    private fun setDefaultShouldFullScreen(
+        originalDecision: FullScreenIntentDecision
+    ) {
+        val provider = visualInterruptionDecisionProvider
+        whenever(provider.makeUnloggedFullScreenIntentDecision(any())).thenAnswer {
+            val entry: NotificationEntry = it.getArgument(0)
+            FullScreenIntentDecisionImpl(entry, originalDecision)
+        }
+    }
+
+    private fun setShouldFullScreen(
+        entry: NotificationEntry,
+        originalDecision: FullScreenIntentDecision
+    ) {
+        whenever(
+            visualInterruptionDecisionProvider.makeUnloggedFullScreenIntentDecision(entry)
+        ).thenAnswer {
+            FullScreenIntentDecisionImpl(entry, originalDecision)
+        }
     }
 
     private fun verifyLoggedFullScreenIntentDecision(
         entry: NotificationEntry,
-        decision: FullScreenIntentDecision
+        originalDecision: FullScreenIntentDecision
     ) {
-        verify(notificationInterruptStateProvider).logFullScreenIntentDecision(entry, decision)
+        val decision = withArgCaptor {
+            verify(visualInterruptionDecisionProvider).logFullScreenIntentDecision(capture())
+        }
+        check(decision is FullScreenIntentDecisionImpl)
+        assertEquals(entry, decision.originalEntry)
+        assertEquals(originalDecision, decision.originalDecision)
     }
 
     private fun verifyNoFullScreenIntentDecisionLogged() {
-        verify(notificationInterruptStateProvider, never())
-            .logFullScreenIntentDecision(any(), any())
+        verify(visualInterruptionDecisionProvider, never()).logFullScreenIntentDecision(any())
     }
 
     private fun clearInterruptionProviderInvocations() {
-        clearInvocations(notificationInterruptStateProvider)
+        clearInvocations(visualInterruptionDecisionProvider)
     }
 
     private fun finishBind(entry: NotificationEntry) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
index 819a75b..90cb734 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
@@ -24,12 +24,12 @@
 
 import static org.mockito.Mockito.mock;
 
+import android.testing.AndroidTestingRunner;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
@@ -39,7 +39,7 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
 public class FooterViewTest extends SysuiTestCase {
 
     FooterView mView;
@@ -102,14 +102,21 @@
     }
 
     @Test
-    public void testSetFooterLabelTextAndIcon() {
-        mView.setFooterLabelTextAndIcon(
-                R.string.unlock_to_see_notif_text,
-                R.drawable.ic_friction_lock_closed);
+    public void testSetFooterLabelVisible() {
+        mView.setFooterLabelVisible(true);
         assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.GONE);
         assertThat(mView.findSecondaryView().getVisibility()).isEqualTo(View.GONE);
         assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility())
                 .isEqualTo(View.VISIBLE);
     }
+
+    @Test
+    public void testSetFooterLabelInvisible() {
+        mView.setFooterLabelVisible(false);
+        assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mView.findSecondaryView().getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility())
+                .isEqualTo(View.GONE);
+    }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index fbbb921..420c7ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -255,6 +255,34 @@
     }
 
     @Test
+    public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() {
+        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
+        initController(/* viewIsAttached= */ true);
+
+        when(mCentralSurfaces.isBouncerShowing()).thenReturn(true);
+        setupShowEmptyShadeViewState(true);
+        reset(mNotificationStackScrollLayout);
+        mController.updateShowEmptyShadeView();
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+                /* visible= */ false,
+                /* areNotificationsHiddenInShade= */ false);
+    }
+
+    @Test
+    public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() {
+        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
+        initController(/* viewIsAttached= */ true);
+
+        when(mCentralSurfaces.isBouncerShowing()).thenReturn(false);
+        setupShowEmptyShadeViewState(true);
+        reset(mNotificationStackScrollLayout);
+        mController.updateShowEmptyShadeView();
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+                /* visible= */ true,
+                /* areNotificationsHiddenInShade= */ false);
+    }
+
+    @Test
     public void testOnUserChange_verifySensitiveProfile() {
         when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
         initController(/* viewIsAttached= */ true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 7153e59..f771606 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -801,6 +801,34 @@
     }
 
     @Test
+    public void onShadeClosesWithAnimationWillResetSwipeState() {
+        // GIVEN shade is expanded
+        mStackScroller.setIsExpanded(true);
+        clearInvocations(mNotificationSwipeHelper);
+
+        // WHEN closing the shade with the animations
+        mStackScroller.onExpansionStarted();
+        mStackScroller.setIsExpanded(false);
+        mStackScroller.onExpansionStopped();
+
+        // VERIFY swipe is reset
+        verify(mNotificationSwipeHelper).resetSwipeState();
+    }
+
+    @Test
+    public void onShadeClosesWithoutAnimationWillResetSwipeState() {
+        // GIVEN shade is expanded
+        mStackScroller.setIsExpanded(true);
+        clearInvocations(mNotificationSwipeHelper);
+
+        // WHEN closing the shade without the animation
+        mStackScroller.setIsExpanded(false);
+
+        // VERIFY swipe is reset
+        verify(mNotificationSwipeHelper).resetSwipeState();
+    }
+
+    @Test
     public void testSplitShade_hasTopOverscroll() {
         mTestableResources
                 .addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 219e6a9..c83769d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -101,6 +101,7 @@
 import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.biometrics.AuthRippleController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.charging.WiredChargingRippleController;
 import com.android.systemui.classifier.FalsingCollectorFake;
@@ -138,6 +139,7 @@
 import com.android.systemui.shade.ShadeLogger;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.LightRevealScrim;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -231,10 +233,12 @@
     @Mock private IStatusBarService mBarService;
     @Mock private IDreamManager mDreamManager;
     @Mock private LightRevealScrimViewModel mLightRevealScrimViewModel;
+    @Mock private LightRevealScrim mLightRevealScrim;
     @Mock private ScrimController mScrimController;
     @Mock private DozeScrimController mDozeScrimController;
     @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
     @Mock private BiometricUnlockController mBiometricUnlockController;
+    @Mock private AuthRippleController mAuthRippleController;
     @Mock private NotificationListener mNotificationListener;
     @Mock private KeyguardViewMediator mKeyguardViewMediator;
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@@ -345,6 +349,7 @@
         mFeatureFlags.set(Flags.WM_SHADE_ALLOW_BACK_GESTURE, true);
         // For the Shade to animate during the Back gesture, we must enable the animation flag.
         mFeatureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true);
+        mFeatureFlags.set(Flags.LIGHT_REVEAL_MIGRATION, true);
 
         IThermalService thermalService = mock(IThermalService.class);
         mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
@@ -497,6 +502,7 @@
                 mScrimController,
                 mLockscreenWallpaperLazy,
                 mBiometricUnlockControllerLazy,
+                mAuthRippleController,
                 mDozeServiceHost,
                 mPowerManager, mScreenPinningRequest,
                 mDozeScrimController,
@@ -538,6 +544,7 @@
                 mDreamManager,
                 mCameraLauncherLazy,
                 () -> mLightRevealScrimViewModel,
+                mLightRevealScrim,
                 mAlternateBouncerInteractor,
                 mUserTracker,
                 () -> mFingerprintManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 780e0c5..6fda56c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -116,7 +116,6 @@
                 .thenReturn(TEST_AUTO_DISMISS_TIME);
         when(mVSProvider.isReorderingAllowed()).thenReturn(true);
         mDependency.injectMockDependency(NotificationShadeWindowController.class);
-        mDependency.injectMockDependency(ConfigurationController.class);
         super.setUp();
 
         mHeadsUpManager = new TestableHeadsUpManagerPhone(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
index 71ac7c4..683136d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
@@ -45,6 +45,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.Map;
 import java.util.function.Consumer;
@@ -53,16 +55,18 @@
 @SmallTest
 public class ExtensionControllerImplTest extends SysuiTestCase {
 
+    @Mock
+    private ConfigurationController mConfigurationController;
+
     private PluginManager mPluginManager;
     private TunerService mTunerService;
     private ExtensionController mExtensionController;
-    private ConfigurationController mConfigurationController;
 
     @Before
     public void setup() {
+        MockitoAnnotations.initMocks(this);
         mPluginManager = mDependency.injectMockDependency(PluginManager.class);
         mTunerService = mDependency.injectMockDependency(TunerService.class);
-        mConfigurationController = mDependency.injectMockDependency(ConfigurationController.class);
         mExtensionController = new ExtensionControllerImpl(
                 mContext,
                 mock(LeakDetector.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index bc3a5b7..1510ee8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -115,6 +115,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -398,7 +399,7 @@
                 mock(INotificationManager.class),
                 mIDreamManager,
                 mVisibilityProvider,
-                interruptionStateProvider,
+                new NotificationInterruptStateProviderWrapper(interruptionStateProvider),
                 mZenModeController,
                 mLockscreenUserManager,
                 mCommonNotifCollection,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 8cae998..9de7a87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -128,7 +128,7 @@
     @Test
     public void initDesktopMode_registersListener() {
         mWMShell.initDesktopMode(mDesktopMode);
-        verify(mDesktopMode).addListener(
+        verify(mDesktopMode).addVisibleTasksListener(
                 any(DesktopModeTaskRepository.VisibleTasksListener.class),
                 any(Executor.class));
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/RoboPilotTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/RoboPilotTest.java
new file mode 100644
index 0000000..3fff136
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/RoboPilotTest.java
@@ -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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark as tests for Robolectric pilot projects. The filter can better help grouping test results
+ * that runs on CI
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RoboPilotTest {
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
index 5b431e7..0983041 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
@@ -14,6 +14,8 @@
 
 package com.android.systemui.animation
 
+import com.android.app.animation.Interpolators
+
 /** A [LaunchAnimator] to be used in tests. */
 fun fakeLaunchAnimator(): LaunchAnimator {
     return LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 3bd4547..b2e8ffc 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -4277,7 +4277,7 @@
 
         getUiForShowing().showFillUi(filledId, response, filterText,
                 mService.getServicePackageName(), mComponentName,
-                targetLabel, targetIcon, this, id, mCompatMode);
+                targetLabel, targetIcon, this, userId, id, mCompatMode);
 
         synchronized (mLock) {
             mPresentationStatsEventLogger.maybeSetCountShown(
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 8291610..a631818 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -23,6 +23,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -196,17 +197,19 @@
      * @param serviceLabel label of autofill service
      * @param serviceIcon icon of autofill service
      * @param callback identifier for the caller
+     * @param userId the user associated wit the session
      * @param sessionId id of the autofill session
      * @param compatMode whether the app is being autofilled in compatibility mode.
      */
     public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response,
             @Nullable String filterText, @Nullable String servicePackageName,
             @NonNull ComponentName componentName, @NonNull CharSequence serviceLabel,
-            @NonNull Drawable serviceIcon, @NonNull AutoFillUiCallback callback, int sessionId,
-            boolean compatMode) {
+            @NonNull Drawable serviceIcon, @NonNull AutoFillUiCallback callback,
+            @UserIdInt int userId, int sessionId, boolean compatMode) {
         if (sDebug) {
             final int size = filterText == null ? 0 : filterText.length();
-            Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + size + " chars");
+            Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + size + " chars, userId="
+                    + userId);
         }
         final LogMaker log = Helper
                 .newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, componentName, servicePackageName,
@@ -221,7 +224,7 @@
                 return;
             }
             hideAllUiThread(callback);
-            mFillUi = new FillUi(mContext, response, focusedId,
+            mFillUi = new FillUi(mContext, userId, response, focusedId,
                     filterText, mOverlayControl, serviceLabel, serviceIcon,
                     mUiModeMgr.isNightMode(),
                     new FillUi.Callback() {
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 76f4505..30d2fe4 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -22,12 +22,15 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.IntentSender;
 import android.content.pm.PackageManager;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.hardware.display.DisplayManager;
+import android.os.UserManager;
 import android.service.autofill.Dataset;
 import android.service.autofill.Dataset.DatasetFieldFilter;
 import android.service.autofill.FillResponse;
@@ -36,6 +39,7 @@
 import android.util.Slog;
 import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
+import android.view.Display;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -57,9 +61,12 @@
 import android.widget.TextView;
 
 import com.android.internal.R;
+import com.android.server.LocalServices;
 import com.android.server.UiThread;
 import com.android.server.autofill.AutofillManagerService;
 import com.android.server.autofill.Helper;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.utils.Slogf;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -133,13 +140,29 @@
         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
     }
 
-    FillUi(@NonNull Context context, @NonNull FillResponse response,
+    FillUi(@NonNull Context context, @UserIdInt int userId, @NonNull FillResponse response,
             @NonNull AutofillId focusedViewId, @Nullable String filterText,
             @NonNull OverlayControl overlayControl, @NonNull CharSequence serviceLabel,
             @NonNull Drawable serviceIcon, boolean nightMode, @NonNull Callback callback) {
         if (sVerbose) Slog.v(TAG, "nightMode: " + nightMode);
         mThemeId = nightMode ? THEME_ID_DARK : THEME_ID_LIGHT;
         mCallback = callback;
+
+        if (UserManager.isVisibleBackgroundUsersEnabled()) {
+            UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+            int displayId = umi.getMainDisplayAssignedToUser(userId);
+            if (sDebug) {
+                Slogf.d(TAG, "Creating context for display %d for user %d", displayId, userId);
+            }
+            Display display = context.getSystemService(DisplayManager.class).getDisplay(displayId);
+            if (display != null) {
+                context = context.createDisplayContext(display);
+            } else {
+                Slogf.d(TAG, "Could not get display with id %d (which is associated with user %d; "
+                        + "FillUi operations will probably fail", displayId, userId);
+            }
+        }
+
         mFullScreen = isFullScreen(context);
         mContext = new ContextThemeWrapper(context, mThemeId);
 
@@ -774,6 +797,7 @@
         pw.print(prefix); pw.print("mContentWidth: "); pw.println(mContentWidth);
         pw.print(prefix); pw.print("mContentHeight: "); pw.println(mContentHeight);
         pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
+        pw.print(prefix); pw.print("mContext: "); pw.println(mContext);
         pw.print(prefix); pw.print("theme id: "); pw.print(mThemeId);
         switch (mThemeId) {
             case THEME_ID_DARK:
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index efff112..d8fbd08 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -604,25 +604,21 @@
         }
 
         @Override
-        @GuardedBy("CompanionDeviceManagerService.this.mTransportManager.mTransports")
         public void addOnTransportsChangedListener(IOnTransportsChangedListener listener) {
             mTransportManager.addListener(listener);
         }
 
         @Override
-        @GuardedBy("CompanionDeviceManagerService.this.mTransportManager.mTransports")
         public void removeOnTransportsChangedListener(IOnTransportsChangedListener listener) {
             mTransportManager.removeListener(listener);
         }
 
         @Override
-        @GuardedBy("CompanionDeviceManagerService.this.mTransportManager.mTransports")
         public void sendMessage(int messageType, byte[] data, int[] associationIds) {
             mTransportManager.sendMessage(messageType, data, associationIds);
         }
 
         @Override
-        @GuardedBy("CompanionDeviceManagerService.this.mTransportManager.mTransports")
         public void addOnMessageReceivedListener(int messageType,
                 IOnMessageReceivedListener listener) {
             mTransportManager.addListener(messageType, listener);
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
index 19d8b87..6f99d86 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
@@ -17,26 +17,38 @@
 package com.android.server.companion.datatransfer.contextsync;
 
 import android.content.ComponentName;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.telecom.Call;
+import android.telecom.Connection;
 import android.telecom.ConnectionService;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 import java.util.UUID;
 
 /** Service for Telecom to bind to when call metadata is synced between devices. */
 public class CallMetadataSyncConnectionService extends ConnectionService {
 
+    private static final String TAG = "CallMetadataSyncConnectionService";
+
+    private AudioManager mAudioManager;
     private TelecomManager mTelecomManager;
-    private final Map<String, PhoneAccountHandle> mPhoneAccountHandles = new HashMap<>();
+    private final Map<PhoneAccountHandleIdentifier, PhoneAccountHandle> mPhoneAccountHandles =
+            new HashMap<>();
 
     @Override
     public void onCreate() {
         super.onCreate();
+
+        mAudioManager = getSystemService(AudioManager.class);
         mTelecomManager = getSystemService(TelecomManager.class);
     }
 
@@ -44,34 +56,277 @@
      * Registers a {@link android.telecom.PhoneAccount} for a given call-capable app on the synced
      * device.
      */
-    public void registerPhoneAccount(String packageName, String humanReadableAppName) {
-        final PhoneAccount phoneAccount = createPhoneAccount(packageName, humanReadableAppName);
-        if (phoneAccount != null) {
-            mTelecomManager.registerPhoneAccount(phoneAccount);
-            mTelecomManager.enablePhoneAccount(mPhoneAccountHandles.get(packageName), true);
-        }
+    private void registerPhoneAccount(int associationId, String appIdentifier,
+            String humanReadableAppName) {
+        final PhoneAccountHandleIdentifier phoneAccountHandleIdentifier =
+                new PhoneAccountHandleIdentifier(associationId, appIdentifier);
+        final PhoneAccount phoneAccount = createPhoneAccount(phoneAccountHandleIdentifier,
+                humanReadableAppName);
+        mTelecomManager.registerPhoneAccount(phoneAccount);
+        mTelecomManager.enablePhoneAccount(mPhoneAccountHandles.get(phoneAccountHandleIdentifier),
+                true);
     }
 
     /**
      * Unregisters a {@link android.telecom.PhoneAccount} for a given call-capable app on the synced
      * device.
      */
-    public void unregisterPhoneAccount(String packageName) {
-        mTelecomManager.unregisterPhoneAccount(mPhoneAccountHandles.remove(packageName));
+    private void unregisterPhoneAccount(int associationId, String appIdentifier) {
+        mTelecomManager.unregisterPhoneAccount(mPhoneAccountHandles.remove(
+                new PhoneAccountHandleIdentifier(associationId, appIdentifier)));
     }
 
     @VisibleForTesting
-    PhoneAccount createPhoneAccount(String packageName, String humanReadableAppName) {
-        if (mPhoneAccountHandles.containsKey(packageName)) {
+    PhoneAccount createPhoneAccount(PhoneAccountHandleIdentifier phoneAccountHandleIdentifier,
+            String humanReadableAppName) {
+        if (mPhoneAccountHandles.containsKey(phoneAccountHandleIdentifier)) {
             // Already exists!
             return null;
         }
         final PhoneAccountHandle handle = new PhoneAccountHandle(
                 new ComponentName(this, CallMetadataSyncConnectionService.class),
                 UUID.randomUUID().toString());
-        mPhoneAccountHandles.put(packageName, handle);
+        mPhoneAccountHandles.put(phoneAccountHandleIdentifier, handle);
         return new PhoneAccount.Builder(handle, humanReadableAppName)
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER
                         | PhoneAccount.CAPABILITY_SELF_MANAGED).build();
     }
+
+    static final class PhoneAccountHandleIdentifier {
+        private final int mAssociationId;
+        private final String mAppIdentifier;
+
+        PhoneAccountHandleIdentifier(int associationId, String appIdentifier) {
+            mAssociationId = associationId;
+            mAppIdentifier = appIdentifier;
+        }
+
+        public int getAssociationId() {
+            return mAssociationId;
+        }
+
+        public String getAppIdentifier() {
+            return mAppIdentifier;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mAssociationId, mAppIdentifier);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other instanceof PhoneAccountHandleIdentifier) {
+                return ((PhoneAccountHandleIdentifier) other).getAssociationId() == mAssociationId
+                        && mAppIdentifier != null
+                        && mAppIdentifier.equals(
+                        ((PhoneAccountHandleIdentifier) other).getAppIdentifier());
+            }
+            return false;
+        }
+    }
+
+    private static final class CallMetadataSyncConnectionIdentifier {
+        private final int mAssociationId;
+        private final long mCallId;
+
+        CallMetadataSyncConnectionIdentifier(int associationId, long callId) {
+            mAssociationId = associationId;
+            mCallId = callId;
+        }
+
+        public int getAssociationId() {
+            return mAssociationId;
+        }
+
+        public long getCallId() {
+            return mCallId;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mAssociationId, mCallId);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other instanceof CallMetadataSyncConnectionIdentifier) {
+                return ((CallMetadataSyncConnectionIdentifier) other).getAssociationId()
+                        == mAssociationId
+                        && (((CallMetadataSyncConnectionIdentifier) other).getCallId() == mCallId);
+            }
+            return false;
+        }
+    }
+
+    private abstract static class CallMetadataSyncConnectionCallback {
+
+        abstract void sendCallAction(int associationId, long callId, int action);
+
+        abstract void sendStateChange(int associationId, long callId, int newState);
+    }
+
+    private static class CallMetadataSyncConnection extends Connection {
+
+        private final TelecomManager mTelecomManager;
+        private final AudioManager mAudioManager;
+        private final int mAssociationId;
+        private final CallMetadataSyncData.Call mCall;
+        private final CallMetadataSyncConnectionCallback mCallback;
+
+        CallMetadataSyncConnection(TelecomManager telecomManager, AudioManager audioManager,
+                int associationId, CallMetadataSyncData.Call call,
+                CallMetadataSyncConnectionCallback callback) {
+            mTelecomManager = telecomManager;
+            mAudioManager = audioManager;
+            mAssociationId = associationId;
+            mCall = call;
+            mCallback = callback;
+        }
+
+        public long getCallId() {
+            return mCall.getId();
+        }
+
+        public void initialize() {
+            final int status = mCall.getStatus();
+            if (status == android.companion.Telecom.Call.RINGING_SILENCED) {
+                mTelecomManager.silenceRinger();
+            }
+            final int state = CrossDeviceCall.convertStatusToState(status);
+            if (state == Call.STATE_RINGING) {
+                setRinging();
+            } else if (state == Call.STATE_ACTIVE) {
+                setActive();
+            } else if (state == Call.STATE_HOLDING) {
+                setOnHold();
+            } else {
+                Slog.e(TAG, "Could not initialize call to unknown state");
+            }
+
+            final Bundle extras = new Bundle();
+            extras.putLong(CrossDeviceCall.EXTRA_CALL_ID, mCall.getId());
+            putExtras(extras);
+
+            int capabilities = getConnectionCapabilities();
+            if (mCall.hasControl(android.companion.Telecom.Call.PUT_ON_HOLD)) {
+                capabilities |= CAPABILITY_HOLD;
+            } else {
+                capabilities &= ~CAPABILITY_HOLD;
+            }
+            if (mCall.hasControl(android.companion.Telecom.Call.MUTE)) {
+                capabilities |= CAPABILITY_MUTE;
+            } else {
+                capabilities &= ~CAPABILITY_MUTE;
+            }
+            mAudioManager.setMicrophoneMute(
+                    mCall.hasControl(android.companion.Telecom.Call.UNMUTE));
+            if (capabilities != getConnectionCapabilities()) {
+                setConnectionCapabilities(capabilities);
+            }
+        }
+
+        public void update(CallMetadataSyncData.Call call) {
+            final int status = call.getStatus();
+            if (status == android.companion.Telecom.Call.RINGING_SILENCED
+                    && mCall.getStatus() != android.companion.Telecom.Call.RINGING_SILENCED) {
+                mTelecomManager.silenceRinger();
+            }
+            mCall.setStatus(status);
+            final int state = CrossDeviceCall.convertStatusToState(status);
+            if (state != getState()) {
+                if (state == Call.STATE_RINGING) {
+                    setRinging();
+                } else if (state == Call.STATE_ACTIVE) {
+                    setActive();
+                } else if (state == Call.STATE_HOLDING) {
+                    setOnHold();
+                } else {
+                    Slog.e(TAG, "Could not update call to unknown state");
+                }
+            }
+
+            int capabilities = getConnectionCapabilities();
+            final boolean hasHoldControl = mCall.hasControl(
+                    android.companion.Telecom.Call.PUT_ON_HOLD)
+                    || mCall.hasControl(android.companion.Telecom.Call.TAKE_OFF_HOLD);
+            if (hasHoldControl != ((getConnectionCapabilities() & CAPABILITY_HOLD)
+                    == CAPABILITY_HOLD)) {
+                if (hasHoldControl) {
+                    capabilities |= CAPABILITY_HOLD;
+                } else {
+                    capabilities &= ~CAPABILITY_HOLD;
+                }
+            }
+            final boolean hasMuteControl = mCall.hasControl(android.companion.Telecom.Call.MUTE);
+            if (hasMuteControl != ((getConnectionCapabilities() & CAPABILITY_MUTE)
+                    == CAPABILITY_MUTE)) {
+                if (hasMuteControl) {
+                    capabilities |= CAPABILITY_MUTE;
+                } else {
+                    capabilities &= ~CAPABILITY_MUTE;
+                }
+            }
+            mAudioManager.setMicrophoneMute(
+                    mCall.hasControl(android.companion.Telecom.Call.UNMUTE));
+            if (capabilities != getConnectionCapabilities()) {
+                setConnectionCapabilities(capabilities);
+            }
+        }
+
+        @Override
+        public void onAnswer(int videoState) {
+            sendCallAction(android.companion.Telecom.Call.ACCEPT);
+        }
+
+        @Override
+        public void onReject() {
+            sendCallAction(android.companion.Telecom.Call.REJECT);
+        }
+
+        @Override
+        public void onReject(int rejectReason) {
+            onReject();
+        }
+
+        @Override
+        public void onReject(String replyMessage) {
+            onReject();
+        }
+
+        @Override
+        public void onSilence() {
+            sendCallAction(android.companion.Telecom.Call.SILENCE);
+        }
+
+        @Override
+        public void onHold() {
+            sendCallAction(android.companion.Telecom.Call.PUT_ON_HOLD);
+        }
+
+        @Override
+        public void onUnhold() {
+            sendCallAction(android.companion.Telecom.Call.TAKE_OFF_HOLD);
+        }
+
+        @Override
+        public void onMuteStateChanged(boolean isMuted) {
+            sendCallAction(isMuted ? android.companion.Telecom.Call.MUTE
+                    : android.companion.Telecom.Call.UNMUTE);
+        }
+
+        @Override
+        public void onDisconnect() {
+            sendCallAction(android.companion.Telecom.Call.END);
+        }
+
+        @Override
+        public void onStateChanged(int state) {
+            mCallback.sendStateChange(mAssociationId, mCall.getId(), state);
+        }
+
+        private void sendCallAction(int action) {
+            mCallback.sendCallAction(mAssociationId, mCall.getId(), action);
+        }
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 9677b70..a3e095e 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -96,27 +96,29 @@
     /**
      * Add a listener to receive callbacks when a message is received for the message type
      */
-    @GuardedBy("mTransports")
     public void addListener(int message, @NonNull IOnMessageReceivedListener listener) {
         mMessageListeners.put(message, listener);
-        for (int i = 0; i < mTransports.size(); i++) {
-            mTransports.valueAt(i).addListener(message, listener);
+        synchronized (mTransports) {
+            for (int i = 0; i < mTransports.size(); i++) {
+                mTransports.valueAt(i).addListener(message, listener);
+            }
         }
     }
 
     /**
      * Add a listener to receive callbacks when any of the transports is changed
      */
-    @GuardedBy("mTransports")
     public void addListener(IOnTransportsChangedListener listener) {
         Slog.i(TAG, "Registering OnTransportsChangedListener");
         mTransportsListeners.register(listener);
         List<AssociationInfo> associations = new ArrayList<>();
-        for (int i = 0; i < mTransports.size(); i++) {
-            AssociationInfo association = mAssociationStore.getAssociationById(
-                    mTransports.keyAt(i));
-            if (association != null) {
-                associations.add(association);
+        synchronized (mTransports) {
+            for (int i = 0; i < mTransports.size(); i++) {
+                AssociationInfo association = mAssociationStore.getAssociationById(
+                        mTransports.keyAt(i));
+                if (association != null) {
+                    associations.add(association);
+                }
             }
         }
         mTransportsListeners.broadcast(listener1 -> {
@@ -148,18 +150,19 @@
     /**
      * Send a message to remote devices through the transports
      */
-    @GuardedBy("mTransports")
     public void sendMessage(int message, byte[] data, int[] associationIds) {
         Slog.i(TAG, "Sending message 0x" + Integer.toHexString(message)
                 + " data length " + data.length);
-        for (int i = 0; i < associationIds.length; i++) {
-            if (mTransports.contains(associationIds[i])) {
-                try {
-                    mTransports.get(associationIds[i]).sendMessage(message, data);
-                } catch (IOException e) {
-                    Slog.e(TAG, "Failed to send message 0x" + Integer.toHexString(message)
-                            + " data length " + data.length + " to association "
-                            + associationIds[i]);
+        synchronized (mTransports) {
+            for (int i = 0; i < associationIds.length; i++) {
+                if (mTransports.contains(associationIds[i])) {
+                    try {
+                        mTransports.get(associationIds[i]).sendMessage(message, data);
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Failed to send message 0x" + Integer.toHexString(message)
+                                + " data length " + data.length + " to association "
+                                + associationIds[i]);
+                    }
                 }
             }
         }
@@ -215,14 +218,15 @@
         }
     }
 
-    @GuardedBy("mTransports")
     private void notifyOnTransportsChanged() {
         List<AssociationInfo> associations = new ArrayList<>();
-        for (int i = 0; i < mTransports.size(); i++) {
-            AssociationInfo association = mAssociationStore.getAssociationById(
-                    mTransports.keyAt(i));
-            if (association != null) {
-                associations.add(association);
+        synchronized (mTransports) {
+            for (int i = 0; i < mTransports.size(); i++) {
+                AssociationInfo association = mAssociationStore.getAssociationById(
+                        mTransports.keyAt(i));
+                if (association != null) {
+                    associations.add(association);
+                }
             }
         }
         mTransportsListeners.broadcast(listener -> {
@@ -233,14 +237,15 @@
         });
     }
 
-    @GuardedBy("mTransports")
     private void initializeTransport(int associationId, ParcelFileDescriptor fd) {
         Slog.i(TAG, "Initializing transport");
         if (!isSecureTransportEnabled()) {
             Transport transport = new RawTransport(associationId, fd, mContext);
             addMessageListenersToTransport(transport);
             transport.start();
-            mTransports.put(associationId, transport);
+            synchronized (mTransports) {
+                mTransports.put(associationId, transport);
+            }
             Slog.i(TAG, "RawTransport is created");
             return;
         }
@@ -283,7 +288,6 @@
     /**
      * Depending on the remote platform info to decide which transport should be created
      */
-    @GuardedBy("CompanionTransportManager.this.mTransports")
     private void onPlatformInfoReceived(int associationId, byte[] data) {
         if (mTempTransport.getAssociationId() != associationId) {
             return;
@@ -330,7 +334,9 @@
         }
         addMessageListenersToTransport(transport);
         transport.start();
-        mTransports.put(transport.getAssociationId(), transport);
+        synchronized (mTransports) {
+            mTransports.put(transport.getAssociationId(), transport);
+        }
         // Doesn't need to notifyTransportsChanged here, it'll be done in attachSystemDataTransport
     }
 
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 ae88f24..de0f68c 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -404,39 +404,44 @@
     public void close() {
         super.close_enforcePermission();
         // Remove about-to-be-closed virtual device from the service before butchering it.
-        mService.removeVirtualDevice(mDeviceId);
+        boolean removed = mService.removeVirtualDevice(mDeviceId);
         mDeviceId = Context.DEVICE_ID_INVALID;
 
-        VirtualDisplayWrapper[] virtualDisplaysToBeReleased;
-        synchronized (mVirtualDeviceLock) {
-            if (mVirtualAudioController != null) {
-                mVirtualAudioController.stopListening();
-                mVirtualAudioController = null;
-            }
-            mLocaleList = null;
-            virtualDisplaysToBeReleased = new VirtualDisplayWrapper[mVirtualDisplays.size()];
-            for (int i = 0; i < mVirtualDisplays.size(); i++) {
-                virtualDisplaysToBeReleased[i] = mVirtualDisplays.valueAt(i);
-            }
-            mVirtualDisplays.clear();
-            mVirtualSensorList = null;
-            mVirtualSensors.clear();
+        // Device is already closed.
+        if (!removed) {
+            return;
         }
-        // Destroy the display outside locked section.
-        for (VirtualDisplayWrapper virtualDisplayWrapper : virtualDisplaysToBeReleased) {
-            mDisplayManager.releaseVirtualDisplay(virtualDisplayWrapper.getToken());
-            // The releaseVirtualDisplay call above won't trigger
-            // VirtualDeviceImpl.onVirtualDisplayRemoved callback because we already removed the
-            // virtual device from the service - we release the other display-tied resources here
-            // with the guarantee it will be done exactly once.
-            releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
-        }
-
-        mAppToken.unlinkToDeath(this, 0);
-        mCameraAccessController.stopObservingIfNeeded();
 
         final long ident = Binder.clearCallingIdentity();
         try {
+            VirtualDisplayWrapper[] virtualDisplaysToBeReleased;
+            synchronized (mVirtualDeviceLock) {
+                if (mVirtualAudioController != null) {
+                    mVirtualAudioController.stopListening();
+                    mVirtualAudioController = null;
+                }
+                mLocaleList = null;
+                virtualDisplaysToBeReleased = new VirtualDisplayWrapper[mVirtualDisplays.size()];
+                for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                    virtualDisplaysToBeReleased[i] = mVirtualDisplays.valueAt(i);
+                }
+                mVirtualDisplays.clear();
+                mVirtualSensorList = null;
+                mVirtualSensors.clear();
+            }
+            // Destroy the display outside locked section.
+            for (VirtualDisplayWrapper virtualDisplayWrapper : virtualDisplaysToBeReleased) {
+                mDisplayManager.releaseVirtualDisplay(virtualDisplayWrapper.getToken());
+                // The releaseVirtualDisplay call above won't trigger
+                // VirtualDeviceImpl.onVirtualDisplayRemoved callback because we already removed the
+                // virtual device from the service - we release the other display-tied resources
+                // here with the guarantee it will be done exactly once.
+                releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
+            }
+
+            mAppToken.unlinkToDeath(this, 0);
+            mCameraAccessController.stopObservingIfNeeded();
+
             mInputController.close();
             mSensorController.close();
         } finally {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 9644642..ad4c0bf 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -202,8 +202,19 @@
         }
     }
 
-    void removeVirtualDevice(int deviceId) {
+    /**
+     * Remove the virtual device. Sends the
+     * {@link VirtualDeviceManager#ACTION_VIRTUAL_DEVICE_REMOVED} broadcast as a result.
+     *
+     * @param deviceId deviceId to be removed
+     * @return {@code true} if the device was removed, {@code false} if the operation was a no-op
+     */
+    boolean removeVirtualDevice(int deviceId) {
         synchronized (mVirtualDeviceManagerLock) {
+            if (!mVirtualDevices.contains(deviceId)) {
+                return false;
+            }
+
             mAppsOnVirtualDevices.remove(deviceId);
             mVirtualDevices.remove(deviceId);
         }
@@ -223,6 +234,7 @@
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
+        return true;
     }
 
     private void syncVirtualDevicesToCdmAssociations(List<AssociationInfo> associations) {
@@ -248,7 +260,6 @@
         for (VirtualDeviceImpl virtualDevice : virtualDevicesToRemove) {
             virtualDevice.close();
         }
-
     }
 
     private void registerCdmAssociationListener() {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index cfdf3ac..f8d19ec 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -161,6 +161,7 @@
         "android.hardware.health-V2-java", // AIDL
         "android.hardware.health-translate-java",
         "android.hardware.light-V1-java",
+        "android.hardware.security.rkp-V3-java",
         "android.hardware.tv.cec-V1.1-java",
         "android.hardware.tv.hdmi.cec-V1-java",
         "android.hardware.tv.hdmi.connection-V1-java",
@@ -177,6 +178,7 @@
         "android.hardware.power.stats-V2-java",
         "android.hardware.power-V4-java",
         "android.hidl.manager-V1.2-java",
+        "cbor-java",
         "icu4j_calendar_astronomer",
         "netd-client",
         "overlayable_policy_aidl-java",
diff --git a/services/core/java/com/android/server/SoundTriggerInternal.java b/services/core/java/com/android/server/SoundTriggerInternal.java
index e6c1750..6529465 100644
--- a/services/core/java/com/android/server/SoundTriggerInternal.java
+++ b/services/core/java/com/android/server/SoundTriggerInternal.java
@@ -142,6 +142,12 @@
                 @ModelParams int modelParam);
 
         /**
+         * Invalidates the sound trigger session and clears any associated resources. Subsequent
+         * calls to this object will throw IllegalStateException.
+         */
+        void detach();
+
+        /**
          * Unloads (and stops if running) the given keyphraseId
          */
         int unloadKeyphraseModel(int keyphaseId);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1f80aec..def2a2f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16707,6 +16707,11 @@
             pw.println(String.format("Resources History for %s (%s)",
                     app.processName,
                     app.info.packageName));
+            if (app.mOptRecord.isFrozen()) {
+                pw.println("  Skipping frozen process");
+                pw.flush();
+                continue;
+            }
             pw.flush();
             try {
                 TransferPipe tp = new TransferPipe("  ");
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index d926c2c..a181402 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -141,6 +141,7 @@
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 
 /**
  * Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
@@ -157,7 +158,7 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM;
 
     // Amount of time we wait for observers to handle a user switch before
-    // giving up on them and unfreezing the screen.
+    // giving up on them and dismissing the user switching dialog.
     static final int DEFAULT_USER_SWITCH_TIMEOUT_MS = 3 * 1000;
 
     /**
@@ -207,7 +208,7 @@
     /**
      * Amount of time waited for {@link WindowManagerService#dismissKeyguard} callbacks to be
      * called after dismissing the keyguard.
-     * Otherwise, we should move on to unfreeze the screen {@link #unfreezeScreen}
+     * Otherwise, we should move on to dismiss the dialog {@link #dismissUserSwitchDialog()}
      * and report user switch is complete {@link #REPORT_USER_SWITCH_COMPLETE_MSG}.
      */
     private static final int DISMISS_KEYGUARD_TIMEOUT_MS = 2 * 1000;
@@ -1695,14 +1696,6 @@
                 return false;
             }
 
-            if (foreground && isUserSwitchUiEnabled()) {
-                t.traceBegin("startFreezingScreen");
-                mInjector.getWindowManager().startFreezingScreen(
-                        R.anim.screen_user_exit, R.anim.screen_user_enter);
-                t.traceEnd();
-            }
-            dismissUserSwitchDialog(); // so that we don't hold a reference to mUserSwitchingDialog
-
             boolean needStart = false;
             boolean updateUmState = false;
             UserState uss;
@@ -1877,7 +1870,7 @@
         if (!success) {
             mInjector.getWindowManager().setSwitchingUser(false);
             mTargetUserId = UserHandle.USER_NULL;
-            dismissUserSwitchDialog();
+            dismissUserSwitchDialog(null);
         }
     }
 
@@ -2015,22 +2008,26 @@
             mUiHandler.sendMessage(mUiHandler.obtainMessage(
                     START_USER_SWITCH_UI_MSG, userNames));
         } else {
-            mHandler.removeMessages(START_USER_SWITCH_FG_MSG);
-            mHandler.sendMessage(mHandler.obtainMessage(
-                    START_USER_SWITCH_FG_MSG, targetUserId, 0));
+            sendStartUserSwitchFgMessage(targetUserId);
         }
         return true;
     }
 
-    private void dismissUserSwitchDialog() {
-        mInjector.dismissUserSwitchingDialog();
+    private void sendStartUserSwitchFgMessage(int targetUserId) {
+        mHandler.removeMessages(START_USER_SWITCH_FG_MSG);
+        mHandler.sendMessage(mHandler.obtainMessage(START_USER_SWITCH_FG_MSG, targetUserId, 0));
+    }
+
+    private void dismissUserSwitchDialog(Runnable onDismissed) {
+        mInjector.dismissUserSwitchingDialog(onDismissed);
     }
 
     private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
         // The dialog will show and then initiate the user switch by calling startUserInForeground
         mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second,
                 getSwitchingFromSystemUserMessageUnchecked(),
-                getSwitchingToSystemUserMessageUnchecked());
+                getSwitchingToSystemUserMessageUnchecked(),
+                /* onShown= */ () -> sendStartUserSwitchFgMessage(fromToUserPair.second.id));
     }
 
     private void dispatchForegroundProfileChanged(@UserIdInt int userId) {
@@ -2236,7 +2233,7 @@
 
         EventLog.writeEvent(EventLogTags.UC_CONTINUE_USER_SWITCH, oldUserId, newUserId);
 
-        // Do the keyguard dismiss and unfreeze later
+        // Do the keyguard dismiss and dismiss the user switching dialog later
         mHandler.removeMessages(COMPLETE_USER_SWITCH_MSG);
         mHandler.sendMessage(mHandler.obtainMessage(
                 COMPLETE_USER_SWITCH_MSG, oldUserId, newUserId));
@@ -2251,35 +2248,31 @@
     @VisibleForTesting
     void completeUserSwitch(int oldUserId, int newUserId) {
         final boolean isUserSwitchUiEnabled = isUserSwitchUiEnabled();
-        final Runnable runnable = () -> {
-            if (isUserSwitchUiEnabled) {
-                unfreezeScreen();
-            }
-            mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
-            mHandler.sendMessage(mHandler.obtainMessage(
-                    REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId));
-        };
-
-        // If there is no challenge set, dismiss the keyguard right away
-        if (isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId)) {
-            // Wait until the keyguard is dismissed to unfreeze
-            mInjector.dismissKeyguard(runnable);
-        } else {
-            runnable.run();
-        }
+        // serialize each conditional step
+        await(
+                // STEP 1 - If there is no challenge set, dismiss the keyguard right away
+                isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId),
+                mInjector::dismissKeyguard,
+                () -> await(
+                        // STEP 2 - If user switch ui was enabled, dismiss user switch dialog
+                        isUserSwitchUiEnabled,
+                        this::dismissUserSwitchDialog,
+                        () -> {
+                            // STEP 3 - Send REPORT_USER_SWITCH_COMPLETE_MSG to broadcast
+                            // ACTION_USER_SWITCHED & call UserSwitchObservers.onUserSwitchComplete
+                            mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
+                            mHandler.sendMessage(mHandler.obtainMessage(
+                                    REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId));
+                        }
+                ));
     }
 
-    /**
-     * Tell WindowManager we're ready to unfreeze the screen, at its leisure. Note that there is
-     * likely a lot going on, and WM won't unfreeze until the drawing is all done, so
-     * the actual unfreeze may still not happen for a long time; this is expected.
-     */
-    @VisibleForTesting
-    void unfreezeScreen() {
-        TimingsTraceAndSlog t = new TimingsTraceAndSlog();
-        t.traceBegin("stopFreezingScreen");
-        mInjector.getWindowManager().stopFreezingScreen();
-        t.traceEnd();
+    private void await(boolean condition, Consumer<Runnable> conditionalStep, Runnable nextStep) {
+        if (condition) {
+            conditionalStep.accept(nextStep);
+        } else {
+            nextStep.run();
+        }
     }
 
     private void moveUserToForeground(UserState uss, int newUserId) {
@@ -3731,17 +3724,18 @@
             mService.mCpHelper.installEncryptionUnawareProviders(userId);
         }
 
-        void dismissUserSwitchingDialog() {
+        void dismissUserSwitchingDialog(@Nullable Runnable onDismissed) {
             synchronized (mUserSwitchingDialogLock) {
                 if (mUserSwitchingDialog != null) {
-                    mUserSwitchingDialog.dismiss();
+                    mUserSwitchingDialog.dismiss(onDismissed);
                     mUserSwitchingDialog = null;
                 }
             }
         }
 
         void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser,
-                String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
+                String switchingFromSystemUserMessage, String switchingToSystemUserMessage,
+                @NonNull Runnable onShown) {
             if (mService.mContext.getPackageManager()
                     .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
                 // config_customUserSwitchUi is set to true on Automotive as CarSystemUI is
@@ -3751,11 +3745,10 @@
                         + "condition if it's shown by CarSystemUI as well");
             }
             synchronized (mUserSwitchingDialogLock) {
-                dismissUserSwitchingDialog();
-                mUserSwitchingDialog = new UserSwitchingDialog(mService, mService.mContext,
-                        fromUser, toUser, true /* above system */, switchingFromSystemUserMessage,
-                        switchingToSystemUserMessage);
-                mUserSwitchingDialog.show();
+                dismissUserSwitchingDialog(null);
+                mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser,
+                        switchingFromSystemUserMessage, switchingToSystemUserMessage);
+                mUserSwitchingDialog.show(onShown);
             }
         }
 
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index a5651bf..649305f 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -16,160 +16,258 @@
 
 package com.android.server.am;
 
-import android.app.AlertDialog;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.Dialog;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
-import android.os.Handler;
-import android.os.Message;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.drawable.Animatable2;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
-import android.view.LayoutInflater;
+import android.util.TypedValue;
 import android.view.View;
-import android.view.ViewTreeObserver;
+import android.view.Window;
 import android.view.WindowManager;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ObjectUtils;
+import com.android.internal.util.UserIcons;
 
 /**
- * Dialog to show when a user switch it about to happen. The intent is to snapshot the screen
- * immediately after the dialog shows so that the user is informed that something is happening
- * in the background rather than just freeze the screen and not know if the user-switch affordance
- * was being handled.
+ * Dialog to show during the user switch. This dialog shows target user's name and their profile
+ * picture with a circular spinner animation around it if the animations for this dialog are not
+ * disabled. And covers the whole screen so that all the UI jank caused by the switch are hidden.
  */
-class UserSwitchingDialog extends AlertDialog
-        implements ViewTreeObserver.OnWindowShownListener {
-    private static final String TAG = "ActivityManagerUserSwitchingDialog";
-
-    // Time to wait for the onWindowShown() callback before continuing the user switch
-    private static final int WINDOW_SHOWN_TIMEOUT_MS = 3000;
+class UserSwitchingDialog extends Dialog {
+    private static final String TAG = "UserSwitchingDialog";
+    private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
     // User switching doesn't happen that frequently, so it doesn't hurt to have it always on
     protected static final boolean DEBUG = true;
+    private static final long DIALOG_SHOW_HIDE_ANIMATION_DURATION_MS = 300;
+    private final boolean mDisableAnimations;
 
-    private final ActivityManagerService mService;
-    private final int mUserId;
-    private static final int MSG_START_USER = 1;
-    @GuardedBy("this")
-    private boolean mStartedUser;
-    final protected UserInfo mOldUser;
-    final protected UserInfo mNewUser;
-    final private String mSwitchingFromSystemUserMessage;
-    final private String mSwitchingToSystemUserMessage;
-    final protected Context mContext;
+    protected final UserInfo mOldUser;
+    protected final UserInfo mNewUser;
+    private final String mSwitchingFromSystemUserMessage;
+    private final String mSwitchingToSystemUserMessage;
+    protected final Context mContext;
+    private final int mTraceCookie;
 
-    public UserSwitchingDialog(ActivityManagerService service, Context context, UserInfo oldUser,
-            UserInfo newUser, boolean aboveSystem, String switchingFromSystemUserMessage,
-            String switchingToSystemUserMessage) {
-        super(context);
+    UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser,
+            String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
+        // TODO(b/278857848): Make full screen user switcher cover top part of the screen as well.
+        //                    This problem is seen only on phones, it works fine on tablets.
+        super(context, R.style.Theme_Material_NoActionBar_Fullscreen);
 
         mContext = context;
-        mService = service;
-        mUserId = newUser.id;
         mOldUser = oldUser;
         mNewUser = newUser;
         mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage;
         mSwitchingToSystemUserMessage = switchingToSystemUserMessage;
+        mDisableAnimations = ActivityManager.isLowRamDeviceStatic() || SystemProperties.getBoolean(
+                "debug.usercontroller.disable_user_switching_dialog_animations", false);
+        mTraceCookie = UserHandle.MAX_SECONDARY_USER_ID * oldUser.id + newUser.id;
 
         inflateContent();
+        configureWindow();
+    }
 
-        if (aboveSystem) {
-            getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
-        }
-
-        WindowManager.LayoutParams attrs = getWindow().getAttributes();
+    private void configureWindow() {
+        final Window window = getWindow();
+        final WindowManager.LayoutParams attrs = window.getAttributes();
         attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR |
-            WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-        getWindow().setAttributes(attrs);
+                WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+        window.setAttributes(attrs);
+        window.setBackgroundDrawableResource(android.R.color.transparent);
+        window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
     }
 
     void inflateContent() {
-        // Set up the dialog contents
         setCancelable(false);
-        Resources res = getContext().getResources();
-        // Custom view due to alignment and font size requirements
-        TextView view = (TextView) LayoutInflater.from(getContext()).inflate(
-                R.layout.user_switching_dialog, null);
+        setContentView(R.layout.user_switching_dialog);
 
-        String viewMessage = null;
-        if (UserManager.isDeviceInDemoMode(mContext)) {
-            if (mOldUser.isDemo()) {
-                viewMessage = res.getString(R.string.demo_restarting_message);
-            } else {
-                viewMessage = res.getString(R.string.demo_starting_message);
-            }
-        } else {
-            if (mOldUser.id == UserHandle.USER_SYSTEM) {
-                viewMessage = mSwitchingFromSystemUserMessage;
-            } else if (mNewUser.id == UserHandle.USER_SYSTEM) {
-                viewMessage = mSwitchingToSystemUserMessage;
-            }
-
-            // If switchingFromSystemUserMessage or switchingToSystemUserMessage is null, fallback
-            // to system message.
-            if (viewMessage == null) {
-                viewMessage = res.getString(R.string.user_switching_message, mNewUser.name);
-            }
-
-            view.setCompoundDrawablesWithIntrinsicBounds(null,
-                    getContext().getDrawable(R.drawable.ic_swap_horiz), null, null);
+        final TextView textView = findViewById(R.id.message);
+        if (textView != null) {
+            final String message = getTextMessage();
+            textView.setAccessibilityPaneTitle(message);
+            textView.setText(message);
         }
-        view.setAccessibilityPaneTitle(viewMessage);
-        view.setText(viewMessage);
-        setView(view);
+
+        final ImageView imageView = findViewById(R.id.icon);
+        if (imageView != null) {
+            imageView.setImageBitmap(getUserIconRounded());
+        }
+
+        final ImageView progressCircular = findViewById(R.id.progress_circular);
+        if (progressCircular != null) {
+            if (mDisableAnimations) {
+                progressCircular.setVisibility(View.GONE);
+            } else {
+                final TypedValue value = new TypedValue();
+                getContext().getTheme().resolveAttribute(R.attr.colorAccentPrimary, value, true);
+                progressCircular.setColorFilter(value.data);
+            }
+        }
+    }
+
+    private Bitmap getUserIconRounded() {
+        final Bitmap bmp = ObjectUtils.getOrElse(BitmapFactory.decodeFile(mNewUser.iconPath),
+                defaultUserIcon(mNewUser.id));
+        final int w = bmp.getWidth();
+        final int h = bmp.getHeight();
+        final Bitmap bmpRounded = Bitmap.createBitmap(w, h, bmp.getConfig());
+        final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        paint.setShader(new BitmapShader(bmp, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
+        new Canvas(bmpRounded).drawRoundRect((new RectF(0, 0, w, h)), w / 2f, h / 2f, paint);
+        return bmpRounded;
+    }
+
+    private Bitmap defaultUserIcon(@UserIdInt int userId) {
+        final Resources res = getContext().getResources();
+        final Drawable icon = UserIcons.getDefaultUserIcon(res, userId, /* light= */ false);
+        return UserIcons.convertToBitmapAtUserIconSize(res, icon);
+    }
+
+    private String getTextMessage() {
+        final Resources res = getContext().getResources();
+
+        if (UserManager.isDeviceInDemoMode(mContext)) {
+            return res.getString(mOldUser.isDemo()
+                    ? R.string.demo_restarting_message
+                    : R.string.demo_starting_message);
+        }
+
+        final String message =
+                mOldUser.id == UserHandle.USER_SYSTEM ? mSwitchingFromSystemUserMessage
+                : mNewUser.id == UserHandle.USER_SYSTEM ? mSwitchingToSystemUserMessage : null;
+
+        return message != null ? message
+                // If switchingFromSystemUserMessage or switchingToSystemUserMessage is null,
+                // fallback to system message.
+                : res.getString(R.string.user_switching_message, mNewUser.name);
     }
 
     @Override
     public void show() {
-        if (DEBUG) Slog.d(TAG, "show called");
+        asyncTraceBegin("", 0);
         super.show();
-        final View decorView = getWindow().getDecorView();
-        if (decorView != null) {
-            decorView.getViewTreeObserver().addOnWindowShownListener(this);
-        }
-        // Add a timeout as a safeguard, in case a race in screen on/off causes the window
-        // callback to never come.
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_USER),
-                WINDOW_SHOWN_TIMEOUT_MS);
     }
 
     @Override
-    public void onWindowShown() {
-        if (DEBUG) Slog.d(TAG, "onWindowShown called");
-        startUser();
+    public void dismiss() {
+        super.dismiss();
+        asyncTraceEnd("", 0);
     }
 
-    void startUser() {
-        synchronized (this) {
-            if (!mStartedUser) {
-                Slog.i(TAG, "starting user " + mUserId);
-                mService.mUserController.startUserInForeground(mUserId);
+    public void show(@NonNull Runnable onShown) {
+        if (DEBUG) Slog.d(TAG, "show called");
+        show();
+
+        if (mDisableAnimations) {
+            onShown.run();
+        } else {
+            startShowAnimation(onShown);
+        }
+    }
+
+    public void dismiss(@Nullable Runnable onDismissed) {
+        if (DEBUG) Slog.d(TAG, "dismiss called");
+
+        if (onDismissed == null) {
+            // no animation needed
+            dismiss();
+        } else if (mDisableAnimations) {
+            dismiss();
+            onDismissed.run();
+        } else {
+            startDismissAnimation(() -> {
                 dismiss();
-                mStartedUser = true;
-                final View decorView = getWindow().getDecorView();
-                if (decorView != null) {
-                    decorView.getViewTreeObserver().removeOnWindowShownListener(this);
-                }
-                mHandler.removeMessages(MSG_START_USER);
-            } else {
-                Slog.i(TAG, "user " + mUserId + " already started");
-            }
+                onDismissed.run();
+            });
         }
     }
 
-    private final Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_START_USER:
-                    Slog.w(TAG, "user switch window not shown in "
-                            + WINDOW_SHOWN_TIMEOUT_MS + " ms");
-                    startUser();
-                    break;
+    private void startShowAnimation(Runnable onAnimationEnd) {
+        asyncTraceBegin("-showAnimation", 1);
+        startDialogAnimation(new AlphaAnimation(0, 1), () -> {
+            asyncTraceEnd("-showAnimation", 1);
+
+            asyncTraceBegin("-spinnerAnimation", 2);
+            startProgressAnimation(() -> {
+                asyncTraceEnd("-spinnerAnimation", 2);
+
+                onAnimationEnd.run();
+            });
+        });
+    }
+
+    private void startDismissAnimation(Runnable onAnimationEnd) {
+        asyncTraceBegin("-dismissAnimation", 3);
+        startDialogAnimation(new AlphaAnimation(1, 0), () -> {
+            asyncTraceEnd("-dismissAnimation", 3);
+
+            onAnimationEnd.run();
+        });
+    }
+
+    private void startProgressAnimation(Runnable onAnimationEnd) {
+        final ImageView progressCircular = findViewById(R.id.progress_circular);
+        final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) progressCircular.getDrawable();
+        avd.registerAnimationCallback(new Animatable2.AnimationCallback() {
+            @Override
+            public void onAnimationEnd(Drawable drawable) {
+                onAnimationEnd.run();
             }
-        }
-    };
+        });
+        avd.start();
+    }
+
+    private void startDialogAnimation(Animation animation, Runnable onAnimationEnd) {
+        animation.setDuration(DIALOG_SHOW_HIDE_ANIMATION_DURATION_MS);
+        animation.setAnimationListener(new Animation.AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {
+
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                onAnimationEnd.run();
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) {
+
+            }
+        });
+        findViewById(R.id.content).startAnimation(animation);
+    }
+
+    private void asyncTraceBegin(String subTag, int subCookie) {
+        Trace.asyncTraceBegin(TRACE_TAG, TAG + subTag, mTraceCookie + subCookie);
+    }
+
+    private void asyncTraceEnd(String subTag, int subCookie) {
+        Trace.asyncTraceEnd(TRACE_TAG, TAG + subTag, mTraceCookie + subCookie);
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index a021174..ca482dc 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -2396,7 +2396,6 @@
             mHbmData.timeWindowMillis = hbmTiming.getTimeWindowSecs_all().longValue() * 1000;
             mHbmData.timeMaxMillis = hbmTiming.getTimeMaxSecs_all().longValue() * 1000;
             mHbmData.timeMinMillis = hbmTiming.getTimeMinSecs_all().longValue() * 1000;
-            mHbmData.thermalStatusLimit = convertThermalStatus(hbm.getThermalStatusLimit_all());
             mHbmData.allowInLowPowerMode = hbm.getAllowInLowPowerMode_all();
             final RefreshRateRange rr = hbm.getRefreshRate_all();
             if (rr != null) {
@@ -2972,9 +2971,6 @@
         /** Brightness level at which we transition from normal to high-brightness. */
         public float transitionPoint;
 
-        /** Enable HBM only if the thermal status is not higher than this. */
-        public @PowerManager.ThermalStatus int thermalStatusLimit;
-
         /** Whether HBM is allowed when {@code Settings.Global.LOW_POWER_MODE} is active. */
         public boolean allowInLowPowerMode;
 
@@ -2993,15 +2989,13 @@
         HighBrightnessModeData() {}
 
         HighBrightnessModeData(float minimumLux, float transitionPoint, long timeWindowMillis,
-                long timeMaxMillis, long timeMinMillis,
-                @PowerManager.ThermalStatus int thermalStatusLimit, boolean allowInLowPowerMode,
+                long timeMaxMillis, long timeMinMillis, boolean allowInLowPowerMode,
                 float minimumHdrPercentOfScreen) {
             this.minimumLux = minimumLux;
             this.transitionPoint = transitionPoint;
             this.timeWindowMillis = timeWindowMillis;
             this.timeMaxMillis = timeMaxMillis;
             this.timeMinMillis = timeMinMillis;
-            this.thermalStatusLimit = thermalStatusLimit;
             this.allowInLowPowerMode = allowInLowPowerMode;
             this.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen;
         }
@@ -3016,7 +3010,6 @@
             other.timeMaxMillis = timeMaxMillis;
             other.timeMinMillis = timeMinMillis;
             other.transitionPoint = transitionPoint;
-            other.thermalStatusLimit = thermalStatusLimit;
             other.allowInLowPowerMode = allowInLowPowerMode;
             other.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen;
         }
@@ -3029,7 +3022,6 @@
                     + ", timeWindow: " + timeWindowMillis + "ms"
                     + ", timeMax: " + timeMaxMillis + "ms"
                     + ", timeMin: " + timeMinMillis + "ms"
-                    + ", thermalStatusLimit: " + thermalStatusLimit
                     + ", allowInLowPowerMode: " + allowInLowPowerMode
                     + ", minimumHdrPercentOfScreen: " + minimumHdrPercentOfScreen
                     + "} ";
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index f1efec0..78c5f0e 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2828,6 +2828,7 @@
             pw.println("  mDisplayId=" + mDisplayId);
             pw.println("  mLeadDisplayId=" + mLeadDisplayId);
             pw.println("  mLightSensor=" + mLightSensor);
+            pw.println("  mDisplayBrightnessFollowers=" + mDisplayBrightnessFollowers);
 
             pw.println();
             pw.println("Display Power Controller Locked State:");
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 59e112e..a76f907 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -2222,6 +2222,7 @@
             pw.println("  mDisplayId=" + mDisplayId);
             pw.println("  mLeadDisplayId=" + mLeadDisplayId);
             pw.println("  mLightSensor=" + mLightSensor);
+            pw.println("  mDisplayBrightnessFollowers=" + mDisplayBrightnessFollowers);
 
             pw.println();
             pw.println("Display Power Controller Locked State:");
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index ca208ac..11160a5 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -22,13 +22,8 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.IThermalEventListener;
-import android.os.IThermalService;
 import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.os.Temperature;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.MathUtils;
@@ -75,7 +70,6 @@
     private final Runnable mHbmChangeCallback;
     private final Runnable mRecalcRunnable;
     private final Clock mClock;
-    private final SkinThermalStatusObserver mSkinThermalStatusObserver;
     private final Context mContext;
     private final SettingsObserver mSettingsObserver;
     private final Injector mInjector;
@@ -100,10 +94,8 @@
 
     private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
     private boolean mIsHdrLayerPresent = false;
-
     // mMaxDesiredHdrSdrRatio should only be applied when there is a valid backlight->nits mapping
     private float mMaxDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO;
-    private boolean mIsThermalStatusWithinLimit = true;
     private boolean mIsBlockedByLowPowerMode = false;
     private int mWidth;
     private int mHeight;
@@ -138,7 +130,6 @@
         mBrightnessMax = brightnessMax;
         mHbmChangeCallback = hbmChangeCallback;
         mHighBrightnessModeMetadata = hbmMetadata;
-        mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
         mSettingsObserver = new SettingsObserver(mHandler);
         mRecalcRunnable = this::recalculateTimeAllowance;
         mHdrListener = new HdrListener();
@@ -261,7 +252,6 @@
 
     void stop() {
         registerHdrListener(null /*displayToken*/);
-        mSkinThermalStatusObserver.stopObserving();
         mSettingsObserver.stopObserving();
     }
 
@@ -278,15 +268,10 @@
         mDisplayStatsId = displayUniqueId.hashCode();
 
         unregisterHdrListener();
-        mSkinThermalStatusObserver.stopObserving();
         mSettingsObserver.stopObserving();
         if (deviceSupportsHbm()) {
             registerHdrListener(displayToken);
             recalculateTimeAllowance();
-            if (mHbmData.thermalStatusLimit > PowerManager.THERMAL_STATUS_NONE) {
-                mIsThermalStatusWithinLimit = true;
-                mSkinThermalStatusObserver.startObserving();
-            }
             if (!mHbmData.allowInLowPowerMode) {
                 mIsBlockedByLowPowerMode = false;
                 mSettingsObserver.startObserving();
@@ -327,7 +312,6 @@
         pw.println("  mIsTimeAvailable= " + mIsTimeAvailable);
         pw.println("  mRunningStartTimeMillis="
                 + TimeUtils.formatUptime(mHighBrightnessModeMetadata.getRunningStartTimeMillis()));
-        pw.println("  mIsThermalStatusWithinLimit=" + mIsThermalStatusWithinLimit);
         pw.println("  mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode);
         pw.println("  width*height=" + mWidth + "*" + mHeight);
         pw.println("  mEvents=");
@@ -344,8 +328,6 @@
             }
             lastStartTime = dumpHbmEvent(pw, event);
         }
-
-        mSkinThermalStatusObserver.dump(pw);
     }
 
     private long dumpHbmEvent(PrintWriter pw, HbmEvent event) {
@@ -367,7 +349,7 @@
         // See {@link #getHdrBrightnessValue}.
         return !mIsHdrLayerPresent
                 && (mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange
-                && mIsThermalStatusWithinLimit && !mIsBlockedByLowPowerMode);
+                && !mIsBlockedByLowPowerMode);
     }
 
     private boolean deviceSupportsHbm() {
@@ -469,7 +451,6 @@
                     + ", isAutoBrightnessEnabled: " +  mIsAutoBrightnessEnabled
                     + ", mIsTimeAvailable: " + mIsTimeAvailable
                     + ", mIsInAllowedAmbientRange: " + mIsInAllowedAmbientRange
-                    + ", mIsThermalStatusWithinLimit: " + mIsThermalStatusWithinLimit
                     + ", mIsBlockedByLowPowerMode: " + mIsBlockedByLowPowerMode
                     + ", mBrightness: " + mBrightness
                     + ", mUnthrottledBrightness: " + mUnthrottledBrightness
@@ -499,13 +480,12 @@
     }
 
     private void updateHbmStats(int newMode) {
-        final float transitionPoint = mHbmData.transitionPoint;
         int state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF;
         if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
-                && getHdrBrightnessValue() > transitionPoint) {
+                && getHdrBrightnessValue() > mHbmData.transitionPoint) {
             state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR;
         } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT
-                && mBrightness > transitionPoint) {
+                && mBrightness > mHbmData.transitionPoint) {
             state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT;
         }
         if (state == mHbmStatsState) {
@@ -519,16 +499,6 @@
         final boolean newHbmSv =
                 (state == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT);
         if (oldHbmSv && !newHbmSv) {
-            // HighBrightnessModeController (HBMC) currently supports throttling from two sources:
-            //     1. Internal, received from HBMC.SkinThermalStatusObserver.notifyThrottling()
-            //     2. External, received from HBMC.onBrightnessChanged()
-            // TODO(b/216373254): Deprecate internal throttling source
-            final boolean internalThermalThrottling = !mIsThermalStatusWithinLimit;
-            final boolean externalThermalThrottling =
-                mUnthrottledBrightness > transitionPoint && // We would've liked HBM brightness...
-                mBrightness <= transitionPoint &&           // ...but we got NBM, because of...
-                mThrottlingReason == BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; // ...thermals.
-
             // If more than one conditions are flipped and turn off HBM sunlight
             // visibility, only one condition will be reported to make it simple.
             if (!mIsAutoBrightnessEnabled && mIsAutoBrightnessOffByState) {
@@ -541,7 +511,7 @@
                 reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP;
             } else if (!mIsTimeAvailable) {
                 reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT;
-            } else if (internalThermalThrottling || externalThermalThrottling) {
+            } else if (isThermalThrottlingActive()) {
                 reason = FrameworkStatsLog
                                  .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT;
             } else if (mIsHdrLayerPresent) {
@@ -561,6 +531,14 @@
         mHbmStatsState = state;
     }
 
+    @VisibleForTesting
+    boolean isThermalThrottlingActive() {
+        // We would've liked HBM, but we got NBM (normal brightness mode) because of thermals.
+        return mUnthrottledBrightness > mHbmData.transitionPoint
+                && mBrightness <= mHbmData.transitionPoint
+                && mThrottlingReason == BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
+    }
+
     private String hbmStatsStateToString(int hbmStatsState) {
         switch (hbmStatsState) {
         case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF:
@@ -635,82 +613,6 @@
         }
     }
 
-    private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
-        private final Injector mInjector;
-        private final Handler mHandler;
-
-        private IThermalService mThermalService;
-        private boolean mStarted;
-
-        SkinThermalStatusObserver(Injector injector, Handler handler) {
-            mInjector = injector;
-            mHandler = handler;
-        }
-
-        @Override
-        public void notifyThrottling(Temperature temp) {
-            if (DEBUG) {
-                Slog.d(TAG, "New thermal throttling status "
-                        + ", current thermal status = " + temp.getStatus()
-                        + ", threshold = " + mHbmData.thermalStatusLimit);
-            }
-            mHandler.post(() -> {
-                mIsThermalStatusWithinLimit = temp.getStatus() <= mHbmData.thermalStatusLimit;
-                // This recalculates HbmMode and runs mHbmChangeCallback if the mode has changed
-                updateHbmMode();
-            });
-        }
-
-        void startObserving() {
-            if (mStarted) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Thermal status observer already started");
-                }
-                return;
-            }
-            mThermalService = mInjector.getThermalService();
-            if (mThermalService == null) {
-                Slog.w(TAG, "Could not observe thermal status. Service not available");
-                return;
-            }
-            try {
-                // We get a callback immediately upon registering so there's no need to query
-                // for the current value.
-                mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
-                mStarted = true;
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to register thermal status listener", e);
-            }
-        }
-
-        void stopObserving() {
-            mIsThermalStatusWithinLimit = true;
-            if (!mStarted) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Stop skipped because thermal status observer not started");
-                }
-                return;
-            }
-            try {
-                mThermalService.unregisterThermalEventListener(this);
-                mStarted = false;
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to unregister thermal status listener", e);
-            }
-            mThermalService = null;
-        }
-
-        void dump(PrintWriter writer) {
-            writer.println("  SkinThermalStatusObserver:");
-            writer.println("    mStarted: " + mStarted);
-            if (mThermalService != null) {
-                writer.println("    ThermalService available");
-            } else {
-                writer.println("    ThermalService not available");
-            }
-        }
-    }
-
     private final class SettingsObserver extends ContentObserver {
         private final Uri mLowPowerModeSetting = Settings.Global.getUriFor(
                 Settings.Global.LOW_POWER_MODE);
@@ -766,11 +668,6 @@
             return SystemClock::uptimeMillis;
         }
 
-        public IThermalService getThermalService() {
-            return IThermalService.Stub.asInterface(
-                    ServiceManager.getService(Context.THERMAL_SERVICE));
-        }
-
         public void reportHbmStateChange(int display, int state, int reason) {
             FrameworkStatsLog.write(
                     FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED, display, state, reason);
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 0189294..03b49f0 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -65,6 +65,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.display.RefreshRateSettingsUtils;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.display.DisplayDeviceConfig;
@@ -1171,7 +1172,7 @@
         public static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 2;
 
         // SETTING_MIN_RENDER_FRAME_RATE is used to propose a lower bound of the render frame rate.
-        // It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY]
+        // It votes [minRefreshRate, Float.POSITIVE_INFINITY]
         public static final int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3;
 
         // APP_REQUEST_RENDER_FRAME_RATE_RANGE is used to for internal apps to limit the render
@@ -1376,10 +1377,10 @@
 
     @VisibleForTesting
     final class SettingsObserver extends ContentObserver {
-        private final Uri mPeakRefreshRateSetting =
-                Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
-        private final Uri mMinRefreshRateSetting =
-                Settings.System.getUriFor(Settings.System.MIN_REFRESH_RATE);
+        private final Uri mSmoothDisplaySetting =
+                Settings.System.getUriFor(Settings.System.SMOOTH_DISPLAY);
+        private final Uri mForcePeakRefreshRateSetting =
+                Settings.System.getUriFor(Settings.System.FORCE_PEAK_REFRESH_RATE);
         private final Uri mLowPowerModeSetting =
                 Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE);
         private final Uri mMatchContentFrameRateSetting =
@@ -1415,9 +1416,8 @@
 
         public void observe() {
             final ContentResolver cr = mContext.getContentResolver();
-            mInjector.registerPeakRefreshRateObserver(cr, this);
-            cr.registerContentObserver(mMinRefreshRateSetting, false /*notifyDescendants*/, this,
-                    UserHandle.USER_SYSTEM);
+            mInjector.registerSmoothDisplayObserver(cr, this);
+            mInjector.registerForcePeakRefreshRateObserver(cr, this);
             cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this,
                     UserHandle.USER_SYSTEM);
             cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/,
@@ -1459,8 +1459,8 @@
         @Override
         public void onChange(boolean selfChange, Uri uri, int userId) {
             synchronized (mLock) {
-                if (mPeakRefreshRateSetting.equals(uri)
-                        || mMinRefreshRateSetting.equals(uri)) {
+                if (mSmoothDisplaySetting.equals(uri)
+                        || mForcePeakRefreshRateSetting.equals(uri)) {
                     updateRefreshRateSettingLocked();
                 } else if (mLowPowerModeSetting.equals(uri)) {
                     updateLowPowerModeSettingLocked();
@@ -1515,12 +1515,9 @@
         }
 
         private void updateRefreshRateSettingLocked() {
-            final ContentResolver cr = mContext.getContentResolver();
-            float minRefreshRate = Settings.System.getFloatForUser(cr,
-                    Settings.System.MIN_REFRESH_RATE, 0f, cr.getUserId());
-            float peakRefreshRate = Settings.System.getFloatForUser(cr,
-                    Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate, cr.getUserId());
-            updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate);
+            updateRefreshRateSettingLocked(RefreshRateSettingsUtils.getMinRefreshRate(mContext),
+                    RefreshRateSettingsUtils.getPeakRefreshRate(mContext, mDefaultPeakRefreshRate),
+                    mDefaultRefreshRate);
         }
 
         private void updateRefreshRateSettingLocked(
@@ -3165,12 +3162,17 @@
     }
 
     interface Injector {
-        Uri PEAK_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
+        Uri SMOOTH_DISPLAY_URI = Settings.System.getUriFor(Settings.System.SMOOTH_DISPLAY);
+        Uri FORCE_PEAK_REFRESH_RATE_URI =
+                Settings.System.getUriFor(Settings.System.FORCE_PEAK_REFRESH_RATE);
 
         @NonNull
         DeviceConfigInterface getDeviceConfig();
 
-        void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
+        void registerSmoothDisplayObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer);
+
+        void registerForcePeakRefreshRateObserver(@NonNull ContentResolver cr,
                 @NonNull ContentObserver observer);
 
         void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
@@ -3205,9 +3207,16 @@
         }
 
         @Override
-        public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
+        public void registerSmoothDisplayObserver(@NonNull ContentResolver cr,
                 @NonNull ContentObserver observer) {
-            cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/,
+            cr.registerContentObserver(SMOOTH_DISPLAY_URI, false /*notifyDescendants*/,
+                    observer, UserHandle.USER_SYSTEM);
+        }
+
+        @Override
+        public void registerForcePeakRefreshRateObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            cr.registerContentObserver(FORCE_PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/,
                     observer, UserHandle.USER_SYSTEM);
         }
 
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index de10b1b..6d70d21 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -345,6 +345,7 @@
         if (!mCurrentDream.mIsPreviewMode && !mSentStartBroadcast) {
             mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL,
                     null /* receiverPermission */, mDreamingStartedStoppedOptions);
+            mListener.onDreamStarted(mCurrentDream.mToken);
             mSentStartBroadcast = true;
         }
     }
@@ -353,6 +354,7 @@
      * Callback interface to be implemented by the {@link DreamManagerService}.
      */
     public interface Listener {
+        void onDreamStarted(Binder token);
         void onDreamStopped(Binder token);
     }
 
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 0e26d46..d2dcc50 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -84,6 +84,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
 
 /**
  * Service api for managing dreams.
@@ -341,10 +342,24 @@
     }
 
     private void reportKeepDreamingWhenUnpluggingChanged(boolean keepDreaming) {
+        notifyDreamStateListeners(
+                listener -> listener.onKeepDreamingWhenUnpluggingChanged(keepDreaming));
+    }
+
+    private void reportDreamingStarted() {
+        notifyDreamStateListeners(listener -> listener.onDreamingStarted());
+    }
+
+    private void reportDreamingStopped() {
+        notifyDreamStateListeners(listener -> listener.onDreamingStopped());
+    }
+
+    private void notifyDreamStateListeners(
+            Consumer<DreamManagerInternal.DreamManagerStateListener> notifier) {
         mHandler.post(() -> {
             for (DreamManagerInternal.DreamManagerStateListener listener
                     : mDreamManagerStateListeners) {
-                listener.onKeepDreamingWhenUnpluggingChanged(keepDreaming);
+                notifier.accept(listener);
             }
         });
     }
@@ -767,12 +782,23 @@
 
     private final DreamController.Listener mControllerListener = new DreamController.Listener() {
         @Override
+        public void onDreamStarted(Binder token) {
+            // Note that this event is distinct from DreamManagerService#startDreamLocked as it
+            // tracks the DreamService attach point from DreamController, closest to the broadcast
+            // of ACTION_DREAMING_STARTED.
+
+            reportDreamingStarted();
+        }
+
+        @Override
         public void onDreamStopped(Binder token) {
             synchronized (mLock) {
                 if (mCurrentDream != null && mCurrentDream.token == token) {
                     cleanupDreamLocked();
                 }
             }
+
+            reportDreamingStopped();
         }
     };
 
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 2d3b97b..8a9cfba 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -764,7 +764,7 @@
                         userRecord.mHandler, routerRecord));
 
         Slog.i(TAG, TextUtils.formatSimple(
-                "registerRouter2 | package: %s, uid: %d, pid: %d, router: %d",
+                "registerRouter2 | package: %s, uid: %d, pid: %d, router id: %d",
                 packageName, uid, pid, routerRecord.mRouterId));
     }
 
@@ -776,10 +776,11 @@
             return;
         }
 
-        Slog.i(TAG, TextUtils.formatSimple(
-                "unregisterRouter2 | package: %s, router: %d",
-                routerRecord.mPackageName,
-                routerRecord.mRouterId));
+        Slog.i(
+                TAG,
+                TextUtils.formatSimple(
+                        "unregisterRouter2 | package: %s, router id: %d",
+                        routerRecord.mPackageName, routerRecord.mRouterId));
 
         UserRecord userRecord = routerRecord.mUserRecord;
         userRecord.mRouterRecords.remove(routerRecord);
@@ -806,9 +807,14 @@
             return;
         }
 
-        Slog.i(TAG, TextUtils.formatSimple(
-                "setDiscoveryRequestWithRouter2 | router: %d, discovery request: %s",
-                routerRecord.mRouterId, discoveryRequest.toString()));
+        Slog.i(
+                TAG,
+                TextUtils.formatSimple(
+                        "setDiscoveryRequestWithRouter2 | router: %s(id: %d), discovery request:"
+                            + " %s",
+                        routerRecord.mPackageName,
+                        routerRecord.mRouterId,
+                        discoveryRequest.toString()));
 
         routerRecord.mDiscoveryPreference = discoveryRequest;
         routerRecord.mUserRecord.mHandler.sendMessage(
@@ -832,10 +838,12 @@
                                 .collect(Collectors.joining(","))
                         : null;
 
-        Slog.i(TAG, TextUtils.formatSimple(
-                "setRouteListingPreference | router: %d, route listing preference: [%s]",
-                routerRecord.mRouterId,
-                routeListingAsString));
+        Slog.i(
+                TAG,
+                TextUtils.formatSimple(
+                        "setRouteListingPreference | router: %s(id: %d), route listing preference:"
+                            + " [%s]",
+                        routerRecord.mPackageName, routerRecord.mRouterId, routeListingAsString));
 
         routerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(
@@ -851,9 +859,11 @@
         RouterRecord routerRecord = mAllRouterRecords.get(binder);
 
         if (routerRecord != null) {
-            Slog.i(TAG, TextUtils.formatSimple(
-                    "setRouteVolumeWithRouter2 | router: %d, volume: %d",
-                    routerRecord.mRouterId, volume));
+            Slog.i(
+                    TAG,
+                    TextUtils.formatSimple(
+                            "setRouteVolumeWithRouter2 | router: %s(id: %d), volume: %d",
+                            routerRecord.mPackageName, routerRecord.mRouterId, volume));
 
             routerRecord.mUserRecord.mHandler.sendMessage(
                     obtainMessage(UserHandler::setRouteVolumeOnHandler,
@@ -935,9 +945,11 @@
             return;
         }
 
-        Slog.i(TAG, TextUtils.formatSimple(
-                "selectRouteWithRouter2 | router: %d, route: %s",
-                routerRecord.mRouterId, route.getId()));
+        Slog.i(
+                TAG,
+                TextUtils.formatSimple(
+                        "selectRouteWithRouter2 | router: %s(id: %d), route: %s",
+                        routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
 
         routerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::selectRouteOnHandler,
@@ -954,9 +966,11 @@
             return;
         }
 
-        Slog.i(TAG, TextUtils.formatSimple(
-                "deselectRouteWithRouter2 | router: %d, route: %s",
-                routerRecord.mRouterId, route.getId()));
+        Slog.i(
+                TAG,
+                TextUtils.formatSimple(
+                        "deselectRouteWithRouter2 | router: %s(id: %d), route: %s",
+                        routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
 
         routerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::deselectRouteOnHandler,
@@ -973,9 +987,11 @@
             return;
         }
 
-        Slog.i(TAG, TextUtils.formatSimple(
-                "transferToRouteWithRouter2 | router: %d, route: %s",
-                routerRecord.mRouterId, route.getId()));
+        Slog.i(
+                TAG,
+                TextUtils.formatSimple(
+                        "transferToRouteWithRouter2 | router: %s(id: %d), route: %s",
+                        routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
 
         String defaultRouteId =
                 routerRecord.mUserRecord.mHandler.mSystemProvider.getDefaultRoute().getId();
@@ -1002,9 +1018,14 @@
             return;
         }
 
-        Slog.i(TAG, TextUtils.formatSimple(
-                "setSessionVolumeWithRouter2 | router: %d, session: %s, volume: %d",
-                routerRecord.mRouterId,  uniqueSessionId, volume));
+        Slog.i(
+                TAG,
+                TextUtils.formatSimple(
+                        "setSessionVolumeWithRouter2 | router: %s(id: %d), session: %s, volume: %d",
+                        routerRecord.mPackageName,
+                        routerRecord.mRouterId,
+                        uniqueSessionId,
+                        volume));
 
         routerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::setSessionVolumeOnHandler,
@@ -1021,9 +1042,11 @@
             return;
         }
 
-        Slog.i(TAG, TextUtils.formatSimple(
-                "releaseSessionWithRouter2 | router: %d, session: %s",
-                routerRecord.mRouterId,  uniqueSessionId));
+        Slog.i(
+                TAG,
+                TextUtils.formatSimple(
+                        "releaseSessionWithRouter2 | router: %s(id: %d), session: %s",
+                        routerRecord.mPackageName, routerRecord.mRouterId, uniqueSessionId));
 
         routerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::releaseSessionOnHandler,
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 94d5aab..7a51126 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -19,9 +19,19 @@
 import static android.Manifest.permission.MANAGE_MEDIA_PROJECTION;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT;
+import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT;
+import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
+import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
+import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_TASK;
+import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
 import android.Manifest;
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
@@ -30,7 +40,9 @@
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -45,11 +57,13 @@
 import android.media.projection.IMediaProjectionWatcherCallback;
 import android.media.projection.MediaProjectionInfo;
 import android.media.projection.MediaProjectionManager;
+import android.media.projection.ReviewGrantedConsentResult;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.PermissionEnforcer;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -57,6 +71,7 @@
 import android.util.Slog;
 import android.view.ContentRecordingSession;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
@@ -69,6 +84,7 @@
 import java.io.PrintWriter;
 import java.time.Duration;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Manages MediaProjection sessions.
@@ -161,10 +177,9 @@
         }
     }
 
-
     @Override
     public void onStart() {
-        publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(),
+        publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(mContext),
                 false /*allowIsolated*/);
         mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback,
                 MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
@@ -305,6 +320,10 @@
                 }
                 return false;
             }
+            if (mProjectionGrant != null) {
+                // Cache the session details.
+                mProjectionGrant.mSession = incomingSession;
+            }
             return true;
         }
     }
@@ -323,9 +342,8 @@
         }
     }
 
-
     /**
-     * Reshows the permisison dialog for the user to review consent they've already granted in
+     * Re-shows the permission dialog for the user to review consent they've already granted in
      * the given projection instance.
      *
      * <p>Preconditions:
@@ -337,18 +355,111 @@
      * <p>Returns immediately but waits to start recording until user has reviewed their consent.
      */
     @VisibleForTesting
-    void requestConsentForInvalidProjection(IMediaProjection projection) {
+    void requestConsentForInvalidProjection() {
         synchronized (mLock) {
             Slog.v(TAG, "Reusing token: Reshow dialog for due to invalid projection.");
-            // TODO(b/274790702): Trigger the permission dialog again in SysUI.
+            // Trigger the permission dialog again in SysUI
+            // Do not handle the result; SysUI will update us when the user has consented.
+            mContext.startActivityAsUser(buildReviewGrantedConsentIntent(),
+                    UserHandle.getUserHandleForUid(mProjectionGrant.uid));
+        }
+    }
+
+    /**
+     * Returns an intent to re-show the consent dialog in SysUI. Should only be used for the
+     * scenario where the host app has re-used the consent token.
+     *
+     * <p>Consent dialog result handled in
+     * {@link BinderService#setUserReviewGrantedConsentResult(int)}.
+     */
+    private Intent buildReviewGrantedConsentIntent() {
+        final String permissionDialogString = mContext.getResources().getString(
+                R.string.config_mediaProjectionPermissionDialogComponent);
+        final ComponentName mediaProjectionPermissionDialogComponent =
+                ComponentName.unflattenFromString(permissionDialogString);
+        // We can use mProjectionGrant since we already checked that it matches the given token.
+        return new Intent().setComponent(mediaProjectionPermissionDialogComponent)
+                .putExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, true)
+                .putExtra(EXTRA_PACKAGE_REUSING_GRANTED_CONSENT, mProjectionGrant.packageName)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+    }
+
+    /**
+     * Handles result of dialog shown from {@link BinderService#buildReviewGrantedConsentIntent()}.
+     *
+     * <p>Tears down session if user did not consent, or starts mirroring if user did consent.
+     */
+    @VisibleForTesting
+    void setUserReviewGrantedConsentResult(@ReviewGrantedConsentResult int consentResult,
+            @Nullable IMediaProjection projection) {
+        synchronized (mLock) {
+            final boolean consentGranted =
+                    consentResult == RECORD_CONTENT_DISPLAY || consentResult == RECORD_CONTENT_TASK;
+            if (consentGranted && projection == null || !isCurrentProjection(
+                    projection.asBinder())) {
+                Slog.v(TAG, "Reusing token: Ignore consent result of " + consentResult + " for a "
+                        + "token that isn't current");
+                return;
+            }
+            if (mProjectionGrant == null) {
+                Slog.w(TAG, "Reusing token: Can't review consent with no ongoing projection.");
+                return;
+            }
+            if (mProjectionGrant.mSession == null
+                    || !mProjectionGrant.mSession.isWaitingToRecord()) {
+                Slog.w(TAG, "Reusing token: Ignore consent result " + consentResult
+                        + " if not waiting for the result.");
+                return;
+            }
+            Slog.v(TAG, "Reusing token: Handling user consent result " + consentResult);
+            switch (consentResult) {
+                case UNKNOWN:
+                case RECORD_CANCEL:
+                    // Pass in null to stop mirroring.
+                    setReviewedConsentSessionLocked(/* session= */ null);
+                    // The grant may now be null if setting the session failed.
+                    if (mProjectionGrant != null) {
+                        // Always stop the projection.
+                        mProjectionGrant.stop();
+                    }
+                    break;
+                case RECORD_CONTENT_DISPLAY:
+                    // TODO(270118861) The app may have specified a particular id in the virtual
+                    //  display config. However - below will always return INVALID since it checks
+                    //  that window manager mirroring is not enabled (it is always enabled for MP).
+                    setReviewedConsentSessionLocked(ContentRecordingSession.createDisplaySession(
+                            DEFAULT_DISPLAY));
+                    break;
+                case RECORD_CONTENT_TASK:
+                    setReviewedConsentSessionLocked(ContentRecordingSession.createTaskSession(
+                            mProjectionGrant.getLaunchCookie()));
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Updates the session after the user has reviewed consent. There must be a current session.
+     *
+     * @param session The new session details, or {@code null} to stop recording.
+     */
+    private void setReviewedConsentSessionLocked(@Nullable ContentRecordingSession session) {
+        if (session != null) {
+            session.setWaitingToRecord(false);
+            session.setVirtualDisplayId(mProjectionGrant.mVirtualDisplayId);
+        }
+
+        Slog.v(TAG, "Reusing token: Processed consent so set the session " + session);
+        if (!setContentRecordingSession(session)) {
+            Slog.e(TAG, "Reusing token: Failed to set session for reused consent, so stop");
+            // Do not need to invoke stop; updating the session does it for us.
         }
     }
 
     // TODO(b/261563516): Remove internal method and test aidl directly, here and elsewhere.
     @VisibleForTesting
     MediaProjection createProjectionInternal(int uid, String packageName, int type,
-            boolean isPermanentGrant, UserHandle callingUser,
-            boolean packageAttemptedReusingGrantedConsent) {
+            boolean isPermanentGrant, UserHandle callingUser) {
         MediaProjection projection;
         ApplicationInfo ai;
         try {
@@ -371,6 +482,34 @@
         return projection;
     }
 
+    // TODO(b/261563516): Remove internal method and test aidl directly, here and elsewhere.
+    @VisibleForTesting
+    MediaProjection getProjectionInternal(int uid, String packageName) {
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            // Supposedly the package has re-used the user's consent; confirm the provided details
+            // against the current projection token before re-using the current projection.
+            if (mProjectionGrant == null || mProjectionGrant.mSession == null
+                    || !mProjectionGrant.mSession.isWaitingToRecord()) {
+                Slog.e(TAG, "Reusing token: Not possible to reuse the current projection "
+                        + "instance");
+                return null;
+            }
+                // The package matches, go ahead and re-use the token for this request.
+            if (mProjectionGrant.uid == uid
+                    && Objects.equals(mProjectionGrant.packageName, packageName)) {
+                Slog.v(TAG, "Reusing token: getProjection can reuse the current projection");
+                return mProjectionGrant;
+            } else {
+                Slog.e(TAG, "Reusing token: Not possible to reuse the current projection "
+                        + "instance due to package details mismatching");
+                return null;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+    }
+
     @VisibleForTesting
     MediaProjectionInfo getActiveProjectionInfo() {
         synchronized (mLock) {
@@ -395,6 +534,10 @@
 
     private final class BinderService extends IMediaProjectionManager.Stub {
 
+        BinderService(Context context) {
+            super(PermissionEnforcer.fromContext(context));
+        }
+
         @Override // Binder call
         public boolean hasProjectionPermission(int uid, String packageName) {
             final long token = Binder.clearCallingIdentity();
@@ -424,7 +567,25 @@
             }
             final UserHandle callingUser = Binder.getCallingUserHandle();
             return createProjectionInternal(uid, packageName, type, isPermanentGrant,
-                    callingUser, false);
+                    callingUser);
+        }
+
+        @Override // Binder call
+        @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+        public IMediaProjection getProjection(int uid, String packageName) {
+            getProjection_enforcePermission();
+            if (packageName == null || packageName.isEmpty()) {
+                throw new IllegalArgumentException("package name must not be empty");
+            }
+
+            MediaProjection projection;
+            final long callingToken = Binder.clearCallingIdentity();
+            try {
+                projection = getProjectionInternal(uid, packageName);
+            } finally {
+                Binder.restoreCallingIdentity(callingToken);
+            }
+            return projection;
         }
 
         @Override // Binder call
@@ -562,7 +723,7 @@
         }
 
         @Override
-        public void requestConsentForInvalidProjection(IMediaProjection projection) {
+        public void requestConsentForInvalidProjection(@NonNull IMediaProjection projection) {
             if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
                     != PackageManager.PERMISSION_GRANTED) {
                 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to check if the given"
@@ -577,7 +738,22 @@
             // Remove calling app identity before performing any privileged operations.
             final long token = Binder.clearCallingIdentity();
             try {
-                MediaProjectionManagerService.this.requestConsentForInvalidProjection(projection);
+                MediaProjectionManagerService.this.requestConsentForInvalidProjection();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
+        @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+        public void setUserReviewGrantedConsentResult(@ReviewGrantedConsentResult int consentResult,
+                @Nullable IMediaProjection projection) {
+            setUserReviewGrantedConsentResult_enforcePermission();
+            // Remove calling app identity before performing any privileged operations.
+            final long token = Binder.clearCallingIdentity();
+            try {
+                MediaProjectionManagerService.this.setUserReviewGrantedConsentResult(consentResult,
+                        projection);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -594,7 +770,6 @@
             }
         }
 
-
         private boolean checkPermission(String packageName, String permission) {
             return mContext.getPackageManager().checkPermission(permission, packageName)
                     == PackageManager.PERMISSION_GRANTED;
@@ -630,6 +805,8 @@
         // Set if MediaProjection#createVirtualDisplay has been invoked previously (it
         // should only be called once).
         private int mVirtualDisplayId = INVALID_DISPLAY;
+        // The associated session details already sent to WindowManager.
+        private ContentRecordingSession mSession;
 
         MediaProjection(int type, int uid, String packageName, int targetSdkVersion,
                 boolean isPrivileged) {
@@ -883,6 +1060,18 @@
             }
             synchronized (mLock) {
                 mVirtualDisplayId = displayId;
+
+                // If prior session was does not have a valid display id, then update the display
+                // so recording can start.
+                if (mSession != null && mSession.getVirtualDisplayId() == INVALID_DISPLAY) {
+                    Slog.v(TAG, "Virtual display now created, so update session with the virtual "
+                            + "display id");
+                    mSession.setVirtualDisplayId(mVirtualDisplayId);
+                    if (!setContentRecordingSession(mSession)) {
+                        Slog.e(TAG, "Failed to set session for virtual display id");
+                        // Do not need to invoke stop; updating the session does it for us.
+                    }
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 07891f3..d1d6f5f 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -693,6 +693,7 @@
     private NotificationRecordLogger mNotificationRecordLogger;
     private InstanceIdSequence mNotificationInstanceIdSequence;
     private Set<String> mMsgPkgsAllowedAsConvos = new HashSet();
+    private String mDefaultSearchSelectorPkg;
 
     // Broadcast intent receiver for notification permissions review-related intents
     private ReviewNotificationPermissionsReceiver mReviewNotificationPermissionsReceiver;
@@ -2435,6 +2436,8 @@
 
         mMsgPkgsAllowedAsConvos = Set.of(getStringArrayResource(
                 com.android.internal.R.array.config_notificationMsgPkgsAllowedAsConvos));
+        mDefaultSearchSelectorPkg = getContext().getString(getContext().getResources()
+                .getIdentifier("config_defaultSearchSelectorPackageName", "string", "android"));
 
         mFlagResolver = flagResolver;
 
@@ -6934,7 +6937,12 @@
      */
     private boolean canBeNonDismissible(ApplicationInfo ai, Notification notification) {
         return notification.isMediaNotification() || isEnterpriseExempted(ai)
-                || isCallNotification(ai.packageName, ai.uid, notification);
+                || isCallNotification(ai.packageName, ai.uid, notification)
+                || isDefaultSearchSelectorPackage(ai.packageName);
+    }
+
+    private boolean isDefaultSearchSelectorPackage(String pkg) {
+        return Objects.equals(mDefaultSearchSelectorPkg, pkg);
     }
 
     private boolean isEnterpriseExempted(ApplicationInfo ai) {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index f733199..2460ce5 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -48,6 +48,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
 import android.metrics.LogMaker;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
 import android.os.UserHandle;
@@ -60,6 +61,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IntArray;
+import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
@@ -387,7 +389,8 @@
                 NotificationChannel channel = new NotificationChannel(
                         id, channelName, channelImportance);
                 if (forRestore) {
-                    channel.populateFromXmlForRestore(parser, mContext);
+                    final boolean pkgInstalled = r.uid != UNKNOWN_UID;
+                    channel.populateFromXmlForRestore(parser, pkgInstalled, mContext);
                 } else {
                     channel.populateFromXml(parser);
                 }
@@ -2412,6 +2415,21 @@
                         mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId));
                         synchronized (mPackagePreferences) {
                             mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
+
+                            // Try to restore any unrestored sound resources
+                            for (NotificationChannel channel : r.channels.values()) {
+                                if (!channel.isSoundRestored()) {
+                                    Uri uri = channel.getSound();
+                                    Uri restoredUri = channel.restoreSoundUri(mContext, uri, true);
+                                    if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(
+                                            restoredUri)) {
+                                        Log.w(TAG,
+                                                "Could not restore sound: " + uri + " for channel: "
+                                                        + channel);
+                                    }
+                                    channel.setSound(restoredUri, channel.getAudioAttributes());
+                                }
+                            }
                         }
                         if (r.migrateToPm) {
                             try {
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 064be7c..39cd888 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -745,6 +745,9 @@
         applyPackageFilter(snapshot, remainingPredicate, result, remainingPkgSettings, sortTemp,
                 packageManagerService);
 
+        // Make sure the system server isn't in the result, because it can never be dexopted here.
+        result.removeIf(pkgSetting -> PLATFORM_PACKAGE_NAME.equals(pkgSetting.getPackageName()));
+
         if (debug) {
             Log.i(TAG, "Packages to be dexopted: " + packagesToString(result));
             Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgSettings));
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 006d7c8..29c5ada 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1148,8 +1148,13 @@
             info.userId = userId;
             info.installerPackageName = mInstallSource.mInstallerPackageName;
             info.installerAttributionTag = mInstallSource.mInstallerAttributionTag;
-            info.resolvedBaseCodePath = (mResolvedBaseFile != null) ?
-                    mResolvedBaseFile.getAbsolutePath() : null;
+            if (mContext.checkCallingOrSelfPermission(
+                    Manifest.permission.READ_INSTALLED_SESSION_PATHS)
+                            == PackageManager.PERMISSION_GRANTED && mResolvedBaseFile != null) {
+                info.resolvedBaseCodePath = mResolvedBaseFile.getAbsolutePath();
+            } else {
+                info.resolvedBaseCodePath = null;
+            }
             info.progress = progress;
             info.sealed = mSealed;
             info.isCommitted = isCommitted();
@@ -2754,11 +2759,6 @@
                         : PackageInstaller.ACTION_CONFIRM_INSTALL);
         intent.setPackage(mPm.getPackageInstallerPackageName());
         intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
-        synchronized (mLock) {
-            intent.putExtra(PackageInstaller.EXTRA_RESOLVED_BASE_PATH,
-                    mResolvedBaseFile != null ? mResolvedBaseFile.getAbsolutePath() : null);
-        }
-
         sendOnUserActionRequired(mContext, target, sessionId, intent);
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index eea6720..ef7d413 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -24,6 +24,7 @@
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
 
 import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import android.accounts.IAccountManager;
 import android.annotation.NonNull;
@@ -1954,6 +1955,8 @@
         List<String> packageNames = null;
         if (allPackages) {
             packageNames = mInterface.getAllPackages();
+            // Compiling the system server is only supported from odrefresh, so skip it.
+            packageNames.removeIf(packageName -> PLATFORM_PACKAGE_NAME.equals(packageName));
         } else {
             String packageName = getNextArg();
             if (packageName == null) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index e8f89d3..c54b111 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4922,6 +4922,8 @@
             date.setTime(ps.getLoadingCompletedTime());
             pw.print(prefix); pw.println("  loadingCompletedTime=" + sdf.format(date));
         }
+        pw.print(prefix); pw.print("  appMetadataFilePath=");
+        pw.println(ps.getAppMetadataFilePath());
         if (ps.getVolumeUuid() != null) {
             pw.print(prefix); pw.print("  volumeUuid=");
                     pw.println(ps.getVolumeUuid());
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4094b1a..0532a79 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -710,7 +710,7 @@
                     final int deviceId = msg.arg1;
                     final Long eventTime = (Long) msg.obj;
                     launchAssistAction(null /* hint */, deviceId, eventTime,
-                            AssistUtils.INVOCATION_TYPE_UNKNOWN);
+                            AssistUtils.INVOCATION_TYPE_ASSIST_BUTTON);
                     break;
                 case MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK:
                     launchVoiceAssistWithWakeLock();
@@ -2186,12 +2186,6 @@
                     Intent.EXTRA_DOCK_STATE_UNDOCKED));
         }
 
-        // register for dream-related broadcasts
-        filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_DREAMING_STARTED);
-        filter.addAction(Intent.ACTION_DREAMING_STOPPED);
-        mContext.registerReceiver(mDreamReceiver, filter);
-
         // register for multiuser-relevant broadcasts
         filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
         mContext.registerReceiver(mMultiuserReceiver, filter);
@@ -4576,6 +4570,12 @@
 
             case KeyEvent.KEYCODE_BACK:
                 return mWakeOnBackKeyPress;
+
+            case KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY:
+            case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY:
+            case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY:
+            case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL:
+                return mStylusButtonsEnabled;
         }
 
         return true;
@@ -4779,21 +4779,6 @@
         }
     };
 
-    BroadcastReceiver mDreamReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_DREAMING_STARTED.equals(intent.getAction())) {
-                if (mKeyguardDelegate != null) {
-                    mKeyguardDelegate.onDreamingStarted();
-                }
-            } else if (Intent.ACTION_DREAMING_STOPPED.equals(intent.getAction())) {
-                if (mKeyguardDelegate != null) {
-                    mKeyguardDelegate.onDreamingStopped();
-                }
-            }
-        }
-    };
-
     BroadcastReceiver mMultiuserReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 646dc4e..495e239 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -18,6 +18,7 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.service.dreams.DreamManagerInternal;
 import android.util.Log;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -27,6 +28,7 @@
 import com.android.internal.policy.IKeyguardDrawnCallback;
 import com.android.internal.policy.IKeyguardExitCallback;
 import com.android.internal.policy.IKeyguardService;
+import com.android.server.LocalServices;
 import com.android.server.UiThread;
 import com.android.server.policy.WindowManagerPolicy.OnKeyguardExitResult;
 import com.android.server.wm.EventLogTags;
@@ -60,6 +62,19 @@
 
     private DrawnListener mDrawnListenerWhenConnect;
 
+    private final DreamManagerInternal.DreamManagerStateListener mDreamManagerStateListener =
+            new DreamManagerInternal.DreamManagerStateListener() {
+                @Override
+                public void onDreamingStarted() {
+                    KeyguardServiceDelegate.this.onDreamingStarted();
+                }
+
+                @Override
+                public void onDreamingStopped() {
+                    KeyguardServiceDelegate.this.onDreamingStopped();
+                }
+            };
+
     private static final class KeyguardState {
         KeyguardState() {
             reset();
@@ -158,6 +173,11 @@
         } else {
             if (DEBUG) Log.v(TAG, "*** Keyguard started");
         }
+
+        final DreamManagerInternal dreamManager =
+                LocalServices.getService(DreamManagerInternal.class);
+
+        dreamManager.registerDreamManagerStateListener(mDreamManagerStateListener);
     }
 
     private final ServiceConnection mKeyguardConnection = new ServiceConnection() {
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
index cd1a968..97e4636 100644
--- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
@@ -19,14 +19,18 @@
 import android.content.Context;
 import android.os.Binder;
 import android.os.OutcomeReceiver;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.security.rkp.IGetRegistrationCallback;
 import android.security.rkp.IRemoteProvisioning;
 import android.security.rkp.service.RegistrationProxy;
 import android.util.Log;
 
+import com.android.internal.util.DumpUtils;
 import com.android.server.SystemService;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.time.Duration;
 import java.util.concurrent.Executor;
 
@@ -97,5 +101,18 @@
                 Binder.restoreCallingIdentity(callingIdentity);
             }
         }
+
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
+            new RemoteProvisioningShellCommand().dump(pw);
+        }
+
+        @Override
+        public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out,
+                ParcelFileDescriptor err, String[] args) {
+            return new RemoteProvisioningShellCommand().exec(this, in.getFileDescriptor(),
+                    out.getFileDescriptor(), err.getFileDescriptor(), args);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java
new file mode 100644
index 0000000..71eca69
--- /dev/null
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java
@@ -0,0 +1,250 @@
+/*
+ * 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.security.rkp;
+
+import android.hardware.security.keymint.DeviceInfo;
+import android.hardware.security.keymint.IRemotelyProvisionedComponent;
+import android.hardware.security.keymint.MacedPublicKey;
+import android.hardware.security.keymint.ProtectedData;
+import android.hardware.security.keymint.RpcHardwareInfo;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ShellCommand;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import java.util.Base64;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.Map;
+import co.nstant.in.cbor.model.SimpleValue;
+import co.nstant.in.cbor.model.UnsignedInteger;
+
+class RemoteProvisioningShellCommand extends ShellCommand {
+    private static final String USAGE = "usage: cmd remote_provisioning SUBCOMMAND [ARGS]\n"
+            + "help\n"
+            + "  Show this message.\n"
+            + "dump\n"
+            + "  Dump service diagnostics.\n"
+            + "list [--min-version MIN_VERSION]\n"
+            + "  List the names of the IRemotelyProvisionedComponent instances.\n"
+            + "csr [--challenge CHALLENGE] NAME\n"
+            + "  Generate and print a base64-encoded CSR from the named\n"
+            + "  IRemotelyProvisionedComponent. A base64-encoded challenge can be provided,\n"
+            + "  or else it defaults to an empty challenge.\n";
+
+    @VisibleForTesting
+    static final String EEK_ED25519_BASE64 = "goRDoQEnoFgqpAEBAycgBiFYIJm57t1e5FL2hcZMYtw+YatXSH11N"
+            + "ymtdoAy0rPLY1jZWEAeIghLpLekyNdOAw7+uK8UTKc7b6XN3Np5xitk/pk5r3bngPpmAIUNB5gqrJFcpyUUS"
+            + "QY0dcqKJ3rZ41pJ6wIDhEOhASegWE6lAQECWCDQrsEVyirPc65rzMvRlh1l6LHd10oaN7lDOpfVmd+YCAM4G"
+            + "CAEIVggvoXnRsSjQlpA2TY6phXQLFh+PdwzAjLS/F4ehyVfcmBYQJvPkOIuS6vRGLEOjl0gJ0uEWP78MpB+c"
+            + "gWDvNeCvvpkeC1UEEvAMb9r6B414vAtzmwvT/L1T6XUg62WovGHWAQ=";
+
+    @VisibleForTesting
+    static final String EEK_P256_BASE64 = "goRDoQEmoFhNpQECAyYgASFYIPcUituX9MxT79JkEcTjdR9mH6RxDGzP"
+            + "+glGgHSHVPKtIlggXn9b9uzk9hnM/xM3/Q+hyJPbGAZ2xF3m12p3hsMtr49YQC+XjkL7vgctlUeFR5NAsB/U"
+            + "m0ekxESp8qEHhxDHn8sR9L+f6Dvg5zRMFfx7w34zBfTRNDztAgRgehXgedOK/ySEQ6EBJqBYcaYBAgJYIDVz"
+            + "tz+gioCJsSZn6ct8daGvAmH8bmUDkTvTS30UlD5GAzgYIAEhWCDgQc8vDzQPHDMsQbDP1wwwVTXSHmpHE0su"
+            + "0UiWfiScaCJYIB/ORcX7YbqBIfnlBZubOQ52hoZHuB4vRfHOr9o/gGjbWECMs7p+ID4ysGjfYNEdffCsOI5R"
+            + "vP9s4Wc7Snm8Vnizmdh8igfY2rW1f3H02GvfMyc0e2XRKuuGmZirOrSAqr1Q";
+
+    private static final int ERROR = -1;
+    private static final int SUCCESS = 0;
+
+    private final Injector mInjector;
+
+    RemoteProvisioningShellCommand() {
+        this(new Injector());
+    }
+
+    @VisibleForTesting
+    RemoteProvisioningShellCommand(Injector injector) {
+        mInjector = injector;
+    }
+
+    @Override
+    public void onHelp() {
+        getOutPrintWriter().print(USAGE);
+    }
+
+    @Override
+    @SuppressWarnings("CatchAndPrintStackTrace")
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        try {
+            switch (cmd) {
+                case "list":
+                    return list();
+                case "csr":
+                    return csr();
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        } catch (Exception e) {
+            e.printStackTrace(getErrPrintWriter());
+            return ERROR;
+        }
+    }
+
+    @SuppressWarnings("CatchAndPrintStackTrace")
+    void dump(PrintWriter pw) {
+        try {
+            IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+            for (String name : mInjector.getIrpcNames()) {
+                ipw.println(name + ":");
+                ipw.increaseIndent();
+                dumpRpcInstance(ipw, name);
+                ipw.decreaseIndent();
+            }
+        } catch (Exception e) {
+            e.printStackTrace(pw);
+        }
+    }
+
+    private void dumpRpcInstance(PrintWriter pw, String name) throws RemoteException {
+        RpcHardwareInfo info = mInjector.getIrpcBinder(name).getHardwareInfo();
+        pw.println("hwVersion=" + info.versionNumber);
+        pw.println("rpcAuthorName=" + info.rpcAuthorName);
+        if (info.versionNumber < 3) {
+            pw.println("supportedEekCurve=" + info.supportedEekCurve);
+        }
+        pw.println("uniqueId=" + info.uniqueId);
+        pw.println("supportedNumKeysInCsr=" + info.supportedNumKeysInCsr);
+    }
+
+    private int list() throws RemoteException {
+        for (String name : mInjector.getIrpcNames()) {
+            getOutPrintWriter().println(name);
+        }
+        return SUCCESS;
+    }
+
+    private int csr() throws RemoteException, CborException {
+        byte[] challenge = {};
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--challenge":
+                    challenge = Base64.getDecoder().decode(getNextArgRequired());
+                    break;
+                default:
+                    getErrPrintWriter().println("error: unknown option");
+                    return ERROR;
+            }
+        }
+        String name = getNextArgRequired();
+
+        IRemotelyProvisionedComponent binder = mInjector.getIrpcBinder(name);
+        RpcHardwareInfo info = binder.getHardwareInfo();
+        MacedPublicKey[] emptyKeys = new MacedPublicKey[] {};
+        byte[] csrBytes;
+        switch (info.versionNumber) {
+            case 1:
+            case 2:
+                DeviceInfo deviceInfo = new DeviceInfo();
+                ProtectedData protectedData = new ProtectedData();
+                byte[] eek = getEekChain(info.supportedEekCurve);
+                byte[] keysToSignMac = binder.generateCertificateRequest(
+                        /*testMode=*/false, emptyKeys, eek, challenge, deviceInfo, protectedData);
+                csrBytes = composeCertificateRequestV1(
+                        deviceInfo, challenge, protectedData, keysToSignMac);
+                break;
+            case 3:
+                csrBytes = binder.generateCertificateRequestV2(emptyKeys, challenge);
+                break;
+            default:
+                getErrPrintWriter().println("error: unsupported hwVersion: " + info.versionNumber);
+                return ERROR;
+        }
+        getOutPrintWriter().println(Base64.getEncoder().encodeToString(csrBytes));
+        return SUCCESS;
+    }
+
+    private byte[] getEekChain(int supportedEekCurve) {
+        switch (supportedEekCurve) {
+            case RpcHardwareInfo.CURVE_25519:
+                return Base64.getDecoder().decode(EEK_ED25519_BASE64);
+            case RpcHardwareInfo.CURVE_P256:
+                return Base64.getDecoder().decode(EEK_P256_BASE64);
+            default:
+                throw new IllegalArgumentException("unsupported EEK curve: " + supportedEekCurve);
+        }
+    }
+
+    private byte[] composeCertificateRequestV1(DeviceInfo deviceInfo, byte[] challenge,
+            ProtectedData protectedData, byte[] keysToSignMac) throws CborException {
+        Array info = new Array()
+                .add(decode(deviceInfo.deviceInfo))
+                .add(new Map());
+
+        // COSE_Signature with the hmac-sha256 algorithm and without a payload.
+        Array mac = new Array()
+                .add(new ByteString(encode(
+                            new Map().put(new UnsignedInteger(1), new UnsignedInteger(5)))))
+                .add(new Map())
+                .add(SimpleValue.NULL)
+                .add(new ByteString(keysToSignMac));
+
+        Array csr = new Array()
+                .add(info)
+                .add(new ByteString(challenge))
+                .add(decode(protectedData.protectedData))
+                .add(mac);
+
+        return encode(csr);
+    }
+
+    private byte[] encode(DataItem item) throws CborException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        new CborEncoder(baos).encode(item);
+        return baos.toByteArray();
+    }
+
+    private DataItem decode(byte[] data) throws CborException {
+        ByteArrayInputStream bais = new ByteArrayInputStream(data);
+        return new CborDecoder(bais).decodeNext();
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        String[] getIrpcNames() {
+            return ServiceManager.getDeclaredInstances(IRemotelyProvisionedComponent.DESCRIPTOR);
+        }
+
+        IRemotelyProvisionedComponent getIrpcBinder(String name) {
+            String irpc = IRemotelyProvisionedComponent.DESCRIPTOR + "/" + name;
+            IRemotelyProvisionedComponent binder =
+                    IRemotelyProvisionedComponent.Stub.asInterface(
+                            ServiceManager.waitForDeclaredService(irpc));
+            if (binder == null) {
+                throw new IllegalArgumentException("failed to find " + irpc);
+            }
+            return binder;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index fb400da..80cb085 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -2340,6 +2340,10 @@
 
         @Override
         public void binderDied() {
+            synchronized (mLock) {
+                mSession = null;
+                clearSessionAndNotifyClientLocked(this);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
index d844c6f..9647a62 100644
--- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -84,6 +84,7 @@
             PERMISSION_POLICY_ORDERED_ID,
             VIRTUAL_DEVICE_SERVICE_ORDERED_ID,
             DREAM_MANAGER_ORDERED_ID,
+            PRODUCT_ORDERED_ID,
             SYSTEM_LAST_ORDERED_ID, // Update this when adding new ids
             // Order Ids for mainline module services
             MAINLINE_FIRST_ORDERED_ID,
@@ -119,11 +120,18 @@
     int DREAM_MANAGER_ORDERED_ID = 4;
 
     /**
+     * The identifier for an interceptor which is specific to the type of android product like
+     * automotive, wear, TV etc.
+     * @hide
+     */
+    int PRODUCT_ORDERED_ID = 5;
+
+    /**
      * The final id, used by the framework to determine the valid range of ids. Update this when
      * adding new ids.
      * @hide
      */
-    int SYSTEM_LAST_ORDERED_ID = DREAM_MANAGER_ORDERED_ID;
+    int SYSTEM_LAST_ORDERED_ID = PRODUCT_ORDERED_ID;
 
     /**
      * The first mainline module id, used by the framework to determine the valid range of ids
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c6a2e0e..bc3a1a2 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -10551,8 +10551,8 @@
     }
 
     @Override
-    boolean isSyncFinished() {
-        if (!super.isSyncFinished()) return false;
+    boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
+        if (!super.isSyncFinished(group)) return false;
         if (mDisplayContent != null && mDisplayContent.mUnknownAppVisibilityController
                 .isVisibilityUnknown(this)) {
             return false;
@@ -10572,11 +10572,14 @@
     }
 
     @Override
-    void finishSync(Transaction outMergedTransaction, boolean cancel) {
+    void finishSync(Transaction outMergedTransaction, BLASTSyncEngine.SyncGroup group,
+            boolean cancel) {
         // This override is just for getting metrics. allFinished needs to be checked before
         // finish because finish resets all the states.
+        final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup();
+        if (syncGroup != null && group != getSyncGroup()) return;
         mLastAllReadyAtSync = allSyncFinished();
-        super.finishSync(outMergedTransaction, cancel);
+        super.finishSync(outMergedTransaction, group, cancel);
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f93afe8..ea0731a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1996,6 +1996,24 @@
         }
     }
 
+    @Override
+    public void focusTopTask(int displayId) {
+        enforceTaskPermission("focusTopTask()");
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId);
+                if (dc == null) return;
+                final Task task = dc.getTask((t) -> t.isLeafTask() && t.isFocusable(),
+                        true /*  traverseTopToBottom */);
+                if (task == null) return;
+                setFocusedTask(task.mTaskId, null /* touchedActivity */);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
     void setFocusedTask(int taskId, ActivityRecord touchedActivity) {
         ProtoLog.d(WM_DEBUG_FOCUS, "setFocusedTask: taskId=%d touchedActivity=%s", taskId,
                 touchedActivity);
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 7ecc083..778951a 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -27,7 +27,6 @@
 import android.os.Trace;
 import android.util.ArraySet;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.view.SurfaceControl;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -61,6 +60,26 @@
  * This works primarily by setting-up state and then watching/waiting for the registered subtrees
  * to enter into a "finished" state (either by receiving drawn content or by disappearing). This
  * checks the subtrees during surface-placement.
+ *
+ * By default, all Syncs will be serialized (and it is an error to start one while another is
+ * active). However, a sync can be explicitly started in "parallel". This does not guarantee that
+ * it will run in parallel; however, it will run in parallel as long as it's watched hierarchy
+ * doesn't overlap with any other syncs' watched hierarchies.
+ *
+ * Currently, a sync that is started as "parallel" implicitly ignores the subtree below it's
+ * direct members unless those members are activities (WindowStates are considered "part of" the
+ * activity). This allows "stratified" parallelism where, eg, a sync that is only at Task-level
+ * can run in parallel with another sync that includes only the task's activities.
+ *
+ * If, at any time, a container is added to a parallel sync that *is* watched by another sync, it
+ * will be forced to serialize with it. This is done by adding a dependency. A sync will only
+ * finish if it has no active dependencies. At this point it is effectively not parallel anymore.
+ *
+ * To avoid dependency cycles, if a sync B ultimately depends on a sync A and a container is added
+ * to A which is watched by B, that container will, instead, be moved from B to A instead of
+ * creating a cyclic dependency.
+ *
+ * When syncs overlap, this will attempt to finish everything in the order they were started.
  */
 class BLASTSyncEngine {
     private static final String TAG = "BLASTSyncEngine";
@@ -104,6 +123,18 @@
         private SurfaceControl.Transaction mOrphanTransaction = null;
         private String mTraceName;
 
+        private static final ArrayList<SyncGroup> NO_DEPENDENCIES = new ArrayList<>();
+
+        /**
+         * When `true`, this SyncGroup will only wait for mRootMembers to draw; otherwise,
+         * it waits for the whole subtree(s) rooted at the mRootMembers.
+         */
+        boolean mIgnoreIndirectMembers = false;
+
+        /** List of SyncGroups that must finish before this one can. */
+        @NonNull
+        ArrayList<SyncGroup> mDependencies = NO_DEPENDENCIES;
+
         private SyncGroup(TransactionReadyListener listener, int id, String name) {
             mSyncId = id;
             mListener = listener;
@@ -133,19 +164,43 @@
             return mOrphanTransaction;
         }
 
-        private void tryFinish() {
-            if (!mReady) return;
+        /**
+         * Check if the sync-group ignores a particular container. This is used to allow syncs at
+         * different levels to run in parallel. The primary example is Recents while an activity
+         * sync is happening.
+         */
+        boolean isIgnoring(WindowContainer wc) {
+            // Some heuristics to avoid unnecessary work:
+            // 1. For now, require an explicit acknowledgement of potential "parallelism" across
+            //    hierarchy levels (horizontal).
+            if (!mIgnoreIndirectMembers) return false;
+            // 2. Don't check WindowStates since they are below the relevant abstraction level (
+            //    anything activity/token and above).
+            if (wc.asWindowState() != null) return false;
+            // Obviously, don't ignore anything that is directly part of this group.
+            return wc.mSyncGroup != this;
+        }
+
+        /** @return `true` if it finished. */
+        private boolean tryFinish() {
+            if (!mReady) return false;
             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: onSurfacePlacement checking %s",
                     mSyncId, mRootMembers);
+            if (!mDependencies.isEmpty()) {
+                ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d:  Unfinished dependencies: %s",
+                        mSyncId, mDependencies);
+                return false;
+            }
             for (int i = mRootMembers.size() - 1; i >= 0; --i) {
                 final WindowContainer wc = mRootMembers.valueAt(i);
-                if (!wc.isSyncFinished()) {
+                if (!wc.isSyncFinished(this)) {
                     ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d:  Unfinished container: %s",
                             mSyncId, wc);
-                    return;
+                    return false;
                 }
             }
             finishNow();
+            return true;
         }
 
         private void finishNow() {
@@ -158,7 +213,7 @@
                 merged.merge(mOrphanTransaction);
             }
             for (WindowContainer wc : mRootMembers) {
-                wc.finishSync(merged, false /* cancel */);
+                wc.finishSync(merged, this, false /* cancel */);
             }
 
             final ArraySet<WindowContainer> wcAwaitingCommit = new ArraySet<>();
@@ -204,7 +259,7 @@
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady");
             mListener.onTransactionReady(mSyncId, merged);
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-            mActiveSyncs.remove(mSyncId);
+            mActiveSyncs.remove(this);
             mHandler.removeCallbacks(mOnTimeout);
 
             // Immediately start the next pending sync-transaction if there is one.
@@ -230,54 +285,115 @@
             }
         }
 
-        private void setReady(boolean ready) {
+        /** returns true if readiness changed. */
+        private boolean setReady(boolean ready) {
             if (mReady == ready) {
-                return;
+                return false;
             }
             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready %b", mSyncId, ready);
             mReady = ready;
-            if (!ready) return;
-            mWm.mWindowPlacerLocked.requestTraversal();
+            if (ready) {
+                mWm.mWindowPlacerLocked.requestTraversal();
+            }
+            return true;
         }
 
         private void addToSync(WindowContainer wc) {
-            if (!mRootMembers.add(wc)) {
+            if (mRootMembers.contains(wc)) {
                 return;
             }
             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Adding to group: %s", mSyncId, wc);
-            wc.setSyncGroup(this);
+            final SyncGroup dependency = wc.getSyncGroup();
+            if (dependency != null && dependency != this && !dependency.isIgnoring(wc)) {
+                // This syncgroup now conflicts with another one, so the whole group now must
+                // wait on the other group.
+                Slog.w(TAG, "SyncGroup " + mSyncId + " conflicts with " + dependency.mSyncId
+                        + ": Making " + mSyncId + " depend on " + dependency.mSyncId);
+                if (mDependencies.contains(dependency)) {
+                    // nothing, it's already a dependency.
+                } else if (dependency.dependsOn(this)) {
+                    Slog.w(TAG, " Detected dependency cycle between " + mSyncId + " and "
+                            + dependency.mSyncId + ": Moving " + wc + " to " + mSyncId);
+                    // Since dependency already depends on this, make this now `wc`'s watcher
+                    if (wc.mSyncGroup == null) {
+                        wc.setSyncGroup(this);
+                    } else {
+                        // Explicit replacement.
+                        wc.mSyncGroup.mRootMembers.remove(wc);
+                        mRootMembers.add(wc);
+                        wc.mSyncGroup = this;
+                    }
+                } else {
+                    if (mDependencies == NO_DEPENDENCIES) {
+                        mDependencies = new ArrayList<>();
+                    }
+                    mDependencies.add(dependency);
+                }
+            } else {
+                mRootMembers.add(wc);
+                wc.setSyncGroup(this);
+            }
             wc.prepareSync();
             if (mReady) {
                 mWm.mWindowPlacerLocked.requestTraversal();
             }
         }
 
+        private boolean dependsOn(SyncGroup group) {
+            if (mDependencies.isEmpty()) return false;
+            // BFS search with membership check. We don't expect cycle here (since this is
+            // explicitly called to avoid cycles) but just to be safe.
+            final ArrayList<SyncGroup> fringe = mTmpFringe;
+            fringe.clear();
+            fringe.add(this);
+            for (int head = 0; head < fringe.size(); ++head) {
+                final SyncGroup next = fringe.get(head);
+                if (next == group) {
+                    fringe.clear();
+                    return true;
+                }
+                for (int i = 0; i < next.mDependencies.size(); ++i) {
+                    if (fringe.contains(next.mDependencies.get(i))) continue;
+                    fringe.add(next.mDependencies.get(i));
+                }
+            }
+            fringe.clear();
+            return false;
+        }
+
         void onCancelSync(WindowContainer wc) {
             mRootMembers.remove(wc);
         }
 
         private void onTimeout() {
-            if (!mActiveSyncs.contains(mSyncId)) return;
+            if (!mActiveSyncs.contains(this)) return;
             boolean allFinished = true;
             for (int i = mRootMembers.size() - 1; i >= 0; --i) {
                 final WindowContainer<?> wc = mRootMembers.valueAt(i);
-                if (!wc.isSyncFinished()) {
+                if (!wc.isSyncFinished(this)) {
                     allFinished = false;
                     Slog.i(TAG, "Unfinished container: " + wc);
                 }
             }
+            for (int i = mDependencies.size() - 1; i >= 0; --i) {
+                allFinished = false;
+                Slog.i(TAG, "Unfinished dependency: " + mDependencies.get(i).mSyncId);
+            }
             if (allFinished && !mReady) {
                 Slog.w(TAG, "Sync group " + mSyncId + " timed-out because not ready. If you see "
                         + "this, please file a bug.");
             }
             finishNow();
+            removeFromDependencies(this);
         }
     }
 
     private final WindowManagerService mWm;
     private final Handler mHandler;
     private int mNextSyncId = 0;
-    private final SparseArray<SyncGroup> mActiveSyncs = new SparseArray<>();
+
+    /** Currently active syncs. Intentionally ordered by start time. */
+    private final ArrayList<SyncGroup> mActiveSyncs = new ArrayList<>();
 
     /**
      * A queue of pending sync-sets waiting for their turn to run.
@@ -288,6 +404,9 @@
 
     private final ArrayList<Runnable> mOnIdleListeners = new ArrayList<>();
 
+    private final ArrayList<SyncGroup> mTmpFinishQueue = new ArrayList<>();
+    private final ArrayList<SyncGroup> mTmpFringe = new ArrayList<>();
+
     BLASTSyncEngine(WindowManagerService wms) {
         this(wms, wms.mH);
     }
@@ -306,32 +425,39 @@
         return new SyncGroup(listener, mNextSyncId++, name);
     }
 
-    int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name) {
+    int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name,
+            boolean parallel) {
         final SyncGroup s = prepareSyncSet(listener, name);
-        startSyncSet(s, timeoutMs);
+        startSyncSet(s, timeoutMs, parallel);
         return s.mSyncId;
     }
 
     void startSyncSet(SyncGroup s) {
-        startSyncSet(s, BLAST_TIMEOUT_DURATION);
+        startSyncSet(s, BLAST_TIMEOUT_DURATION, false /* parallel */);
     }
 
-    void startSyncSet(SyncGroup s, long timeoutMs) {
-        if (mActiveSyncs.size() != 0) {
-            // We currently only support one sync at a time, so start a new SyncGroup when there is
-            // another may cause issue.
+    void startSyncSet(SyncGroup s, long timeoutMs, boolean parallel) {
+        final boolean alreadyRunning = mActiveSyncs.size() > 0;
+        if (!parallel && alreadyRunning) {
+            // We only support overlapping syncs when explicitly declared `parallel`.
             Slog.e(TAG, "SyncGroup " + s.mSyncId
                     + ": Started when there is other active SyncGroup");
         }
-        mActiveSyncs.put(s.mSyncId, s);
-        ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s",
-                s.mSyncId, s.mListener);
+        mActiveSyncs.add(s);
+        // For now, parallel implies this.
+        s.mIgnoreIndirectMembers = parallel;
+        ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started %sfor listener: %s",
+                s.mSyncId, (parallel && alreadyRunning ? "(in parallel) " : ""), s.mListener);
         scheduleTimeout(s, timeoutMs);
     }
 
     @Nullable
     SyncGroup getSyncSet(int id) {
-        return mActiveSyncs.get(id);
+        for (int i = 0; i < mActiveSyncs.size(); ++i) {
+            if (mActiveSyncs.get(i).mSyncId != id) continue;
+            return mActiveSyncs.get(i);
+        }
+        return null;
     }
 
     boolean hasActiveSync() {
@@ -356,8 +482,8 @@
         syncGroup.mSyncMethod = method;
     }
 
-    void setReady(int id, boolean ready) {
-        getSyncGroup(id).setReady(ready);
+    boolean setReady(int id, boolean ready) {
+        return getSyncGroup(id).setReady(ready);
     }
 
     void setReady(int id) {
@@ -372,21 +498,68 @@
      * Aborts the sync (ie. it doesn't wait for ready or anything to finish)
      */
     void abort(int id) {
-        getSyncGroup(id).finishNow();
+        final SyncGroup group = getSyncGroup(id);
+        group.finishNow();
+        removeFromDependencies(group);
     }
 
     private SyncGroup getSyncGroup(int id) {
-        final SyncGroup syncGroup = mActiveSyncs.get(id);
+        final SyncGroup syncGroup = getSyncSet(id);
         if (syncGroup == null) {
             throw new IllegalStateException("SyncGroup is not started yet id=" + id);
         }
         return syncGroup;
     }
 
+    /**
+     * Just removes `group` from any dependency lists. Does not try to evaluate anything. However,
+     * it will schedule traversals if any groups were changed in a way that could make them ready.
+     */
+    private void removeFromDependencies(SyncGroup group) {
+        boolean anyChange = false;
+        for (int i = 0; i < mActiveSyncs.size(); ++i) {
+            final SyncGroup active = mActiveSyncs.get(i);
+            if (!active.mDependencies.remove(group)) continue;
+            if (!active.mDependencies.isEmpty()) continue;
+            anyChange = true;
+        }
+        if (!anyChange) return;
+        mWm.mWindowPlacerLocked.requestTraversal();
+    }
+
     void onSurfacePlacement() {
-        // backwards since each state can remove itself if finished
-        for (int i = mActiveSyncs.size() - 1; i >= 0; --i) {
-            mActiveSyncs.valueAt(i).tryFinish();
+        if (mActiveSyncs.isEmpty()) return;
+        // queue in-order since we want interdependent syncs to become ready in the same order they
+        // started in.
+        mTmpFinishQueue.addAll(mActiveSyncs);
+        // There shouldn't be any dependency cycles or duplicates, but add an upper-bound just
+        // in case. Assuming absolute worst case, each visit will try and revisit everything
+        // before it, so n + (n-1) + (n-2) ... = (n+1)*n/2
+        int visitBounds = ((mActiveSyncs.size() + 1) * mActiveSyncs.size()) / 2;
+        while (!mTmpFinishQueue.isEmpty()) {
+            if (visitBounds <= 0) {
+                Slog.e(TAG, "Trying to finish more syncs than theoretically possible. This "
+                        + "should never happen. Most likely a dependency cycle wasn't detected.");
+            }
+            --visitBounds;
+            final SyncGroup group = mTmpFinishQueue.remove(0);
+            final int grpIdx = mActiveSyncs.indexOf(group);
+            // Skip if it's already finished:
+            if (grpIdx < 0) continue;
+            if (!group.tryFinish()) continue;
+            // Finished, so update dependencies of any prior groups and retry if unblocked.
+            int insertAt = 0;
+            for (int i = 0; i < mActiveSyncs.size(); ++i) {
+                final SyncGroup active = mActiveSyncs.get(i);
+                if (!active.mDependencies.remove(group)) continue;
+                // Anything afterwards is already in queue.
+                if (i >= grpIdx) continue;
+                if (!active.mDependencies.isEmpty()) continue;
+                // `active` became unblocked so it can finish, since it started earlier, it should
+                // be checked next to maintain order.
+                mTmpFinishQueue.add(insertAt, mActiveSyncs.get(i));
+                insertAt += 1;
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index a41dcc6..040da88 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -80,7 +80,7 @@
         }
         // Invalid scenario: ignore identical incoming session.
         if (ContentRecordingSession.isProjectionOnSameDisplay(mSession, incomingSession)) {
-            // TODO(242833866) if incoming session is no longer waiting to record, allow
+            // TODO(242833866): if incoming session is no longer waiting to record, allow
             //  the update through.
 
             ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
@@ -99,7 +99,7 @@
             incomingDisplayContent = wmService.mRoot.getDisplayContentOrCreate(
                     incomingSession.getVirtualDisplayId());
             incomingDisplayContent.setContentRecordingSession(incomingSession);
-            // TODO(b/270118861) When user grants consent to re-use, explicitly ask ContentRecorder
+            // TODO(b/270118861): When user grants consent to re-use, explicitly ask ContentRecorder
             //  to update, since no config/display change arrives. Mark recording as black.
         }
         // Takeover and stopping scenario: stop recording on the pre-existing session.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 76d6951..f478e9b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1755,7 +1755,7 @@
     }
 
     @Override
-    boolean isSyncFinished() {
+    boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
         // Do not consider children because if they are requested to be synced, they should be
         // added to sync group explicitly.
         return !mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 3f7ab14..c6c3b14 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2547,8 +2547,8 @@
     }
 
     @Override
-    boolean isSyncFinished() {
-        return super.isSyncFinished() && isReadyToTransit();
+    boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
+        return super.isSyncFinished(group) && isReadyToTransit();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 452bd6d..b314ed1 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -447,7 +447,7 @@
             throw new IllegalStateException("Attempting to re-use a transition");
         }
         mState = STATE_COLLECTING;
-        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG);
+        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG, false /* parallel */);
         mSyncEngine.setSyncMethod(mSyncId, TransitionController.SYNC_METHOD);
 
         mLogger.mSyncId = mSyncId;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 0ae9c4c..e8e4792 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -192,6 +192,8 @@
 
     private void detachPlayer() {
         if (mTransitionPlayer == null) return;
+        // Immediately set to null so that nothing inadvertently starts/queues.
+        mTransitionPlayer = null;
         // Clean-up/finish any playing transitions.
         for (int i = 0; i < mPlayingTransitions.size(); ++i) {
             mPlayingTransitions.get(i).cleanUpOnFailure();
@@ -200,7 +202,6 @@
         if (mCollectingTransition != null) {
             mCollectingTransition.abort();
         }
-        mTransitionPlayer = null;
         mTransitionPlayerProc = null;
         mRemotePlayer.clear();
         mRunningLock.doNotifyLocked();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index cf6efd2..f4a1765d 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3835,13 +3835,11 @@
 
     void setSyncGroup(@NonNull BLASTSyncEngine.SyncGroup group) {
         ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "setSyncGroup #%d on %s", group.mSyncId, this);
-        if (group != null) {
-            if (mSyncGroup != null && mSyncGroup != group) {
-                // This can still happen if WMCore starts a new transition when there is ongoing
-                // sync transaction from Shell. Please file a bug if it happens.
-                throw new IllegalStateException("Can't sync on 2 engines simultaneously"
-                        + " currentSyncId=" + mSyncGroup.mSyncId + " newSyncId=" + group.mSyncId);
-            }
+        if (mSyncGroup != null && mSyncGroup != group) {
+            // This can still happen if WMCore starts a new transition when there is ongoing
+            // sync transaction from Shell. Please file a bug if it happens.
+            throw new IllegalStateException("Can't sync on 2 groups simultaneously"
+                    + " currentSyncId=" + mSyncGroup.mSyncId + " newSyncId=" + group.mSyncId);
         }
         mSyncGroup = group;
     }
@@ -3883,12 +3881,16 @@
      * @param cancel If true, this is being finished because it is leaving the sync group rather
      *               than due to the sync group completing.
      */
-    void finishSync(Transaction outMergedTransaction, boolean cancel) {
+    void finishSync(Transaction outMergedTransaction, BLASTSyncEngine.SyncGroup group,
+            boolean cancel) {
         if (mSyncState == SYNC_STATE_NONE) return;
+        final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup();
+        // If it's null, then we need to clean-up anyways.
+        if (syncGroup != null && group != syncGroup) return;
         ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "finishSync cancel=%b for %s", cancel, this);
         outMergedTransaction.merge(mSyncTransaction);
         for (int i = mChildren.size() - 1; i >= 0; --i) {
-            mChildren.get(i).finishSync(outMergedTransaction, cancel);
+            mChildren.get(i).finishSync(outMergedTransaction, group, cancel);
         }
         if (cancel && mSyncGroup != null) mSyncGroup.onCancelSync(this);
         mSyncState = SYNC_STATE_NONE;
@@ -3903,7 +3905,7 @@
      *
      * @return {@code true} if this subtree is finished waiting for sync participants.
      */
-    boolean isSyncFinished() {
+    boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
         if (!isVisibleRequested()) {
             return true;
         }
@@ -3917,7 +3919,7 @@
         // Loop from top-down.
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowContainer child = mChildren.get(i);
-            final boolean childFinished = child.isSyncFinished();
+            final boolean childFinished = group.isIgnoring(child) || child.isSyncFinished(group);
             if (childFinished && child.isVisibleRequested() && child.fillsParent()) {
                 // Any lower children will be covered-up, so we can consider this finished.
                 return true;
@@ -3968,11 +3970,11 @@
                 // This is getting removed.
                 if (oldParent.mSyncState != SYNC_STATE_NONE) {
                     // In order to keep the transaction in sync, merge it into the parent.
-                    finishSync(oldParent.mSyncTransaction, true /* cancel */);
+                    finishSync(oldParent.mSyncTransaction, getSyncGroup(), true /* cancel */);
                 } else if (mSyncGroup != null) {
                     // This is watched directly by the sync-group, so merge this transaction into
                     // into the sync-group so it isn't lost
-                    finishSync(mSyncGroup.getOrphanTransaction(), true /* cancel */);
+                    finishSync(mSyncGroup.getOrphanTransaction(), mSyncGroup, true /* cancel */);
                 } else {
                     throw new IllegalStateException("This container is in sync mode without a sync"
                             + " group: " + this);
@@ -3981,7 +3983,7 @@
             } else if (mSyncGroup == null) {
                 // This is being reparented out of the sync-group. To prevent ordering issues on
                 // this container, immediately apply/cancel sync on it.
-                finishSync(getPendingTransaction(), true /* cancel */);
+                finishSync(getPendingTransaction(), getSyncGroup(), true /* cancel */);
                 return;
             }
             // Otherwise this is the "root" of a synced subtree, so continue on to preparation.
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 85c601f..dbd9e4b 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -107,6 +107,12 @@
     private static final String TAG_RELEASE = TAG + POSTFIX_RELEASE;
     private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
 
+    private static final int MAX_RAPID_ACTIVITY_LAUNCH_COUNT = 500;
+    private static final long RAPID_ACTIVITY_LAUNCH_MS = 300;
+    private static final long RESET_RAPID_ACTIVITY_LAUNCH_MS = 5 * RAPID_ACTIVITY_LAUNCH_MS;
+
+    private int mRapidActivityLaunchCount;
+
     // all about the first app in the process
     final ApplicationInfo mInfo;
     final String mName;
@@ -538,7 +544,8 @@
         return mLastActivityLaunchTime > 0;
     }
 
-    void setLastActivityLaunchTime(long launchTime) {
+    void setLastActivityLaunchTime(ActivityRecord r) {
+        long launchTime = r.lastLaunchTime;
         if (launchTime <= mLastActivityLaunchTime) {
             if (launchTime < mLastActivityLaunchTime) {
                 Slog.w(TAG,
@@ -547,9 +554,29 @@
             }
             return;
         }
+        updateRapidActivityLaunch(r, launchTime, mLastActivityLaunchTime);
         mLastActivityLaunchTime = launchTime;
     }
 
+    void updateRapidActivityLaunch(ActivityRecord r, long launchTime, long lastLaunchTime) {
+        if (mInstrumenting || mDebugging || lastLaunchTime <= 0) {
+            return;
+        }
+
+        final long diff = lastLaunchTime - launchTime;
+        if (diff < RAPID_ACTIVITY_LAUNCH_MS) {
+            mRapidActivityLaunchCount++;
+        } else if (diff >= RESET_RAPID_ACTIVITY_LAUNCH_MS) {
+            mRapidActivityLaunchCount = 0;
+        }
+
+        if (mRapidActivityLaunchCount > MAX_RAPID_ACTIVITY_LAUNCH_COUNT) {
+            Slog.w(TAG, "Killing " + mPid + " because of rapid activity launch");
+            r.getRootTask().moveTaskToBack(r.getTask());
+            mAtm.mH.post(() -> mAtm.mAmInternal.killProcess(mName, mUid, "rapidActivityLaunch"));
+        }
+    }
+
     void setLastActivityFinishTimeIfNeeded(long finishTime) {
         if (finishTime <= mLastActivityFinishTime || !hasActivityInVisibleTask()) {
             return;
@@ -696,7 +723,7 @@
 
     void addActivityIfNeeded(ActivityRecord r) {
         // even if we already track this activity, note down that it has been launched
-        setLastActivityLaunchTime(r.lastLaunchTime);
+        setLastActivityLaunchTime(r);
         if (mActivities.contains(r)) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a299592..2920652 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5634,7 +5634,7 @@
     private void dropBufferFrom(Transaction t) {
         SurfaceControl viewSurface = getClientViewRootSurface();
         if (viewSurface == null) return;
-        t.setBuffer(viewSurface, (android.hardware.HardwareBuffer) null);
+        t.unsetBuffer(viewSurface);
     }
 
     @Override
@@ -5678,7 +5678,7 @@
     }
 
     @Override
-    boolean isSyncFinished() {
+    boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
         if (!isVisibleRequested() || isFullyTransparent()) {
             // Don't wait for invisible windows. However, we don't alter the state in case the
             // window becomes visible while the sync group is still active.
@@ -5689,11 +5689,14 @@
             // Complete the sync state immediately for a drawn window that doesn't need to redraw.
             onSyncFinishedDrawing();
         }
-        return super.isSyncFinished();
+        return super.isSyncFinished(group);
     }
 
     @Override
-    void finishSync(Transaction outMergedTransaction, boolean cancel) {
+    void finishSync(Transaction outMergedTransaction, BLASTSyncEngine.SyncGroup group,
+            boolean cancel) {
+        final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup();
+        if (syncGroup != null && group != syncGroup) return;
         mPrepareSyncSeqId = 0;
         if (cancel) {
             // This is leaving sync so any buffers left in the sync have a chance of
@@ -5701,7 +5704,7 @@
             // window. To prevent this, drop the buffer.
             dropBufferFrom(mSyncTransaction);
         }
-        super.finishSync(outMergedTransaction, cancel);
+        super.finishSync(outMergedTransaction, group, cancel);
     }
 
     boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction, int syncSeqId) {
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 2584b86..d9acf41 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -27,3 +27,4 @@
 per-file com_android_server_tv_* = file:/media/java/android/media/tv/OWNERS
 per-file com_android_server_vibrator_* = file:/services/core/java/com/android/server/vibrator/OWNERS
 per-file com_android_server_am_CachedAppOptimizer.cpp = timmurray@google.com, edgararriaga@google.com, dualli@google.com, carmenjackson@google.com, philipcuadra@google.com
+per-file com_android_server_companion_virtual_InputController.cpp = file:/services/companion/java/com/android/server/companion/virtual/OWNERS
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 981844c..f96ca58 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -153,12 +153,6 @@
                 <xs:annotation name="nullable"/>
                 <xs:annotation name="final"/>
             </xs:element>
-            <!-- The highest (most severe) thermal status at which high-brightness-mode is allowed
-                 to operate. -->
-            <xs:element name="thermalStatusLimit" type="thermalStatus" minOccurs="0" maxOccurs="1">
-                <xs:annotation name="nonnull"/>
-                <xs:annotation name="final"/>
-            </xs:element>
             <xs:element name="allowInLowPowerMode" type="xs:boolean" minOccurs="0" maxOccurs="1">
                 <xs:annotation name="nonnull"/>
                 <xs:annotation name="final"/>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 8cb4837..ad6434e 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -156,7 +156,6 @@
     method @NonNull public final java.math.BigDecimal getMinimumLux_all();
     method @Nullable public final com.android.server.display.config.RefreshRateRange getRefreshRate_all();
     method @Nullable public final com.android.server.display.config.SdrHdrRatioMap getSdrHdrRatioMap_all();
-    method @NonNull public final com.android.server.display.config.ThermalStatus getThermalStatusLimit_all();
     method public com.android.server.display.config.HbmTiming getTiming_all();
     method @NonNull public final java.math.BigDecimal getTransitionPoint_all();
     method public final void setAllowInLowPowerMode_all(@NonNull boolean);
@@ -165,7 +164,6 @@
     method public final void setMinimumLux_all(@NonNull java.math.BigDecimal);
     method public final void setRefreshRate_all(@Nullable com.android.server.display.config.RefreshRateRange);
     method public final void setSdrHdrRatioMap_all(@Nullable com.android.server.display.config.SdrHdrRatioMap);
-    method public final void setThermalStatusLimit_all(@NonNull com.android.server.display.config.ThermalStatus);
     method public void setTiming_all(com.android.server.display.config.HbmTiming);
     method public final void setTransitionPoint_all(@NonNull java.math.BigDecimal);
   }
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index f39de43..0271727 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
-import android.credentials.CredentialOption;
 import android.credentials.CredentialProviderInfo;
 import android.credentials.GetCredentialException;
 import android.credentials.GetCredentialRequest;
@@ -36,7 +35,6 @@
 
 import java.util.ArrayList;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 /**
  * Central session for a single getCredentials request. This class listens to the
@@ -56,11 +54,7 @@
         super(context, sessionCallback, lock, userId, callingUid, request, callback,
                 RequestInfo.TYPE_GET, callingAppInfo, enabledProviders, cancellationSignal,
                 startedTimestamp);
-        int numTypes = (request.getCredentialOptions().stream()
-                .map(CredentialOption::getType).collect(
-                        Collectors.toSet())).size(); // Dedupe type strings
-        mRequestSessionMetric.collectGetFlowInitialMetricInfo(numTypes,
-                /*origin=*/request.getOrigin() != null);
+        mRequestSessionMetric.collectGetFlowInitialMetricInfo(request);
     }
 
     /**
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 47b45ac..50e5163 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -50,6 +50,8 @@
     public static final int UNIT = 1;
     // Used for zero count metric emits, such as zero amounts of various types
     public static final int ZERO = 0;
+    // The number of characters at the end of the string to use as a key
+    public static final int DELTA_CUT = 20;
 
     /**
      * This retrieves the uid of any package name, given a context and a component name for the
@@ -87,6 +89,18 @@
     }
 
     /**
+     * Given the current design, we can designate how the strings in the backend should appear.
+     * This helper method lets us cut strings for our class types.
+     *
+     * @param classtype the classtype string we want to cut to generate a key
+     * @param deltaFromEnd the starting point from the end of the string we wish to begin at
+     * @return the cut up string key we want to use for metric logs
+     */
+    public static String generateMetricKey(String classtype, int deltaFromEnd) {
+        return classtype.substring(classtype.length() - deltaFromEnd);
+    }
+
+    /**
      * A logging utility used primarily for the final phase of the current metric setup.
      *
      * @param finalPhaseMetric     the coalesced data of the chosen provider
@@ -158,13 +172,17 @@
     }
 
     /**
-     * A logging utility used primarily for the candidate phase of the current metric setup.
+     * A logging utility used primarily for the candidate phase of the current metric setup. This
+     * will primarily focus on track 2, where the session id is associated with known providers,
+     * but NOT the calling app.
      *
      * @param providers      a map with known providers and their held metric objects
      * @param emitSequenceId an emitted sequence id for the current session
+     * @param initialPhaseMetric contains initial phase data to avoid repetition for candidate
+     *                           phase, track 2, logging
      */
     public static void logApiCalledCandidatePhase(Map<String, ProviderSession> providers,
-            int emitSequenceId) {
+            int emitSequenceId, InitialPhaseMetric initialPhaseMetric) {
         try {
             if (!LOG_FLAG) {
                 return;
@@ -184,6 +202,7 @@
             int[] candidateActionEntryCountList = new int[providerSize];
             int[] candidateAuthEntryCountList = new int[providerSize];
             int[] candidateRemoteEntryCountList = new int[providerSize];
+            String[] frameworkExceptionList = new String[providerSize];
             int index = 0;
             for (var session : providerSessions) {
                 CandidatePhaseMetric metric = session.mProviderSessionMetric
@@ -209,6 +228,7 @@
                 candidateActionEntryCountList[index] = metric.getActionEntryCount();
                 candidateAuthEntryCountList[index] = metric.getAuthenticationEntryCount();
                 candidateRemoteEntryCountList[index] = metric.getRemoteEntryCount();
+                frameworkExceptionList[index] = metric.getFrameworkException();
                 index++;
             }
             FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_CANDIDATE_PHASE_REPORTED,
@@ -229,11 +249,16 @@
                     /* candidate_provider_credential_entry_type_count */
                     candidateCredentialTypeCountList,
                     /* candidate_provider_remote_entry_count */ candidateRemoteEntryCountList,
-                    /* candidate_provider_authentication_entry_count */ candidateAuthEntryCountList,
-                    DEFAULT_REPEATED_STR,
-                    false,
-                    DEFAULT_REPEATED_STR,
-                    DEFAULT_REPEATED_INT_32
+                    /* candidate_provider_authentication_entry_count */
+                    candidateAuthEntryCountList,
+                    /* framework_exception_per_provider */
+                    frameworkExceptionList,
+                    /* origin_specified originSpecified */
+                    initialPhaseMetric.isOriginSpecified(),
+                    /* request_unique_classtypes */
+                    initialPhaseMetric.getUniqueRequestStrings(),
+                    /* per_classtype_counts */
+                    initialPhaseMetric.getUniqueRequestCounts()
             );
         } catch (Exception e) {
             Log.w(TAG, "Unexpected error during metric logging: " + e);
@@ -297,10 +322,11 @@
                     initialPhaseMetric.getCredentialServiceStartedTimeNanoseconds(),
                     /* count_credential_request_classtypes */
                     initialPhaseMetric.getCountRequestClassType(),
-                    // TODO(b/271135048) - add total count of request options
-                    // TODO(b/271135048) - Uncomment once built past PWG review -
-                    DEFAULT_REPEATED_STR,
-                    DEFAULT_REPEATED_INT_32,
+                    /* request_unique_classtypes */
+                    initialPhaseMetric.getUniqueRequestStrings(),
+                    /* per_classtype_counts */
+                    initialPhaseMetric.getUniqueRequestCounts(),
+                    /* origin_specified */
                     initialPhaseMetric.isOriginSpecified()
             );
         } catch (Exception e) {
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index 1c3d213c..441c87b 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -59,8 +59,7 @@
         int numTypes = (request.getCredentialOptions().stream()
                 .map(CredentialOption::getType).collect(
                         Collectors.toSet())).size(); // Dedupe type strings
-        mRequestSessionMetric.collectGetFlowInitialMetricInfo(numTypes,
-                /*origin=*/request.getOrigin() != null);
+        mRequestSessionMetric.collectGetFlowInitialMetricInfo(request);
         mPrepareGetCredentialCallback = prepareGetCredentialCallback;
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 9ec0ecd..8af6b56 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -91,6 +91,8 @@
     public void onProviderResponseFailure(int errorCode, Exception exception) {
         if (exception instanceof ClearCredentialStateException) {
             mProviderException = (ClearCredentialStateException) exception;
+            // TODO(b/271135048) : Decide on exception type length
+            mProviderSessionMetric.collectCandidateFrameworkException(mProviderException.getType());
         }
         mProviderSessionMetric.collectCandidateExceptionStatus(/*hasException=*/true);
         updateStatusAndInvokeCallback(toStatus(errorCode),
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 09433db..520b937 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -155,6 +155,8 @@
         if (exception instanceof CreateCredentialException) {
             // Store query phase exception for aggregation with final response
             mProviderException = (CreateCredentialException) exception;
+            // TODO(b/271135048) : Decide on exception type length
+            mProviderSessionMetric.collectCandidateFrameworkException(mProviderException.getType());
         }
         mProviderSessionMetric.collectCandidateExceptionStatus(/*hasException=*/true);
         updateStatusAndInvokeCallback(toStatus(errorCode),
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 0c2b563..a62d9e8 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -217,6 +217,8 @@
     public void onProviderResponseFailure(int errorCode, Exception exception) {
         if (exception instanceof GetCredentialException) {
             mProviderException = (GetCredentialException) exception;
+            // TODO(b/271135048) : Decide on exception type length
+            mProviderSessionMetric.collectCandidateFrameworkException(mProviderException.getType());
         }
         mProviderSessionMetric.collectCandidateExceptionStatus(/*hasException=*/true);
         updateStatusAndInvokeCallback(toStatus(errorCode),
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
index ce84d9a..b99f28d 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
@@ -57,7 +57,7 @@
     );
 
     ApiName(int innerMetricCode) {
-        this.mInnerMetricCode = innerMetricCode;
+        mInnerMetricCode = innerMetricCode;
     }
 
     /**
@@ -66,7 +66,7 @@
      * @return a code corresponding to the west world metric name
      */
     public int getMetricCode() {
-        return this.mInnerMetricCode;
+        return mInnerMetricCode;
     }
 
     /**
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
index 4097765..ece729f 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
@@ -32,7 +32,7 @@
     private final int mInnerMetricCode;
 
     ApiStatus(int innerMetricCode) {
-        this.mInnerMetricCode = innerMetricCode;
+        mInnerMetricCode = innerMetricCode;
     }
 
     /**
@@ -41,6 +41,6 @@
      * @return a code corresponding to the west world metric name
      */
     public int getMetricCode() {
-        return this.mInnerMetricCode;
+        return mInnerMetricCode;
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
index 10d4f9c..721d3d7 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
@@ -73,6 +73,8 @@
     private int mAuthenticationEntryCount = -1;
     // Gathered to pass on to chosen provider when required
     private final IntArray mAvailableEntries = new IntArray();
+    // The *framework only* exception held by this provider, empty string by default
+    private String mFrameworkException = "";
 
     public CandidatePhaseMetric() {
     }
@@ -82,27 +84,27 @@
     /* -- Timestamps -- */
 
     public void setServiceBeganTimeNanoseconds(long serviceBeganTimeNanoseconds) {
-        this.mServiceBeganTimeNanoseconds = serviceBeganTimeNanoseconds;
+        mServiceBeganTimeNanoseconds = serviceBeganTimeNanoseconds;
     }
 
     public void setStartQueryTimeNanoseconds(long startQueryTimeNanoseconds) {
-        this.mStartQueryTimeNanoseconds = startQueryTimeNanoseconds;
+        mStartQueryTimeNanoseconds = startQueryTimeNanoseconds;
     }
 
     public void setQueryFinishTimeNanoseconds(long queryFinishTimeNanoseconds) {
-        this.mQueryFinishTimeNanoseconds = queryFinishTimeNanoseconds;
+        mQueryFinishTimeNanoseconds = queryFinishTimeNanoseconds;
     }
 
     public long getServiceBeganTimeNanoseconds() {
-        return this.mServiceBeganTimeNanoseconds;
+        return mServiceBeganTimeNanoseconds;
     }
 
     public long getStartQueryTimeNanoseconds() {
-        return this.mStartQueryTimeNanoseconds;
+        return mStartQueryTimeNanoseconds;
     }
 
     public long getQueryFinishTimeNanoseconds() {
-        return this.mQueryFinishTimeNanoseconds;
+        return mQueryFinishTimeNanoseconds;
     }
 
     /* -- Actual time delta latencies (for local utility) -- */
@@ -111,8 +113,8 @@
      * Returns the latency in microseconds for the query phase.
      */
     public int getQueryLatencyMicroseconds() {
-        return (int) ((this.getQueryFinishTimeNanoseconds()
-                - this.getStartQueryTimeNanoseconds()) / 1000);
+        return (int) ((getQueryFinishTimeNanoseconds()
+                - getStartQueryTimeNanoseconds()) / 1000);
     }
 
     /* --- Time Stamp Conversion to Microseconds from Reference --- */
@@ -126,32 +128,32 @@
      * @return the microsecond integer timestamp from service start to query began
      */
     public int getTimestampFromReferenceStartMicroseconds(long specificTimestamp) {
-        if (specificTimestamp < this.mServiceBeganTimeNanoseconds) {
+        if (specificTimestamp < mServiceBeganTimeNanoseconds) {
             Log.i(TAG, "The timestamp is before service started, falling back to default int");
             return MetricUtilities.DEFAULT_INT_32;
         }
         return (int) ((specificTimestamp
-                - this.mServiceBeganTimeNanoseconds) / 1000);
+                - mServiceBeganTimeNanoseconds) / 1000);
     }
 
     /* ------------- Provider Query Status ------------ */
 
     public void setProviderQueryStatus(int providerQueryStatus) {
-        this.mProviderQueryStatus = providerQueryStatus;
+        mProviderQueryStatus = providerQueryStatus;
     }
 
     public int getProviderQueryStatus() {
-        return this.mProviderQueryStatus;
+        return mProviderQueryStatus;
     }
 
     /* -------------- Candidate Uid ---------------- */
 
     public void setCandidateUid(int candidateUid) {
-        this.mCandidateUid = candidateUid;
+        mCandidateUid = candidateUid;
     }
 
     public int getCandidateUid() {
-        return this.mCandidateUid;
+        return mCandidateUid;
     }
 
     /* -------------- Session Id ---------------- */
@@ -254,7 +256,7 @@
      *          collector
      */
     public void addEntry(EntryEnum e) {
-        this.mAvailableEntries.add(e.getMetricCode());
+        mAvailableEntries.add(e.getMetricCode());
     }
 
     /**
@@ -267,4 +269,14 @@
     public List<Integer> getAvailableEntries() {
         return Arrays.stream(mAvailableEntries.toArray()).boxed().collect(Collectors.toList());
     }
+
+    /* ------ Framework Exception for this Candidate ------ */
+
+    public void setFrameworkException(String frameworkException) {
+        mFrameworkException = frameworkException;
+    }
+
+    public String getFrameworkException() {
+        return mFrameworkException;
+    }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
index 2eef197..c80cc24 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
@@ -138,8 +138,8 @@
     }
 
     public int getUiPhaseLatencyMicroseconds() {
-        return (int) ((this.mUiCallEndTimeNanoseconds
-                - this.mUiCallStartTimeNanoseconds) / 1000);
+        return (int) ((mUiCallEndTimeNanoseconds
+                - mUiCallStartTimeNanoseconds) / 1000);
     }
 
     /**
@@ -147,8 +147,8 @@
      * start time to be provided, such as from {@link CandidatePhaseMetric}.
      */
     public int getEntireProviderLatencyMicroseconds() {
-        return (int) ((this.mFinalFinishTimeNanoseconds
-                - this.mQueryStartTimeNanoseconds) / 1000);
+        return (int) ((mFinalFinishTimeNanoseconds
+                - mQueryStartTimeNanoseconds) / 1000);
     }
 
     /**
@@ -156,8 +156,8 @@
      * start time to be provided, such as from {@link InitialPhaseMetric}.
      */
     public int getEntireLatencyMicroseconds() {
-        return (int) ((this.mFinalFinishTimeNanoseconds
-                - this.mServiceBeganTimeNanoseconds) / 1000);
+        return (int) ((mFinalFinishTimeNanoseconds
+                - mServiceBeganTimeNanoseconds) / 1000);
     }
 
     /* ----- Timestamps for Latency ----- */
@@ -183,11 +183,11 @@
     }
 
     public void setUiCallStartTimeNanoseconds(long uiCallStartTimeNanoseconds) {
-        this.mUiCallStartTimeNanoseconds = uiCallStartTimeNanoseconds;
+        mUiCallStartTimeNanoseconds = uiCallStartTimeNanoseconds;
     }
 
     public void setUiCallEndTimeNanoseconds(long uiCallEndTimeNanoseconds) {
-        this.mUiCallEndTimeNanoseconds = uiCallEndTimeNanoseconds;
+        mUiCallEndTimeNanoseconds = uiCallEndTimeNanoseconds;
     }
 
     public void setFinalFinishTimeNanoseconds(long finalFinishTimeNanoseconds) {
@@ -229,12 +229,12 @@
      * @return the microsecond integer timestamp from service start to query began
      */
     public int getTimestampFromReferenceStartMicroseconds(long specificTimestamp) {
-        if (specificTimestamp < this.mServiceBeganTimeNanoseconds) {
+        if (specificTimestamp < mServiceBeganTimeNanoseconds) {
             Log.i(TAG, "The timestamp is before service started, falling back to default int");
             return MetricUtilities.DEFAULT_INT_32;
         }
         return (int) ((specificTimestamp
-                - this.mServiceBeganTimeNanoseconds) / 1000);
+                - mServiceBeganTimeNanoseconds) / 1000);
     }
 
     /* ----------- Provider Status -------------- */
@@ -334,7 +334,7 @@
      * chosen phase in a semantically correct way.
      */
     public void setAvailableEntries(List<Integer> entries) {
-        this.mAvailableEntries = new ArrayList<>(entries); // no alias copy
+        mAvailableEntries = new ArrayList<>(entries); // no alias copy
     }
 
     /**
@@ -345,7 +345,7 @@
      * candidate phase.
      */
     public List<Integer> getAvailableEntries() {
-        return new ArrayList<>(this.mAvailableEntries); // no alias copy
+        return new ArrayList<>(mAvailableEntries); // no alias copy
     }
 
     /* -------------- Has Exception ---------------- */
diff --git a/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java b/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
index 80f9fdc..b9125dd 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
@@ -56,7 +56,7 @@
     );
 
     EntryEnum(int innerMetricCode) {
-        this.mInnerMetricCode = innerMetricCode;
+        mInnerMetricCode = innerMetricCode;
     }
 
     /**
@@ -65,7 +65,7 @@
      * @return a code corresponding to the west world metric name
      */
     public int getMetricCode() {
-        return this.mInnerMetricCode;
+        return mInnerMetricCode;
     }
 
     /**
diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
index 0210b14..0ecd9cc 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
@@ -16,6 +16,11 @@
 
 package com.android.server.credentials.metrics;
 
+import android.util.Log;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
 /**
  * This handles metrics collected prior to any remote calls to providers.
  * Some types are redundant across these metric collectors, but that has debug use-cases as
@@ -32,7 +37,6 @@
     private int mCallerUid = -1;
     // The session id to unite multiple atom emits, default to -1
     private int mSessionId = -1;
-    private int mCountRequestClassType = -1;
 
     // Raw timestamps in nanoseconds, *the only* one logged as such (i.e. 64 bits) since it is a
     // reference point.
@@ -46,6 +50,9 @@
     // TODO(b/271135048) - Emit once metrics approved
     private boolean mOriginSpecified = false;
 
+    // Stores the deduped request information, particularly {"req":5}.
+    private Map<String, Integer> mRequestCounts = new LinkedHashMap<>();
+
 
     public InitialPhaseMetric() {
     }
@@ -55,8 +62,8 @@
     /* -- Direct Latency Utility -- */
 
     public int getServiceStartToQueryLatencyMicroseconds() {
-        return (int) ((this.mCredentialServiceStartedTimeNanoseconds
-                - this.mCredentialServiceBeginQueryTimeNanoseconds) / 1000);
+        return (int) ((mCredentialServiceStartedTimeNanoseconds
+                - mCredentialServiceBeginQueryTimeNanoseconds) / 1000);
     }
 
     /* -- Timestamps -- */
@@ -64,7 +71,7 @@
     public void setCredentialServiceStartedTimeNanoseconds(
             long credentialServiceStartedTimeNanoseconds
     ) {
-        this.mCredentialServiceStartedTimeNanoseconds = credentialServiceStartedTimeNanoseconds;
+        mCredentialServiceStartedTimeNanoseconds = credentialServiceStartedTimeNanoseconds;
     }
 
     public void setCredentialServiceBeginQueryTimeNanoseconds(
@@ -112,13 +119,11 @@
 
     /* ------ Count Request Class Types ------ */
 
-    public void setCountRequestClassType(int countRequestClassType) {
-        mCountRequestClassType = countRequestClassType;
+    public int getCountRequestClassType() {
+        return mRequestCounts.size();
     }
 
-    public int getCountRequestClassType() {
-        return mCountRequestClassType;
-    }
+    /* ------ Origin Specified ------ */
 
     public void setOriginSpecified(boolean originSpecified) {
         mOriginSpecified = originSpecified;
@@ -127,4 +132,34 @@
     public boolean isOriginSpecified() {
         return mOriginSpecified;
     }
+
+    /* ------ Unique Request Counts Map Information ------ */
+
+    public void setRequestCounts(Map<String, Integer> requestCounts) {
+        mRequestCounts = requestCounts;
+    }
+
+    /**
+     * Reruns the unique, deduped, request classtypes for logging.
+     * @return a string array for deduped classtypes
+     */
+    public String[] getUniqueRequestStrings() {
+        if (mRequestCounts.isEmpty()) {
+            Log.w(TAG, "There are no unique string request types collected");
+        }
+        String[] result = new String[mRequestCounts.keySet().size()];
+        mRequestCounts.keySet().toArray(result);
+        return result;
+    }
+
+    /**
+     * Reruns the unique, deduped, request classtype counts for logging.
+     * @return a string array for deduped classtype counts
+     */
+    public int[] getUniqueRequestCounts() {
+        if (mRequestCounts.isEmpty()) {
+            Log.w(TAG, "There are no unique string request type counts collected");
+        }
+        return mRequestCounts.values().stream().mapToInt(Integer::intValue).toArray();
+    }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
index 76fd478..9a88255 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
@@ -61,6 +61,18 @@
     }
 
     /**
+     * Collects the framework only exception encountered in a candidate flow.
+     * @param exceptionType the string, cut to desired length, of the exception type
+     */
+    public void collectCandidateFrameworkException(String exceptionType) {
+        try {
+            mCandidatePhasePerProviderMetric.setFrameworkException(exceptionType);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
      * Used to collect metrics at the update stage when a candidate provider gives back an update.
      *
      * @param isFailureStatus indicates the candidate provider sent back a terminated response
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java
index a12a694..b1e6a4c 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java
@@ -38,7 +38,7 @@
     private final int mInnerMetricCode;
 
     ProviderStatusForMetrics(int innerMetricCode) {
-        this.mInnerMetricCode = innerMetricCode;
+        mInnerMetricCode = innerMetricCode;
     }
 
     /**
@@ -47,6 +47,6 @@
      * @return a code corresponding to the west world metric name
      */
     public int getMetricCode() {
-        return this.mInnerMetricCode;
+        return mInnerMetricCode;
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
index 10bf56c..547c09a 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
@@ -16,10 +16,12 @@
 
 package com.android.server.credentials.metrics;
 
+import static com.android.server.credentials.MetricUtilities.DELTA_CUT;
+import static com.android.server.credentials.MetricUtilities.generateMetricKey;
 import static com.android.server.credentials.MetricUtilities.logApiCalledCandidatePhase;
 import static com.android.server.credentials.MetricUtilities.logApiCalledFinalPhase;
 
-import android.annotation.NonNull;
+import android.credentials.GetCredentialRequest;
 import android.credentials.ui.UserSelectionDialogResult;
 import android.os.IBinder;
 import android.util.Log;
@@ -27,6 +29,7 @@
 import com.android.server.credentials.ProviderSession;
 
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -48,7 +51,6 @@
     protected final ChosenProviderFinalPhaseMetric
             mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric();
     // TODO(b/271135048) - Replace this with a new atom per each browsing emit (V4)
-    @NonNull
     protected List<CandidateBrowsingPhaseMetric> mCandidateBrowsingPhaseMetric = new ArrayList<>();
 
     public RequestSessionMetric() {
@@ -161,16 +163,32 @@
         }
     }
 
+    // Used by get flows to generate the unique request count maps
+    private Map<String, Integer> getRequestCountMap(GetCredentialRequest request) {
+        Map<String, Integer> uniqueRequestCounts = new LinkedHashMap<>();
+        try {
+            request.getCredentialOptions().forEach(option -> {
+                String optionKey = generateMetricKey(option.getType(), DELTA_CUT);
+                if (!uniqueRequestCounts.containsKey(optionKey)) {
+                    uniqueRequestCounts.put(optionKey, 0);
+                }
+                uniqueRequestCounts.put(optionKey, uniqueRequestCounts.get(optionKey) + 1);
+            });
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during get request metric logging: " + e);
+        }
+        return uniqueRequestCounts;
+    }
+
     /**
      * Collects initializations for Get flow metrics.
      *
-     * @param requestClassTypeCount the number of class types in the request
-     * @param origin indicates if an origin was passed in or not
+     * @param request the get credential request containing information to parse for metrics
      */
-    public void collectGetFlowInitialMetricInfo(int requestClassTypeCount, boolean origin) {
+    public void collectGetFlowInitialMetricInfo(GetCredentialRequest request) {
         try {
-            mInitialPhaseMetric.setCountRequestClassType(requestClassTypeCount);
-            mInitialPhaseMetric.setOriginSpecified(origin);
+            mInitialPhaseMetric.setOriginSpecified(request.getOrigin() != null);
+            mInitialPhaseMetric.setRequestCounts(getRequestCountMap(request));
         } catch (Exception e) {
             Log.w(TAG, "Unexpected error during metric logging: " + e);
         }
@@ -306,7 +324,7 @@
      */
     public void logCandidatePhaseMetrics(Map<String, ProviderSession> providers) {
         try {
-            logApiCalledCandidatePhase(providers, ++mSequenceCounter);
+            logApiCalledCandidatePhase(providers, ++mSequenceCounter, mInitialPhaseMetric);
         } catch (Exception e) {
             Log.w(TAG, "Unexpected error during metric logging: " + e);
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 4d739d2..f6bc93a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5865,8 +5865,7 @@
                 // would allow bypassing of the maximum time to lock.
                 mInjector.settingsGlobalPutInt(Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
             }
-            getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(
-                    UserHandle.USER_SYSTEM, timeMs);
+            getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(parentId, timeMs);
         });
     }
 
@@ -9199,9 +9198,15 @@
                     MANAGE_DEVICE_POLICY_CAMERA,
                     caller.getPackageName(),
                     getProfileParentUserIfRequested(userId, parent));
-
-            setBackwardCompatibleUserRestriction(
-                    caller, enforcingAdmin, UserManager.DISALLOW_CAMERA, disabled, parent);
+            try {
+                setBackwardCompatibleUserRestriction(
+                        caller, enforcingAdmin, UserManager.DISALLOW_CAMERA, disabled, parent);
+            } catch (IllegalStateException e) {
+                throw new IllegalStateException(
+                        "Please use addUserRestriction or addUserRestrictionGlobally using the key"
+                                + " UserManager.DISALLOW_CAMERA to disable the camera locally or"
+                                + " globally, respectively");
+            }
         } else {
             Objects.requireNonNull(who, "ComponentName is null");
             if (parent) {
@@ -15465,11 +15470,13 @@
 
         int userId = caller.getUserId();
         synchronized (getLockObject()) {
-            Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId),
-                    "Admin " + who
-                            + " is neither the device owner or affiliated user's profile owner.");
-            if (isManagedProfile(userId)) {
-                throw new SecurityException("Managed profile cannot disable status bar");
+            if (!isPermissionCheckFlagEnabled()) {
+                Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId),
+                        "Admin " + who + " is neither the device owner or affiliated "
+                                + "user's profile owner.");
+                if (isManagedProfile(userId)) {
+                    throw new SecurityException("Managed profile cannot disable status bar");
+                }
             }
             checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_STATUS_BAR_DISABLED);
 
@@ -15522,16 +15529,23 @@
     @Override
     public boolean isStatusBarDisabled(String callerPackage) {
         final CallerIdentity caller = getCallerIdentity(callerPackage);
-        Preconditions.checkCallAuthorization(
-                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
+        if (isPermissionCheckFlagEnabled()) {
+            enforceCanQuery(
+                    MANAGE_DEVICE_POLICY_STATUS_BAR, caller.getPackageName(), caller.getUserId());
+        } else {
+            Preconditions.checkCallAuthorization(
+                    isProfileOwner(caller) || isDefaultDeviceOwner(caller));
+        }
 
         int userId = caller.getUserId();
         synchronized (getLockObject()) {
-            Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId),
-                    "Admin " + callerPackage
-                            + " is neither the device owner or affiliated user's profile owner.");
-            if (isManagedProfile(userId)) {
-                throw new SecurityException("Managed profile cannot disable status bar");
+            if (!isPermissionCheckFlagEnabled()) {
+                Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId),
+                        "Admin " + callerPackage
+                                + " is neither the device owner or affiliated user's profile owner.");
+                if (isManagedProfile(userId)) {
+                    throw new SecurityException("Managed profile cannot disable status bar");
+                }
             }
             DevicePolicyData policy = getUserData(userId);
             return policy.mStatusBarDisabled;
@@ -22753,6 +22767,7 @@
                     MANAGE_DEVICE_POLICY_AUTOFILL,
                     MANAGE_DEVICE_POLICY_BLUETOOTH,
                     MANAGE_DEVICE_POLICY_CALLS,
+                    MANAGE_DEVICE_POLICY_CAMERA,
                     MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
                     MANAGE_DEVICE_POLICY_DISPLAY,
                     MANAGE_DEVICE_POLICY_FACTORY_RESET,
@@ -22788,7 +22803,6 @@
                     MANAGE_DEVICE_POLICY_ACROSS_USERS,
                     MANAGE_DEVICE_POLICY_AIRPLANE_MODE,
                     MANAGE_DEVICE_POLICY_APPS_CONTROL,
-                    MANAGE_DEVICE_POLICY_CAMERA,
                     MANAGE_DEVICE_POLICY_CERTIFICATES,
                     MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
                     MANAGE_DEVICE_POLICY_DEFAULT_SMS,
@@ -22816,7 +22830,6 @@
     private static final List<String> ADDITIONAL_PROFILE_OWNER_ON_USER_0_PERMISSIONS =
             List.of(
                     MANAGE_DEVICE_POLICY_AIRPLANE_MODE,
-                    MANAGE_DEVICE_POLICY_CAMERA,
                     MANAGE_DEVICE_POLICY_DISPLAY,
                     MANAGE_DEVICE_POLICY_FUN,
                     MANAGE_DEVICE_POLICY_LOCK_TASK,
@@ -22827,7 +22840,6 @@
                     MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
                     MANAGE_DEVICE_POLICY_SAFE_BOOT,
                     MANAGE_DEVICE_POLICY_SMS,
-                    MANAGE_DEVICE_POLICY_STATUS_BAR,
                     MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
                     MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER,
                     MANAGE_DEVICE_POLICY_USERS,
@@ -22848,7 +22860,9 @@
      * All the additional permissions granted to a Profile Owner on an affiliated user.
      */
     private static final List<String> ADDITIONAL_AFFILIATED_PROFILE_OWNER_ON_USER_PERMISSIONS =
-            List.of();
+            List.of(
+                    MANAGE_DEVICE_POLICY_STATUS_BAR
+            );
 
     /**
      * Combination of {@link PROFILE_OWNER_PERMISSIONS} and
diff --git a/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java
new file mode 100644
index 0000000..77c3396
--- /dev/null
+++ b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java
@@ -0,0 +1,244 @@
+/*
+ * 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.security.rkp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.security.keymint.DeviceInfo;
+import android.hardware.security.keymint.IRemotelyProvisionedComponent;
+import android.hardware.security.keymint.MacedPublicKey;
+import android.hardware.security.keymint.ProtectedData;
+import android.hardware.security.keymint.RpcHardwareInfo;
+import android.os.Binder;
+import android.os.FileUtils;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Map;
+
+@RunWith(AndroidJUnit4.class)
+public class RemoteProvisioningShellCommandTest {
+
+    private static class Injector extends RemoteProvisioningShellCommand.Injector {
+
+        private final Map<String, IRemotelyProvisionedComponent> mIrpcs;
+
+        Injector(Map irpcs) {
+            mIrpcs = irpcs;
+        }
+
+        @Override
+        String[] getIrpcNames() {
+            return mIrpcs.keySet().toArray(new String[0]);
+        }
+
+        @Override
+        IRemotelyProvisionedComponent getIrpcBinder(String name) {
+            IRemotelyProvisionedComponent irpc = mIrpcs.get(name);
+            if (irpc == null) {
+                throw new IllegalArgumentException("failed to find " + irpc);
+            }
+            return irpc;
+        }
+    }
+
+    private static class CommandResult {
+        private int mCode;
+        private String mOut;
+        private String mErr;
+
+        CommandResult(int code, String out, String err) {
+            mCode = code;
+            mOut = out;
+            mErr = err;
+        }
+
+        int getCode() {
+            return mCode;
+        }
+
+        String getOut() {
+            return mOut;
+        }
+
+        String getErr() {
+            return mErr;
+        }
+    }
+
+    private static CommandResult exec(
+            RemoteProvisioningShellCommand cmd, String[] args) throws Exception {
+        File in = File.createTempFile("rpsct_in_", null);
+        File out = File.createTempFile("rpsct_out_", null);
+        File err = File.createTempFile("rpsct_err_", null);
+        int code = cmd.exec(
+                new Binder(),
+                new FileInputStream(in).getFD(),
+                new FileOutputStream(out).getFD(),
+                new FileOutputStream(err).getFD(),
+                args);
+        return new CommandResult(
+                code, FileUtils.readTextFile(out, 0, null), FileUtils.readTextFile(err, 0, null));
+    }
+
+    @Test
+    public void list_zeroInstances() throws Exception {
+        RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand(
+                new Injector(Map.of()));
+        CommandResult res = exec(cmd, new String[] {"list"});
+        assertThat(res.getErr()).isEmpty();
+        assertThat(res.getCode()).isEqualTo(0);
+        assertThat(res.getOut()).isEmpty();
+    }
+
+    @Test
+    public void list_oneInstances() throws Exception {
+        RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand(
+                new Injector(Map.of("default", mock(IRemotelyProvisionedComponent.class))));
+        CommandResult res = exec(cmd, new String[] {"list"});
+        assertThat(res.getErr()).isEmpty();
+        assertThat(res.getCode()).isEqualTo(0);
+        assertThat(Arrays.asList(res.getOut().split("\n"))).containsExactly("default");
+    }
+
+    @Test
+    public void list_twoInstances() throws Exception {
+        RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand(
+                new Injector(Map.of(
+                       "default", mock(IRemotelyProvisionedComponent.class),
+                       "strongbox", mock(IRemotelyProvisionedComponent.class))));
+        CommandResult res = exec(cmd, new String[] {"list"});
+        assertThat(res.getErr()).isEmpty();
+        assertThat(res.getCode()).isEqualTo(0);
+        assertThat(Arrays.asList(res.getOut().split("\n"))).containsExactly("default", "strongbox");
+    }
+
+    @Test
+    public void csr_hwVersion1_withChallenge() throws Exception {
+        IRemotelyProvisionedComponent defaultMock = mock(IRemotelyProvisionedComponent.class);
+        RpcHardwareInfo defaultInfo = new RpcHardwareInfo();
+        defaultInfo.versionNumber = 1;
+        defaultInfo.supportedEekCurve = RpcHardwareInfo.CURVE_25519;
+        when(defaultMock.getHardwareInfo()).thenReturn(defaultInfo);
+        doAnswer(invocation -> {
+            ((DeviceInfo) invocation.getArgument(4)).deviceInfo = new byte[] {0x00};
+            ((ProtectedData) invocation.getArgument(5)).protectedData = new byte[] {0x00};
+            return new byte[] {0x77, 0x77, 0x77, 0x77};
+        }).when(defaultMock).generateCertificateRequest(
+                anyBoolean(), any(), any(), any(), any(), any());
+
+        RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand(
+                new Injector(Map.of("default", defaultMock)));
+        CommandResult res = exec(cmd, new String[] {
+                "csr", "--challenge", "dGVzdHRlc3R0ZXN0dGVzdA==", "default"});
+        verify(defaultMock).generateCertificateRequest(
+                /*test_mode=*/eq(false),
+                eq(new MacedPublicKey[0]),
+                eq(Base64.getDecoder().decode(RemoteProvisioningShellCommand.EEK_ED25519_BASE64)),
+                eq(new byte[] {
+                        0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74,
+                        0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74}),
+                any(DeviceInfo.class),
+                any(ProtectedData.class));
+        assertThat(res.getErr()).isEmpty();
+        assertThat(res.getCode()).isEqualTo(0);
+    }
+
+    @Test
+    public void csr_hwVersion2_withChallenge() throws Exception {
+        IRemotelyProvisionedComponent defaultMock = mock(IRemotelyProvisionedComponent.class);
+        RpcHardwareInfo defaultInfo = new RpcHardwareInfo();
+        defaultInfo.versionNumber = 2;
+        defaultInfo.supportedEekCurve = RpcHardwareInfo.CURVE_P256;
+        when(defaultMock.getHardwareInfo()).thenReturn(defaultInfo);
+        doAnswer(invocation -> {
+            ((DeviceInfo) invocation.getArgument(4)).deviceInfo = new byte[] {0x00};
+            ((ProtectedData) invocation.getArgument(5)).protectedData = new byte[] {0x00};
+            return new byte[] {0x77, 0x77, 0x77, 0x77};
+        }).when(defaultMock).generateCertificateRequest(
+                anyBoolean(), any(), any(), any(), any(), any());
+
+        RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand(
+                new Injector(Map.of("default", defaultMock)));
+        CommandResult res = exec(cmd, new String[] {
+                "csr", "--challenge", "dGVzdHRlc3R0ZXN0dGVzdA==", "default"});
+        verify(defaultMock).generateCertificateRequest(
+                /*test_mode=*/eq(false),
+                eq(new MacedPublicKey[0]),
+                eq(Base64.getDecoder().decode(RemoteProvisioningShellCommand.EEK_P256_BASE64)),
+                eq(new byte[] {
+                        0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74,
+                        0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74}),
+                any(DeviceInfo.class),
+                any(ProtectedData.class));
+        assertThat(res.getErr()).isEmpty();
+        assertThat(res.getCode()).isEqualTo(0);
+    }
+
+    @Test
+    public void csr_hwVersion3_withoutChallenge() throws Exception {
+        IRemotelyProvisionedComponent defaultMock = mock(IRemotelyProvisionedComponent.class);
+        RpcHardwareInfo defaultInfo = new RpcHardwareInfo();
+        defaultInfo.versionNumber = 3;
+        when(defaultMock.getHardwareInfo()).thenReturn(defaultInfo);
+        when(defaultMock.generateCertificateRequestV2(any(), any()))
+            .thenReturn(new byte[] {0x68, 0x65, 0x6c, 0x6c, 0x6f});
+
+        RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand(
+                new Injector(Map.of("default", defaultMock)));
+        CommandResult res = exec(cmd, new String[] {"csr", "default"});
+        verify(defaultMock).generateCertificateRequestV2(new MacedPublicKey[0], new byte[0]);
+        assertThat(res.getErr()).isEmpty();
+        assertThat(res.getCode()).isEqualTo(0);
+        assertThat(res.getOut()).isEqualTo("aGVsbG8=\n");
+    }
+
+    @Test
+    public void csr_hwVersion3_withChallenge() throws Exception {
+        IRemotelyProvisionedComponent defaultMock = mock(IRemotelyProvisionedComponent.class);
+        RpcHardwareInfo defaultInfo = new RpcHardwareInfo();
+        defaultInfo.versionNumber = 3;
+        when(defaultMock.getHardwareInfo()).thenReturn(defaultInfo);
+        when(defaultMock.generateCertificateRequestV2(any(), any()))
+            .thenReturn(new byte[] {0x68, 0x69});
+
+        RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand(
+                new Injector(Map.of("default", defaultMock)));
+        CommandResult res = exec(cmd, new String[] {"csr", "--challenge", "dHJpYWw=", "default"});
+        verify(defaultMock).generateCertificateRequestV2(
+                new MacedPublicKey[0], new byte[] {0x74, 0x72, 0x69, 0x61, 0x6c});
+        assertThat(res.getErr()).isEmpty();
+        assertThat(res.getCode()).isEqualTo(0);
+        assertThat(res.getOut()).isEqualTo("aGk=\n");
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
new file mode 100644
index 0000000..17fba9f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.display;
+
+import static com.android.internal.display.RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import android.hardware.display.DisplayManager;
+import android.provider.Settings;
+import android.testing.TestableContext;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.display.RefreshRateSettingsUtils;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RefreshRateSettingsUtilsTest {
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    @Mock
+    private DisplayManager mDisplayManagerMock;
+    @Mock
+    private Display mDisplayMock;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext.addMockSystemService(DisplayManager.class, mDisplayManagerMock);
+
+        Display.Mode[] modes = new Display.Mode[] {
+                new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+                        /* refreshRate= */ 60),
+                new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+                        /* refreshRate= */ 120),
+                new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+                        /* refreshRate= */ 90)
+        };
+
+        when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
+        when(mDisplayMock.getSupportedModes()).thenReturn(modes);
+    }
+
+    @Test
+    public void testFindHighestRefreshRateForDefaultDisplay() {
+        when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(null);
+        assertEquals(DEFAULT_REFRESH_RATE,
+                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
+                /* delta= */ 0);
+
+        when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
+        assertEquals(120,
+                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
+                /* delta= */ 0);
+    }
+
+    @Test
+    public void testGetMinRefreshRate() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.FORCE_PEAK_REFRESH_RATE, -1);
+        assertEquals(0, RefreshRateSettingsUtils.getMinRefreshRate(mContext), /* delta= */ 0);
+
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.FORCE_PEAK_REFRESH_RATE, 0);
+        assertEquals(0, RefreshRateSettingsUtils.getMinRefreshRate(mContext), /* delta= */ 0);
+
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.FORCE_PEAK_REFRESH_RATE, 1);
+        assertEquals(120, RefreshRateSettingsUtils.getMinRefreshRate(mContext), /* delta= */ 0);
+    }
+
+    @Test
+    public void testGetPeakRefreshRate() {
+        float defaultPeakRefreshRate = 100;
+
+        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, -1);
+        assertEquals(defaultPeakRefreshRate,
+                RefreshRateSettingsUtils.getPeakRefreshRate(mContext, defaultPeakRefreshRate),
+                /* delta= */ 0);
+
+        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, 0);
+        assertEquals(DEFAULT_REFRESH_RATE,
+                RefreshRateSettingsUtils.getPeakRefreshRate(mContext, defaultPeakRefreshRate),
+                /* delta= */ 0);
+
+        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, 1);
+        assertEquals(120,
+                RefreshRateSettingsUtils.getPeakRefreshRate(mContext, defaultPeakRefreshRate),
+                /* delta= */ 0);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index ab8f3f2..d12741a 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -211,8 +211,7 @@
     @Test
     public void testStartUser_foreground() {
         mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
-        verify(mInjector.getWindowManager()).startFreezingScreen(anyInt(), anyInt());
-        verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
+        verify(mInjector, never()).dismissUserSwitchingDialog(any());
         verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
         verify(mInjector.getWindowManager()).setSwitchingUser(true);
         verify(mInjector).clearAllLockedTasks(anyString());
@@ -224,7 +223,8 @@
     public void testStartUser_background() {
         boolean started = mUserController.startUser(TEST_USER_ID, USER_START_MODE_BACKGROUND);
         assertWithMessage("startUser(%s, foreground=false)", TEST_USER_ID).that(started).isTrue();
-        verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
+        verify(mInjector, never()).showUserSwitchingDialog(
+                any(), any(), anyString(), anyString(), any());
         verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
         verify(mInjector, never()).clearAllLockedTasks(anyString());
         startBackgroundUserAssertions();
@@ -276,7 +276,8 @@
         assertWithMessage("startUserOnDisplay(%s, %s)", TEST_USER_ID, 42).that(started).isTrue();
         verifyUserAssignedToDisplay(TEST_USER_ID, 42);
 
-        verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
+        verify(mInjector, never()).showUserSwitchingDialog(
+                any(), any(), anyString(), anyString(), any());
         verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
         verify(mInjector, never()).clearAllLockedTasks(anyString());
         startBackgroundUserAssertions();
@@ -288,8 +289,9 @@
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
 
         mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
-        verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
-        verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
+        verify(mInjector, never()).showUserSwitchingDialog(
+                any(), any(), anyString(), anyString(), any());
+        verify(mInjector, never()).dismissUserSwitchingDialog(any());
         verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
         startForegroundUserAssertions();
     }
@@ -310,7 +312,8 @@
         // Make sure no intents have been fired for pre-created users.
         assertTrue(mInjector.mSentIntents.isEmpty());
 
-        verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
+        verify(mInjector, never()).showUserSwitchingDialog(
+                any(), any(), anyString(), anyString(), any());
         verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
         verify(mInjector, never()).clearAllLockedTasks(anyString());
 
@@ -442,7 +445,7 @@
         // Verify that continueUserSwitch worked as expected
         continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
         verify(mInjector, times(0)).dismissKeyguard(any());
-        verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
+        verify(mInjector, times(1)).dismissUserSwitchingDialog(any());
         continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
         verifySystemUserVisibilityChangesNeverNotified();
     }
@@ -463,7 +466,7 @@
         // Verify that continueUserSwitch worked as expected
         continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
         verify(mInjector, times(1)).dismissKeyguard(any());
-        verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
+        verify(mInjector, times(1)).dismissUserSwitchingDialog(any());
         continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
         verifySystemUserVisibilityChangesNeverNotified();
     }
@@ -483,7 +486,7 @@
         mInjector.mHandler.clearAllRecordedMessages();
         // Verify that continueUserSwitch worked as expected
         continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
-        verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
+        verify(mInjector, never()).dismissUserSwitchingDialog(any());
         continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
     }
 
@@ -985,8 +988,7 @@
         mInjector.mHandler.clearAllRecordedMessages();
         // Verify that continueUserSwitch worked as expected
         continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
-        verify(mInjector.getWindowManager(), times(expectedNumberOfCalls))
-                .stopFreezingScreen();
+        verify(mInjector, times(expectedNumberOfCalls)).dismissUserSwitchingDialog(any());
         continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping);
     }
 
@@ -1189,6 +1191,22 @@
         }
 
         @Override
+        void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser,
+                String switchingFromSystemUserMessage, String switchingToSystemUserMessage,
+                Runnable onShown) {
+            if (onShown != null) {
+                onShown.run();
+            }
+        }
+
+        @Override
+        void dismissUserSwitchingDialog(Runnable onDismissed) {
+            if (onDismissed != null) {
+                onDismissed.run();
+            }
+        }
+
+        @Override
         protected LockPatternUtils getLockPatternUtils() {
             return mLockPatternUtilsMock;
         }
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java
index 3ed95eb..bacf256 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java
@@ -45,16 +45,22 @@
     @Test
     public void createPhoneAccount_success() {
         final PhoneAccount phoneAccount = mSyncConnectionService.createPhoneAccount(
-                "com.google.test", "Test App");
+                new CallMetadataSyncConnectionService.PhoneAccountHandleIdentifier(/*
+                associationId= */
+                        0, "com.google.test"), "Test App");
         assertWithMessage("Could not create phone account").that(phoneAccount).isNotNull();
     }
 
     @Test
     public void createPhoneAccount_alreadyExists_doesNotCreateAnother() {
         final PhoneAccount phoneAccount = mSyncConnectionService.createPhoneAccount(
-                "com.google.test", "Test App");
+                new CallMetadataSyncConnectionService.PhoneAccountHandleIdentifier(/*
+                associationId= */
+                        0, "com.google.test"), "Test App");
         final PhoneAccount phoneAccount2 = mSyncConnectionService.createPhoneAccount(
-                "com.google.test", "Test App #2");
+                new CallMetadataSyncConnectionService.PhoneAccountHandleIdentifier(/*
+                associationId= */
+                        0, "com.google.test"), "Test App #2");
         assertWithMessage("Could not create phone account").that(phoneAccount).isNotNull();
         assertWithMessage("Unexpectedly created second phone account").that(phoneAccount2).isNull();
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 16aadac..d85db64b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3267,31 +3267,31 @@
         reset(getServices().settings);
 
         dpm.setMaximumTimeToLock(admin1, 0);
-        verifyScreenTimeoutCall(null, UserHandle.USER_SYSTEM);
+        verifyScreenTimeoutCall(null, CALLER_USER_HANDLE);
         verifyStayOnWhilePluggedCleared(false);
         reset(getServices().powerManagerInternal);
         reset(getServices().settings);
 
         dpm.setMaximumTimeToLock(admin1, 1);
-        verifyScreenTimeoutCall(1L, UserHandle.USER_SYSTEM);
+        verifyScreenTimeoutCall(1L, CALLER_USER_HANDLE);
         verifyStayOnWhilePluggedCleared(true);
         reset(getServices().powerManagerInternal);
         reset(getServices().settings);
 
         dpm.setMaximumTimeToLock(admin2, 10);
-        verifyScreenTimeoutCall(null, UserHandle.USER_SYSTEM);
+        verifyScreenTimeoutCall(null, CALLER_USER_HANDLE);
         verifyStayOnWhilePluggedCleared(false);
         reset(getServices().powerManagerInternal);
         reset(getServices().settings);
 
         dpm.setMaximumTimeToLock(admin1, 5);
-        verifyScreenTimeoutCall(5L, UserHandle.USER_SYSTEM);
+        verifyScreenTimeoutCall(5L, CALLER_USER_HANDLE);
         verifyStayOnWhilePluggedCleared(true);
         reset(getServices().powerManagerInternal);
         reset(getServices().settings);
 
         dpm.setMaximumTimeToLock(admin2, 4);
-        verifyScreenTimeoutCall(4L, UserHandle.USER_SYSTEM);
+        verifyScreenTimeoutCall(4L, CALLER_USER_HANDLE);
         verifyStayOnWhilePluggedCleared(true);
         reset(getServices().powerManagerInternal);
         reset(getServices().settings);
@@ -3301,20 +3301,20 @@
         reset(getServices().settings);
 
         dpm.setMaximumTimeToLock(admin2, Long.MAX_VALUE);
-        verifyScreenTimeoutCall(Long.MAX_VALUE, UserHandle.USER_SYSTEM);
+        verifyScreenTimeoutCall(Long.MAX_VALUE, CALLER_USER_HANDLE);
         verifyStayOnWhilePluggedCleared(true);
         reset(getServices().powerManagerInternal);
         reset(getServices().settings);
 
         dpm.setMaximumTimeToLock(admin2, 10);
-        verifyScreenTimeoutCall(10L, UserHandle.USER_SYSTEM);
+        verifyScreenTimeoutCall(10L, CALLER_USER_HANDLE);
         verifyStayOnWhilePluggedCleared(true);
         reset(getServices().powerManagerInternal);
         reset(getServices().settings);
 
         // There's no restriction; should be set to MAX.
         dpm.setMaximumTimeToLock(admin2, 0);
-        verifyScreenTimeoutCall(Long.MAX_VALUE, UserHandle.USER_SYSTEM);
+        verifyScreenTimeoutCall(Long.MAX_VALUE, CALLER_USER_HANDLE);
         verifyStayOnWhilePluggedCleared(false);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index 3b10db4..e2a66f0 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.display;
 
-import static android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
-import static android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
 import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
 import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
 import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
@@ -29,6 +27,8 @@
 import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.anyFloat;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
@@ -39,14 +39,10 @@
 
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.hardware.display.BrightnessInfo;
 import android.os.Binder;
 import android.os.Handler;
-import android.os.IThermalEventListener;
-import android.os.IThermalService;
 import android.os.Message;
-import android.os.PowerManager;
-import android.os.Temperature;
-import android.os.Temperature.ThrottlingStatus;
 import android.os.test.TestLooper;
 import android.test.mock.MockContentResolver;
 import android.util.MathUtils;
@@ -66,8 +62,6 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -80,7 +74,6 @@
     private static final long TIME_WINDOW_MILLIS = 55 * 1000;
     private static final long TIME_ALLOWED_IN_WINDOW_MILLIS = 12 * 1000;
     private static final long TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS = 5 * 1000;
-    private static final int THERMAL_STATUS_LIMIT = PowerManager.THERMAL_STATUS_SEVERE;
     private static final boolean ALLOW_IN_LOW_POWER_MODE = false;
 
     private static final float DEFAULT_MIN = 0.01f;
@@ -102,17 +95,13 @@
     @Rule
     public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
-    @Mock IThermalService mThermalServiceMock;
     @Mock Injector mInjectorMock;
     @Mock HighBrightnessModeController.HdrBrightnessDeviceConfig mHdrBrightnessDeviceConfigMock;
 
-    @Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor;
-
     private static final HighBrightnessModeData DEFAULT_HBM_DATA =
             new HighBrightnessModeData(MINIMUM_LUX, TRANSITION_POINT, TIME_WINDOW_MILLIS,
                     TIME_ALLOWED_IN_WINDOW_MILLIS, TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS,
-                    THERMAL_STATUS_LIMIT, ALLOW_IN_LOW_POWER_MODE,
-                    HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT);
+                    ALLOW_IN_LOW_POWER_MODE, HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT);
 
     @Before
     public void setUp() {
@@ -125,8 +114,6 @@
         mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
         when(mContextSpy.getContentResolver()).thenReturn(resolver);
-
-        when(mInjectorMock.getThermalService()).thenReturn(mThermalServiceMock);
     }
 
     /////////////////
@@ -321,34 +308,14 @@
     }
 
     @Test
-    public void testNoHbmInHighThermalState() throws Exception {
+    public void testHbmIsNotTurnedOffInHighThermalState() throws Exception {
         final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
 
-        verify(mThermalServiceMock).registerThermalEventListenerWithType(
-                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
-        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+        // Disabled thermal throttling
+        hbmc.onBrightnessChanged(/*brightness=*/ 1f, /*unthrottledBrightness*/ 1f,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
 
-        // Set the thermal status too high.
-        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
-
-        // Try to go into HBM mode but fail
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        advanceTime(10);
-
-        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
-    }
-
-    @Test
-    public void testHbmTurnsOffInHighThermalState() throws Exception {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-
-        verify(mThermalServiceMock).registerThermalEventListenerWithType(
-                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
-        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
-
-        // Set the thermal status tolerable
-        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_LIGHT));
+        assertFalse(hbmc.isThermalThrottlingActive());
 
         // Try to go into HBM mode
         hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
@@ -357,15 +324,19 @@
 
         assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
 
-        // Set the thermal status too high and verify we're off.
-        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
+        // Enable thermal throttling
+        hbmc.onBrightnessChanged(/*brightness=*/ TRANSITION_POINT - 0.01f,
+                /*unthrottledBrightness*/ 1f, BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL);
         advanceTime(10);
-        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+        assertTrue(hbmc.isThermalThrottlingActive());
 
-        // Set the thermal status low again and verify we're back on.
-        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_SEVERE));
+        // Disabled thermal throttling
+        hbmc.onBrightnessChanged(/*brightness=*/ 1f, /*unthrottledBrightness*/ 1f,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
         advanceTime(1);
         assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+        assertFalse(hbmc.isThermalThrottlingActive());
     }
 
     @Test
@@ -578,33 +549,6 @@
             anyInt());
     }
 
-    // Test reporting of thermal throttling when triggered by HighBrightnessModeController's
-    // internal thermal throttling.
-    @Test
-    public void testHbmStats_InternalThermalOff() throws Exception {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-
-        verify(mThermalServiceMock).registerThermalEventListenerWithType(
-                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
-        final IThermalEventListener thermListener = mThermalEventListenerCaptor.getValue();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-        advanceTime(1);
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        thermListener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
-        advanceTime(10);
-        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT));
-    }
-
     // Test reporting of thermal throttling when triggered externally through
     // HighBrightnessModeController.onBrightnessChanged()
     @Test
@@ -617,14 +561,16 @@
         hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
         // Brightness is unthrottled, HBM brightness granted
-        hbmc.onBrightnessChanged(hbmBrightness, hbmBrightness, BRIGHTNESS_MAX_REASON_NONE);
+        hbmc.onBrightnessChanged(hbmBrightness, hbmBrightness,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
         advanceTime(1);
         verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
             eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
             eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
 
         // Brightness is thermally throttled, HBM brightness denied (NBM brightness granted)
-        hbmc.onBrightnessChanged(nbmBrightness, hbmBrightness, BRIGHTNESS_MAX_REASON_THERMAL);
+        hbmc.onBrightnessChanged(nbmBrightness, hbmBrightness,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL);
         advanceTime(1);
         // We expect HBM mode to remain set to sunlight, indicating that HBMC *allows* this mode.
         // However, we expect the HBM state reported by HBMC to be off, since external thermal
@@ -784,11 +730,7 @@
         mTestLooper.dispatchAll();
     }
 
-    private Temperature getSkinTemp(@ThrottlingStatus int status) {
-        return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
-    }
-
     private void hbmcOnBrightnessChanged(HighBrightnessModeController hbmc, float brightness) {
-        hbmc.onBrightnessChanged(brightness, brightness, BRIGHTNESS_MAX_REASON_NONE);
+        hbmc.onBrightnessChanged(brightness, brightness, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 6907145..42c1fd9 100644
--- a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -84,6 +84,7 @@
 
 import com.android.internal.R;
 import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.display.RefreshRateSettingsUtils;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.test.FakeSettingsProvider;
@@ -158,6 +159,9 @@
         LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
+        clearSmoothDisplaySetting();
+        clearForcePeakRefreshRateSetting();
     }
 
     private DisplayModeDirector createDirectorFromRefreshRateArray(
@@ -919,7 +923,6 @@
     public void testLockFpsForLowZone() throws Exception {
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
-        setPeakRefreshRate(90);
         director.getSettingsObserver().setDefaultRefreshRate(90);
         director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
 
@@ -927,6 +930,7 @@
         config.setRefreshRateInLowZone(90);
         config.setLowDisplayBrightnessThresholds(new int[] { 10 });
         config.setLowAmbientBrightnessThresholds(new int[] { 20 });
+        config.setDefaultPeakRefreshRate(90);
 
         Sensor lightSensor = createLightSensor();
         SensorManager sensorManager = createMockSensorManager(lightSensor);
@@ -977,7 +981,6 @@
     public void testLockFpsForHighZone() throws Exception {
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
-        setPeakRefreshRate(90 /*fps*/);
         director.getSettingsObserver().setDefaultRefreshRate(90);
         director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
 
@@ -985,6 +988,7 @@
         config.setRefreshRateInHighZone(60);
         config.setHighDisplayBrightnessThresholds(new int[] { 255 });
         config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+        config.setDefaultPeakRefreshRate(90);
 
         Sensor lightSensor = createLightSensor();
         SensorManager sensorManager = createMockSensorManager(lightSensor);
@@ -1032,16 +1036,123 @@
     }
 
     @Test
+    public void testSmoothDisplay() {
+        float defaultRefreshRate = 60;
+        int defaultPeakRefreshRate = 100;
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        director.getSettingsObserver().setDefaultRefreshRate(defaultRefreshRate);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setDefaultPeakRefreshRate(defaultPeakRefreshRate);
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+        director.start(sensorManager);
+
+        // Default value of the setting
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                defaultPeakRefreshRate);
+        vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                Float.POSITIVE_INFINITY);
+        vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                defaultRefreshRate);
+
+        setSmoothDisplayEnabled(false);
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+        vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                Float.POSITIVE_INFINITY);
+        vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                defaultRefreshRate);
+
+        setSmoothDisplayEnabled(true);
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext));
+        vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                Float.POSITIVE_INFINITY);
+        vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                defaultRefreshRate);
+    }
+
+    @Test
+    public void testForcePeakRefreshRate() {
+        float defaultRefreshRate = 60;
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        director.getSettingsObserver().setDefaultRefreshRate(defaultRefreshRate);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+        director.start(sensorManager);
+
+        setForcePeakRefreshRateEnabled(false);
+        setSmoothDisplayEnabled(false);
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+        vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+        vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                defaultRefreshRate);
+
+        setForcePeakRefreshRateEnabled(true);
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext));
+        vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */
+                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+        vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                defaultRefreshRate);
+    }
+
+    @Test
     public void testSensorRegistration() {
         // First, configure brightness zones or DMD won't register for sensor data.
         final FakeDeviceConfig config = mInjector.getDeviceConfig();
         config.setRefreshRateInHighZone(60);
         config.setHighDisplayBrightnessThresholds(new int[] { 255 });
         config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+        config.setDefaultPeakRefreshRate(90);
 
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
-        setPeakRefreshRate(90 /*fps*/);
         director.getSettingsObserver().setDefaultRefreshRate(90);
         director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
 
@@ -2417,10 +2528,10 @@
         config.setRefreshRateInHighZone(60);
         config.setHighDisplayBrightnessThresholds(new int[] { 255 });
         config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+        config.setDefaultPeakRefreshRate(90);
 
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
-        setPeakRefreshRate(90 /*fps*/);
         director.getSettingsObserver().setDefaultRefreshRate(90);
         director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
 
@@ -2671,10 +2782,30 @@
         listener.onDisplayChanged(DISPLAY_ID);
     }
 
-    private void setPeakRefreshRate(float fps) {
-        Settings.System.putFloat(mContext.getContentResolver(), Settings.System.PEAK_REFRESH_RATE,
-                 fps);
-        mInjector.notifyPeakRefreshRateChanged();
+    private void setSmoothDisplayEnabled(boolean enabled) {
+        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY,
+                enabled ? 1 : 0);
+        mInjector.notifySmoothDisplaySettingChanged();
+        waitForIdleSync();
+    }
+
+    private void clearSmoothDisplaySetting() {
+        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, -1);
+        mInjector.notifySmoothDisplaySettingChanged();
+        waitForIdleSync();
+    }
+
+    private void setForcePeakRefreshRateEnabled(boolean enabled) {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.FORCE_PEAK_REFRESH_RATE, enabled ? 1 : 0);
+        mInjector.notifyForcePeakRefreshRateSettingChanged();
+        waitForIdleSync();
+    }
+
+    private void clearForcePeakRefreshRateSetting() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.FORCE_PEAK_REFRESH_RATE, -1);
+        mInjector.notifySmoothDisplaySettingChanged();
         waitForIdleSync();
     }
 
@@ -2720,7 +2851,8 @@
     public static class FakesInjector implements DisplayModeDirector.Injector {
         private final FakeDeviceConfig mDeviceConfig;
         private ContentObserver mBrightnessObserver;
-        private ContentObserver mPeakRefreshRateObserver;
+        private ContentObserver mSmoothDisplaySettingObserver;
+        private ContentObserver mForcePeakRefreshRateSettingObserver;
 
         FakesInjector() {
             mDeviceConfig = new FakeDeviceConfig();
@@ -2732,9 +2864,15 @@
         }
 
         @Override
-        public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
+        public void registerSmoothDisplayObserver(@NonNull ContentResolver cr,
                 @NonNull ContentObserver observer) {
-            mPeakRefreshRateObserver = observer;
+            mSmoothDisplaySettingObserver = observer;
+        }
+
+        @Override
+        public void registerForcePeakRefreshRateObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            mForcePeakRefreshRateSettingObserver = observer;
         }
 
         @Override
@@ -2775,10 +2913,17 @@
                     ApplicationProvider.getApplicationContext().getResources());
         }
 
-        void notifyPeakRefreshRateChanged() {
-            if (mPeakRefreshRateObserver != null) {
-                mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/,
-                        PEAK_REFRESH_RATE_URI);
+        void notifySmoothDisplaySettingChanged() {
+            if (mSmoothDisplaySettingObserver != null) {
+                mSmoothDisplaySettingObserver.dispatchChange(false /*selfChange*/,
+                        SMOOTH_DISPLAY_URI);
+            }
+        }
+
+        void notifyForcePeakRefreshRateSettingChanged() {
+            if (mForcePeakRefreshRateSettingObserver != null) {
+                mForcePeakRefreshRateSettingObserver.dispatchChange(false /*selfChange*/,
+                        FORCE_PEAK_REFRESH_RATE_URI);
             }
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
index 1ef1197..d5ad815 100644
--- a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityTaskManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ServiceConnection;
@@ -54,6 +55,10 @@
     private DreamController.Listener mListener;
     @Mock
     private Context mContext;
+
+    @Mock
+    private ActivityTaskManager mActivityTaskManager;
+
     @Mock
     private IBinder mIBinder;
     @Mock
@@ -80,6 +85,10 @@
         when(mIDreamService.asBinder()).thenReturn(mIBinder);
         when(mIBinder.queryLocalInterface(anyString())).thenReturn(mIDreamService);
         when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
+        when(mContext.getSystemService(Context.ACTIVITY_TASK_SERVICE))
+                .thenReturn(mActivityTaskManager);
+        when(mContext.getSystemServiceName(ActivityTaskManager.class))
+                .thenReturn(Context.ACTIVITY_TASK_SERVICE);
 
         mToken = new Binder();
         mDreamName = ComponentName.unflattenFromString("dream");
@@ -104,6 +113,37 @@
     }
 
     @Test
+    public void startDream_dreamListenerNotified() {
+        // Call dream controller to start dreaming.
+        mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+                0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+
+        // Mock service connected.
+        final ServiceConnection serviceConnection = captureServiceConnection();
+        serviceConnection.onServiceConnected(mDreamName, mIBinder);
+        mLooper.dispatchAll();
+
+        // Verify that dream service is called to attach.
+        verify(mListener).onDreamStarted(any());
+    }
+
+    @Test
+    public void stopDream_dreamListenerNotified() {
+        // Start dream.
+        mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+                0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+        captureServiceConnection().onServiceConnected(mDreamName, mIBinder);
+        mLooper.dispatchAll();
+
+        // Stop dream.
+        mDreamController.stopDream(true /*immediate*/, "test stop dream" /*reason*/);
+        mLooper.dispatchAll();
+
+        // Verify that dream service is called to detach.
+        verify(mListener).onDreamStopped(any());
+    }
+
+    @Test
     public void startDream_attachOnServiceConnectedInPreviewMode() throws RemoteException {
         // Call dream controller to start dreaming.
         mDreamController.startDream(mToken, mDreamName, true /*isPreview*/, false /*doze*/,
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index 36c2001..5751db0 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -19,15 +19,25 @@
 
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
 import static android.media.projection.MediaProjectionManager.TYPE_MIRRORING;
+import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
+import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
+import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_TASK;
+import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.testng.Assert.assertThrows;
 
 import android.app.ActivityManagerInternal;
@@ -37,7 +47,9 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.ApplicationInfoFlags;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionCallback;
+import android.media.projection.ReviewGrantedConsentResult;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -56,6 +68,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -108,6 +122,8 @@
     private WindowManagerInternal mWindowManagerInternal;
     @Mock
     private PackageManager mPackageManager;
+    @Captor
+    private ArgumentCaptor<ContentRecordingSession> mSessionCaptor;
 
     @Before
     public void setup() throws Exception {
@@ -154,12 +170,15 @@
 
     @Test
     public void testCreateProjection() throws NameNotFoundException {
-        MediaProjectionManagerService.MediaProjection projection =
-                startProjectionPreconditions(/* packageAttemptedReusingGrantedConsent= */ false);
+        // Create a first projection.
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
         projection.start(mIMediaProjectionCallback);
 
+        // We are allowed to create a new projection.
         MediaProjectionManagerService.MediaProjection secondProjection =
-                startProjectionPreconditions(/* packageAttemptedReusingGrantedConsent= */ false);
+                startProjectionPreconditions();
+
+        // This is a new projection.
         assertThat(secondProjection).isNotNull();
         assertThat(secondProjection).isNotEqualTo(projection);
     }
@@ -167,44 +186,58 @@
     @Test
     public void testCreateProjection_attemptReuse_noPriorProjectionGrant()
             throws NameNotFoundException {
-        MediaProjectionManagerService.MediaProjection projection =
-                startProjectionPreconditions(/* packageAttemptedReusingGrantedConsent= */ false);
+        // Create a first projection.
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
         projection.start(mIMediaProjectionCallback);
 
-        MediaProjectionManagerService.MediaProjection secondProjection =
-                startProjectionPreconditions(/* packageAttemptedReusingGrantedConsent= */ true);
-
-        assertThat(secondProjection).isNotNull();
-        assertThat(secondProjection).isNotEqualTo(projection);
+        // We are not allowed to retrieve the prior projection, since we are not waiting for the
+        // user's consent.
+        assertThat(startReusedProjectionPreconditions()).isNull();
     }
 
     @Test
     public void testCreateProjection_attemptReuse_priorProjectionGrant_notWaiting()
             throws NameNotFoundException {
-        MediaProjectionManagerService.MediaProjection projection =
-                startProjectionPreconditions(/* packageAttemptedReusingGrantedConsent= */ false);
+        // Create a first projection.
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
         projection.start(mIMediaProjectionCallback);
 
-        // Mark this projection as not waiting.
+        // Mark this projection as not waiting for the user to review consent.
         doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
                 any(ContentRecordingSession.class));
         mService.setContentRecordingSession(DISPLAY_SESSION);
 
-        // We are allowed to create another projection.
+        // We are not allowed to retrieve the prior projection, since we are not waiting for the
+        // user's consent.
+        assertThat(startReusedProjectionPreconditions()).isNull();
+    }
+
+    @Test
+    public void testCreateProjection_attemptReuse_priorProjectionGrant_waiting()
+            throws NameNotFoundException {
+        // Create a first projection.
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+
+        // Mark this projection as waiting for the user to review consent.
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+        mService.setContentRecordingSession(mWaitingDisplaySession);
+
+        // We are allowed to create another projection, reusing a prior grant if necessary.
         MediaProjectionManagerService.MediaProjection secondProjection =
-                startProjectionPreconditions(/* packageAttemptedReusingGrantedConsent= */ true);
+                startReusedProjectionPreconditions();
 
+        // This is a new projection, since we are waiting for the user's consent; simply provide
+        // the projection grant from before.
         assertThat(secondProjection).isNotNull();
-
-        // But this is a new projection.
-        assertThat(secondProjection).isNotEqualTo(projection);
+        assertThat(secondProjection).isEqualTo(projection);
     }
 
     @Test
     public void testCreateProjection_attemptReuse_priorProjectionGrant_waiting_differentPackage()
             throws NameNotFoundException {
-        MediaProjectionManagerService.MediaProjection projection =
-                startProjectionPreconditions(/* packageAttemptedReusingGrantedConsent= */ false);
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
         projection.start(mIMediaProjectionCallback);
 
         // Mark this projection as not waiting.
@@ -213,8 +246,7 @@
         // We are allowed to create another projection.
         MediaProjectionManagerService.MediaProjection secondProjection =
                 mService.createProjectionInternal(UID + 10, PACKAGE_NAME + "foo",
-                        TYPE_MIRRORING, /* isPermanentGrant= */ true,
-                        UserHandle.CURRENT, /* packageAttemptedReusingGrantedConsent= */ true);
+                        TYPE_MIRRORING, /* isPermanentGrant= */ true, UserHandle.CURRENT);
 
         assertThat(secondProjection).isNotNull();
 
@@ -366,6 +398,267 @@
         assertThat(mService.isCurrentProjection(projection.asBinder())).isTrue();
     }
 
+    @Test
+    public void testSetUserReviewGrantedConsentResult_noCurrentProjection() {
+        // Gracefully handle invocation without a current projection.
+        mService.setUserReviewGrantedConsentResult(RECORD_CONTENT_DISPLAY,
+                mock(IMediaProjection.class));
+        assertThat(mService.getActiveProjectionInfo()).isNull();
+        verify(mWindowManagerInternal, never()).setContentRecordingSession(any(
+                ContentRecordingSession.class));
+    }
+
+    @Test
+    public void testSetUserReviewGrantedConsentResult_projectionNotCurrent() throws Exception {
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+        assertThat(mService.isCurrentProjection(projection)).isTrue();
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+        // Some other token.
+        final IMediaProjection otherProjection = mock(IMediaProjection.class);
+        doReturn(mock(IBinder.class)).when(otherProjection).asBinder();
+        // Waiting for user to review consent.
+        mService.setContentRecordingSession(mWaitingDisplaySession);
+        mService.setUserReviewGrantedConsentResult(RECORD_CONTENT_DISPLAY, otherProjection);
+
+        // Display result is ignored; only the first session is set.
+        verify(mWindowManagerInternal, times(1)).setContentRecordingSession(
+                eq(mWaitingDisplaySession));
+    }
+
+    @Test
+    public void testSetUserReviewGrantedConsentResult_projectionNull() throws Exception {
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+        assertThat(mService.isCurrentProjection(projection)).isTrue();
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+        // Some other token.
+        final IMediaProjection otherProjection = null;
+        // Waiting for user to review consent.
+        mService.setContentRecordingSession(mWaitingDisplaySession);
+        mService.setUserReviewGrantedConsentResult(RECORD_CONTENT_DISPLAY, otherProjection);
+
+        // Display result is ignored; only the first session is set.
+        verify(mWindowManagerInternal, times(1)).setContentRecordingSession(
+                eq(mWaitingDisplaySession));
+    }
+
+    @Test
+    public void testSetUserReviewGrantedConsentResult_noVirtualDisplay() throws Exception {
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+        // Do not indicate that the virtual display was created.
+        ContentRecordingSession session = mWaitingDisplaySession;
+        session.setVirtualDisplayId(INVALID_DISPLAY);
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+        // Waiting for user to review consent.
+        assertThat(mService.isCurrentProjection(projection)).isTrue();
+        mService.setContentRecordingSession(session);
+
+        mService.setUserReviewGrantedConsentResult(RECORD_CONTENT_DISPLAY, projection);
+        // A session is sent, indicating consent is granted to record but the virtual display isn't
+        // ready yet.
+        verify(mWindowManagerInternal, times(2)).setContentRecordingSession(
+                mSessionCaptor.capture());
+        // Examine latest value.
+        final ContentRecordingSession capturedSession = mSessionCaptor.getValue();
+        assertThat(capturedSession.isWaitingToRecord()).isFalse();
+        assertThat(capturedSession.getVirtualDisplayId()).isEqualTo(INVALID_DISPLAY);
+    }
+
+    @Test
+    public void testSetUserReviewGrantedConsentResult_thenVirtualDisplayCreated() throws Exception {
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+        assertThat(mService.isCurrentProjection(projection)).isTrue();
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+        // Waiting for user to review consent.
+        mService.setContentRecordingSession(mWaitingDisplaySession);
+        mService.setUserReviewGrantedConsentResult(RECORD_CONTENT_DISPLAY, projection);
+
+        // Virtual Display is finally created.
+        projection.notifyVirtualDisplayCreated(10);
+        verifySetSessionWithContent(ContentRecordingSession.RECORD_CONTENT_DISPLAY);
+    }
+
+    @Test
+    public void testSetUserReviewGrantedConsentResult_unknown_updatedSession() throws Exception {
+        testSetUserReviewGrantedConsentResult_userCancelsSession(
+                /* isSetSessionSuccessful= */ true, UNKNOWN);
+    }
+
+    @Test
+    public void testSetUserReviewGrantedConsentResult_unknown_failedToUpdateSession()
+            throws Exception {
+        testSetUserReviewGrantedConsentResult_userCancelsSession(
+                /* isSetSessionSuccessful= */ false, UNKNOWN);
+    }
+
+    @Test
+    public void testSetUserReviewGrantedConsentResult_cancel_updatedSession() throws Exception {
+        testSetUserReviewGrantedConsentResult_userCancelsSession(
+                /* isSetSessionSuccessful= */ true, RECORD_CANCEL);
+    }
+
+    @Test
+    public void testSetUserReviewGrantedConsentResult_cancel_failedToUpdateSession()
+            throws Exception {
+        testSetUserReviewGrantedConsentResult_userCancelsSession(
+                /* isSetSessionSuccessful= */ false, RECORD_CANCEL);
+    }
+
+    /**
+     * Executes and validates scenario where the consent result indicates the projection ends.
+     */
+    private void testSetUserReviewGrantedConsentResult_userCancelsSession(
+            boolean isSetSessionSuccessful, @ReviewGrantedConsentResult int consentResult)
+            throws Exception {
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+        projection.notifyVirtualDisplayCreated(10);
+        // Waiting for user to review consent.
+        assertThat(mService.isCurrentProjection(projection)).isTrue();
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+        mService.setContentRecordingSession(mWaitingDisplaySession);
+
+        doReturn(isSetSessionSuccessful).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+
+        mService.setUserReviewGrantedConsentResult(consentResult, projection);
+        verify(mWindowManagerInternal, atLeastOnce()).setContentRecordingSession(
+                mSessionCaptor.capture());
+        // Null value to stop session.
+        assertThat(mSessionCaptor.getValue()).isNull();
+        assertThat(mService.isCurrentProjection(projection.asBinder())).isFalse();
+    }
+
+    @Test
+    public void testSetUserReviewGrantedConsentResult_displayMirroring_startedSession()
+            throws NameNotFoundException {
+        testSetUserReviewGrantedConsentResult_startedSession(RECORD_CONTENT_DISPLAY,
+                ContentRecordingSession.RECORD_CONTENT_DISPLAY);
+    }
+
+    @Test
+    public void testSetUserReviewGrantedConsentResult_displayMirroring_failedToStartSession()
+            throws NameNotFoundException {
+        testSetUserReviewGrantedConsentResult_failedToStartSession(RECORD_CONTENT_DISPLAY,
+                ContentRecordingSession.RECORD_CONTENT_DISPLAY);
+    }
+
+    @Test
+    public void testSetUserReviewGrantedConsentResult_taskMirroring_startedSession()
+            throws NameNotFoundException {
+        testSetUserReviewGrantedConsentResult_startedSession(RECORD_CONTENT_TASK,
+                ContentRecordingSession.RECORD_CONTENT_TASK);
+    }
+
+    @Test
+    public void testSetUserReviewGrantedConsentResult_taskMirroring_failedToStartSession()
+            throws NameNotFoundException {
+        testSetUserReviewGrantedConsentResult_failedToStartSession(RECORD_CONTENT_TASK,
+                ContentRecordingSession.RECORD_CONTENT_TASK);
+    }
+
+    /**
+     * Executes and validates scenario where the consent result indicates the projection continues,
+     * and successfully started projection.
+     */
+    private void testSetUserReviewGrantedConsentResult_startedSession(
+            @ReviewGrantedConsentResult int consentResult,
+            @ContentRecordingSession.RecordContent int recordedContent)
+            throws NameNotFoundException {
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.setLaunchCookie(mock(IBinder.class));
+        projection.start(mIMediaProjectionCallback);
+        projection.notifyVirtualDisplayCreated(10);
+        // Waiting for user to review consent.
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+        mService.setContentRecordingSession(mWaitingDisplaySession);
+
+        mService.setUserReviewGrantedConsentResult(consentResult, projection);
+        verifySetSessionWithContent(recordedContent);
+        assertThat(mService.isCurrentProjection(projection)).isTrue();
+    }
+
+    /**
+     * Executes and validates scenario where the consent result indicates the projection continues,
+     * but unable to continue projection.
+     */
+    private void testSetUserReviewGrantedConsentResult_failedToStartSession(
+            @ReviewGrantedConsentResult int consentResult,
+            @ContentRecordingSession.RecordContent int recordedContent)
+            throws NameNotFoundException {
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+        projection.notifyVirtualDisplayCreated(10);
+        // Waiting for user to review consent.
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                eq(mWaitingDisplaySession));
+        mService.setContentRecordingSession(mWaitingDisplaySession);
+
+        doReturn(false).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+
+        mService.setUserReviewGrantedConsentResult(consentResult, projection);
+        verifySetSessionWithContent(recordedContent);
+        assertThat(mService.isCurrentProjection(projection.asBinder())).isFalse();
+    }
+
+    @Test
+    public void testSetUserReviewGrantedConsentResult_displayMirroring_noPriorSession()
+            throws NameNotFoundException {
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.setLaunchCookie(mock(IBinder.class));
+        projection.start(mIMediaProjectionCallback);
+        // Skip setting the prior session details.
+
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+
+        mService.setUserReviewGrantedConsentResult(RECORD_CONTENT_DISPLAY, projection);
+        // Result is ignored & session not updated.
+        verify(mWindowManagerInternal, never()).setContentRecordingSession(any(
+                ContentRecordingSession.class));
+        // Current session continues.
+        assertThat(mService.isCurrentProjection(projection)).isTrue();
+    }
+
+    @Test
+    public void testSetUserReviewGrantedConsentResult_displayMirroring_sessionNotWaiting()
+            throws NameNotFoundException {
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.setLaunchCookie(mock(IBinder.class));
+        projection.start(mIMediaProjectionCallback);
+        // Session is not waiting for user's consent.
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+        mService.setContentRecordingSession(DISPLAY_SESSION);
+
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+
+        mService.setUserReviewGrantedConsentResult(RECORD_CONTENT_DISPLAY, projection);
+        // Result is ignored; only the original session was ever sent.
+        verify(mWindowManagerInternal).setContentRecordingSession(eq(
+                DISPLAY_SESSION));
+        // Current session continues.
+        assertThat(mService.isCurrentProjection(projection)).isTrue();
+    }
+
+    private void verifySetSessionWithContent(@ContentRecordingSession.RecordContent int content) {
+        verify(mWindowManagerInternal, atLeastOnce()).setContentRecordingSession(
+                mSessionCaptor.capture());
+        assertThat(mSessionCaptor.getValue()).isNotNull();
+        assertThat(mSessionCaptor.getValue().getContentToRecord()).isEqualTo(content);
+    }
+
     // Set up preconditions for creating a projection.
     private MediaProjectionManagerService.MediaProjection createProjectionPreconditions(
             MediaProjectionManagerService service)
@@ -373,14 +666,7 @@
         doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(),
                 any(ApplicationInfoFlags.class), any(UserHandle.class));
         return service.createProjectionInternal(UID, PACKAGE_NAME,
-                TYPE_MIRRORING, /* isPermanentGrant= */ true, UserHandle.CURRENT,
-                /* packageAttemptedReusingGrantedConsent= */ false);
-    }
-
-    // Set up preconditions for creating a projection.
-    private MediaProjectionManagerService.MediaProjection createProjectionPreconditions()
-            throws NameNotFoundException {
-        return createProjectionPreconditions(mService);
+                TYPE_MIRRORING, /* isPermanentGrant= */ true, UserHandle.CURRENT);
     }
 
     // Set up preconditions for starting a projection, with no foreground service requirements.
@@ -391,19 +677,6 @@
         return createProjectionPreconditions(service);
     }
 
-    // Set up preconditions for starting a projection, specifying if it is possible to reuse the
-    // the current projection.
-    private MediaProjectionManagerService.MediaProjection startProjectionPreconditions(
-            boolean packageAttemptedReusingGrantedConsent)
-            throws NameNotFoundException {
-        mAppInfo.privateFlags |= PRIVATE_FLAG_PRIVILEGED;
-        doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(),
-                any(ApplicationInfoFlags.class), any(UserHandle.class));
-        return mService.createProjectionInternal(UID, PACKAGE_NAME,
-                TYPE_MIRRORING, /* isPermanentGrant= */ true, UserHandle.CURRENT,
-                packageAttemptedReusingGrantedConsent);
-    }
-
     // Set up preconditions for starting a projection, with no foreground service requirements.
     private MediaProjectionManagerService.MediaProjection startProjectionPreconditions()
             throws NameNotFoundException {
@@ -411,6 +684,15 @@
         return createProjectionPreconditions(mService);
     }
 
+    // Set up preconditions for starting a projection, retrieving a pre-existing projection.
+    private MediaProjectionManagerService.MediaProjection startReusedProjectionPreconditions()
+            throws NameNotFoundException {
+        mAppInfo.privateFlags |= PRIVATE_FLAG_PRIVILEGED;
+        doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(),
+                any(ApplicationInfoFlags.class), any(UserHandle.class));
+        return mService.getProjectionInternal(UID, PACKAGE_NAME);
+    }
+
     private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub {
         @Override
         public void onStop() throws RemoteException {
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 dd9f3cb..5dbc6ab 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -40,7 +40,6 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MAX;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
-import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
@@ -358,7 +357,7 @@
     private static final int NOTIFICATION_LOCATION_UNKNOWN = 0;
 
     private static final String VALID_CONVO_SHORTCUT_ID = "shortcut";
-
+    private static final String SEARCH_SELECTOR_PKG = "searchSelector";
     @Mock
     private NotificationListeners mListeners;
     @Mock
@@ -549,6 +548,10 @@
         // apps allowed as convos
         mService.setStringArrayResourceValue(PKG_O);
 
+        TestableResources tr = mContext.getOrCreateTestableResources();
+        tr.addOverride(com.android.internal.R.string.config_defaultSearchSelectorPackageName,
+                SEARCH_SELECTOR_PKG);
+
         mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
         mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
                 mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
@@ -5308,7 +5311,7 @@
         parser.setInput(new BufferedInputStream(
                 new ByteArrayInputStream(baos.toByteArray())), null);
         NotificationChannel restored = new NotificationChannel("a", "ab", IMPORTANCE_DEFAULT);
-        restored.populateFromXmlForRestore(parser, getContext());
+        restored.populateFromXmlForRestore(parser, true, getContext());
 
         assertNull(restored.getSound());
     }
@@ -10636,6 +10639,34 @@
     }
 
     @Test
+    public void fixSystemNotification_defaultSearchSelectior_withOnGoingFlag_nondismissible()
+            throws Exception {
+        final ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = SEARCH_SELECTOR_PKG;
+        ai.uid = mUid;
+        ai.flags |= ApplicationInfo.FLAG_SYSTEM;
+
+        when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenReturn(ai);
+        when(mAppOpsManager.checkOpNoThrow(
+                AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid,
+                ai.packageName)).thenReturn(AppOpsManager.MODE_IGNORED);
+        // Given: a notification from an app on the system partition has the flag
+        // FLAG_ONGOING_EVENT set
+        // feature flag: ALLOW_DISMISS_ONGOING is on
+        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
+        Notification n = new Notification.Builder(mContext, "test")
+                .setOngoing(true)
+                .build();
+
+        // When: fix the notification with NotificationManagerService
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
+
+        // Then: the notification's flag FLAG_NO_DISMISS should be set
+        assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
+    }
+
+    @Test
     public void fixCallNotification_withOnGoingFlag_shouldNotBeNonDismissible()
             throws Exception {
         // Given: a call notification has the flag FLAG_ONGOING_EVENT set
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index f6d10b9..c78b03e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -140,6 +140,7 @@
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
@@ -170,6 +171,12 @@
             Uri.parse("content://" + TEST_AUTHORITY
                     + "/internal/audio/media/10?title=Test&canonical=1");
 
+    private static final Uri ANDROID_RES_SOUND_URI =
+            Uri.parse("android.resource://" + TEST_AUTHORITY + "/raw/test");
+
+    private static final Uri FILE_SOUND_URI =
+            Uri.parse("file://" + TEST_AUTHORITY + "/product/media/test.ogg");
+
     @Mock PermissionHelper mPermissionHelper;
     @Mock RankingHandler mHandler;
     @Mock PackageManager mPm;
@@ -1338,6 +1345,57 @@
         assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
     }
 
+    /**
+     * Test sound Uri restore retry behavior when channel is restored before package
+     *  and then package is installed.
+     */
+    @Test
+    public void testRestoreXml_withNonExistentCanonicalizedSoundUriAndMissingPackage()
+            throws Exception {
+        // canonicalization returns CANONICAL_SOUND_URI for getSoundForBackup (backup part)
+        doReturn(CANONICAL_SOUND_URI)
+                .when(mTestIContentProvider).canonicalize(any(), eq(SOUND_URI));
+
+        NotificationChannel channel =
+                new NotificationChannel("id", "name", IMPORTANCE_LOW);
+        channel.setSound(SOUND_URI, mAudioAttributes);
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
+                USER_SYSTEM, channel.getId());
+
+        // canonicalization / uncanonicalization returns null for the restore part
+        doReturn(null)
+                .when(mTestIContentProvider).canonicalize(any(), eq(CANONICAL_SOUND_URI));
+        doReturn(null)
+                .when(mTestIContentProvider).uncanonicalize(any(), any());
+
+        // simulate package not installed
+        when(mPm.getPackageUidAsUser(PKG_N_MR1, USER_SYSTEM)).thenReturn(UNKNOWN_UID);
+        when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt())).thenThrow(
+                new PackageManager.NameNotFoundException());
+
+        loadStreamXml(baos, true, USER_SYSTEM);
+
+        // 1st restore pass fails
+        NotificationChannel actualChannel = mHelper.getNotificationChannel(
+                PKG_N_MR1, UNKNOWN_UID, channel.getId(), false);
+        // sound is CANONICAL_SOUND_URI, unchanged from backup
+        assertEquals(CANONICAL_SOUND_URI, actualChannel.getSound());
+        // sound is flagged as not restored
+        assertFalse(actualChannel.isSoundRestored());
+
+        // package is "installed"
+        when(mPm.getPackageUidAsUser(PKG_N_MR1, USER_SYSTEM)).thenReturn(UID_N_MR1);
+
+        // Trigger 2nd restore pass
+        mHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_N_MR1},
+                new int[]{UID_N_MR1});
+
+        // sound is flagged as restored and set to default URI
+        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
+        assertTrue(actualChannel.isSoundRestored());
+    }
+
 
     /**
      * Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to
@@ -1363,7 +1421,9 @@
                 backupWithUncanonicalizedSoundUri.getBytes(), true, USER_SYSTEM);
 
         NotificationChannel actualChannel = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, id, false);
+
         assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
+        assertTrue(actualChannel.isSoundRestored());
     }
 
     @Test
@@ -1389,6 +1449,73 @@
     }
 
     @Test
+    public void testBackupRestoreXml_withAndroidResourceSoundUri() throws Exception {
+        // Mock ContentResolver.getResourceId:
+        // throw exception on restore 1st pass => simulate app not installed yet
+        // then return a valid resource on package update => sim. app installed
+        ContentResolver contentResolver = mock(ContentResolver.class);
+        when(mContext.getContentResolver()).thenReturn(contentResolver);
+        ContentResolver.OpenResourceIdResult resId = mock(
+                ContentResolver.OpenResourceIdResult.class);
+        when(contentResolver.getResourceId(ANDROID_RES_SOUND_URI)).thenReturn(resId).thenThrow(
+                new FileNotFoundException("")).thenReturn(resId);
+
+        mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
+                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+
+        NotificationChannel channel =
+                new NotificationChannel("id", "name", IMPORTANCE_LOW);
+        channel.setSound(ANDROID_RES_SOUND_URI, mAudioAttributes);
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
+                USER_SYSTEM, channel.getId());
+
+        // simulate package not installed
+        when(mPm.getPackageUidAsUser(PKG_N_MR1, USER_SYSTEM)).thenReturn(UNKNOWN_UID);
+        when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt())).thenThrow(
+                new PackageManager.NameNotFoundException());
+
+        loadStreamXml(baos, true, USER_SYSTEM);
+
+        NotificationChannel actualChannel = mHelper.getNotificationChannel(
+                PKG_N_MR1, UNKNOWN_UID, channel.getId(), false);
+        // sound is ANDROID_RES_SOUND_URI, unchanged from backup
+        assertEquals(ANDROID_RES_SOUND_URI, actualChannel.getSound());
+        // sound is flagged as not restored
+        assertFalse(actualChannel.isSoundRestored());
+
+        // package is "installed"
+        when(mPm.getPackageUidAsUser(PKG_N_MR1, USER_SYSTEM)).thenReturn(UID_N_MR1);
+
+        // Trigger 2nd restore pass
+        mHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_N_MR1},
+                new int[]{UID_N_MR1});
+
+        // sound is flagged as restored
+        assertEquals(ANDROID_RES_SOUND_URI, actualChannel.getSound());
+        assertTrue(actualChannel.isSoundRestored());
+    }
+
+    @Test
+    public void testBackupRestoreXml_withFileResourceSoundUri() throws Exception {
+        NotificationChannel channel =
+                new NotificationChannel("id", "name", IMPORTANCE_LOW);
+        channel.setSound(FILE_SOUND_URI, mAudioAttributes);
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
+                USER_SYSTEM, channel.getId());
+
+        loadStreamXml(baos, true, USER_SYSTEM);
+
+        NotificationChannel actualChannel = mHelper.getNotificationChannel(
+                PKG_N_MR1, UID_N_MR1, channel.getId(), false);
+        // sound is FILE_SOUND_URI, unchanged from backup
+        assertEquals(FILE_SOUND_URI, actualChannel.getSound());
+        // sound is flagged as restored
+        assertTrue(actualChannel.isSoundRestored());
+    }
+
+    @Test
     public void testChannelXml_backup() throws Exception {
         NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
         NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
diff --git a/services/tests/voiceinteractiontests/TEST_MAPPING b/services/tests/voiceinteractiontests/TEST_MAPPING
new file mode 100644
index 0000000..6cbc49a
--- /dev/null
+++ b/services/tests/voiceinteractiontests/TEST_MAPPING
@@ -0,0 +1,17 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksVoiceInteractionTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "FrameworksVoiceInteractionTests"
+    }
+  ]
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index 77efc4b..ddd630e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -48,6 +48,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
 
 /**
  * Test class for {@link BLASTSyncEngine}.
@@ -225,7 +227,7 @@
         parentWC.onSyncFinishedDrawing();
         topChildWC.onSyncFinishedDrawing();
         // Even though bottom isn't finished, we should see callback because it is occluded by top.
-        assertFalse(botChildWC.isSyncFinished());
+        assertFalse(botChildWC.isSyncFinished(botChildWC.getSyncGroup()));
         bse.onSurfacePlacement();
         verify(listener, times(1)).onTransactionReady(eq(id), notNull());
 
@@ -416,9 +418,217 @@
         assertTrue(bse.isReady(nextId[0]));
     }
 
+    @Test
+    public void testStratifiedParallel() {
+        TestWindowContainer parentWC = new TestWindowContainer(mWm, true /* waiter */);
+        TestWindowContainer childWC = new TestWindowContainer(mWm, true /* waiter */);
+        parentWC.addChild(childWC, POSITION_TOP);
+        childWC.mVisibleRequested = true;
+        childWC.mFillsParent = true;
+
+        final BLASTSyncEngine bse = createTestBLASTSyncEngine();
+
+        BLASTSyncEngine.TransactionReadyListener listenerChild = mock(
+                BLASTSyncEngine.TransactionReadyListener.class);
+        BLASTSyncEngine.TransactionReadyListener listenerParent = mock(
+                BLASTSyncEngine.TransactionReadyListener.class);
+
+        // Start a sync-set for the "inner" stuff
+        int childSync = startSyncSet(bse, listenerChild);
+        bse.addToSyncSet(childSync, childWC);
+        bse.setReady(childSync);
+
+        // Start sync-set for the "outer" stuff but explicitly parallel (it should ignore child)
+        int parentSync = startSyncSet(bse, listenerParent, true /* parallel */);
+        bse.addToSyncSet(parentSync, parentWC);
+        bse.setReady(parentSync);
+
+        bse.onSurfacePlacement();
+        // Nothing should have happened yet
+        verify(listenerChild, times(0)).onTransactionReady(anyInt(), any());
+        verify(listenerParent, times(0)).onTransactionReady(anyInt(), any());
+
+        // Now, make PARENT ready, since they are in parallel, this should work
+        parentWC.onSyncFinishedDrawing();
+        bse.onSurfacePlacement();
+
+        // Parent should become ready while child is still waiting.
+        verify(listenerParent, times(1)).onTransactionReady(eq(parentSync), notNull());
+        verify(listenerChild, times(0)).onTransactionReady(anyInt(), any());
+
+        // Child should still work
+        childWC.onSyncFinishedDrawing();
+        bse.onSurfacePlacement();
+        verify(listenerChild, times(1)).onTransactionReady(eq(childSync), notNull());
+    }
+
+    @Test
+    public void testDependencies() {
+        TestWindowContainer parentWC = new TestWindowContainer(mWm, true /* waiter */);
+        TestWindowContainer childWC = new TestWindowContainer(mWm, true /* waiter */);
+        TestWindowContainer childWC2 = new TestWindowContainer(mWm, true /* waiter */);
+        parentWC.addChild(childWC, POSITION_TOP);
+        childWC.mVisibleRequested = true;
+        childWC.mFillsParent = true;
+        childWC2.mVisibleRequested = true;
+        childWC2.mFillsParent = true;
+
+        final BLASTSyncEngine bse = createTestBLASTSyncEngine();
+
+        BLASTSyncEngine.TransactionReadyListener listener = mock(
+                BLASTSyncEngine.TransactionReadyListener.class);
+
+        // This is non-parallel, so it is waiting on the child as-well
+        int sync1 = startSyncSet(bse, listener);
+        bse.addToSyncSet(sync1, parentWC);
+        bse.setReady(sync1);
+
+        // Create one which will end-up depending on the *next* sync
+        int sync2 = startSyncSet(bse, listener, true /* parallel */);
+
+        // If another sync tries to sync on the same subtree, it must now serialize with the other.
+        int sync3 = startSyncSet(bse, listener, true /* parallel */);
+        bse.addToSyncSet(sync3, childWC);
+        bse.addToSyncSet(sync3, childWC2);
+        bse.setReady(sync3);
+
+        // This will depend on sync3.
+        int sync4 = startSyncSet(bse, listener, true /* parallel */);
+        bse.addToSyncSet(sync4, childWC2);
+        bse.setReady(sync4);
+
+        // This makes sync2 depend on sync3. Since both sync2 and sync4 depend on sync3, when sync3
+        // finishes, sync2 should run first since it was created first.
+        bse.addToSyncSet(sync2, childWC2);
+        bse.setReady(sync2);
+
+        childWC.onSyncFinishedDrawing();
+        childWC2.onSyncFinishedDrawing();
+        bse.onSurfacePlacement();
+
+        // Nothing should be ready yet since everything ultimately depends on sync1.
+        verify(listener, times(0)).onTransactionReady(anyInt(), any());
+
+        parentWC.onSyncFinishedDrawing();
+        bse.onSurfacePlacement();
+
+        // They should all be ready, now, so just verify that the order is expected
+        InOrder readyOrder = Mockito.inOrder(listener);
+        // sync1 is the first one, so it should call ready first.
+        readyOrder.verify(listener).onTransactionReady(eq(sync1), any());
+        // everything else depends on sync3, so it should call ready next.
+        readyOrder.verify(listener).onTransactionReady(eq(sync3), any());
+        // both sync2 and sync4 depend on sync3, but sync2 started first, so it should go next.
+        readyOrder.verify(listener).onTransactionReady(eq(sync2), any());
+        readyOrder.verify(listener).onTransactionReady(eq(sync4), any());
+    }
+
+    @Test
+    public void testStratifiedParallelParentFirst() {
+        TestWindowContainer parentWC = new TestWindowContainer(mWm, true /* waiter */);
+        TestWindowContainer childWC = new TestWindowContainer(mWm, true /* waiter */);
+        parentWC.addChild(childWC, POSITION_TOP);
+        childWC.mVisibleRequested = true;
+        childWC.mFillsParent = true;
+
+        final BLASTSyncEngine bse = createTestBLASTSyncEngine();
+
+        BLASTSyncEngine.TransactionReadyListener listener = mock(
+                BLASTSyncEngine.TransactionReadyListener.class);
+
+        // This is parallel, so it should ignore children
+        int sync1 = startSyncSet(bse, listener, true /* parallel */);
+        bse.addToSyncSet(sync1, parentWC);
+        bse.setReady(sync1);
+
+        int sync2 = startSyncSet(bse, listener, true /* parallel */);
+        bse.addToSyncSet(sync2, childWC);
+        bse.setReady(sync2);
+
+        childWC.onSyncFinishedDrawing();
+        bse.onSurfacePlacement();
+
+        // Sync2 should have run in parallel
+        verify(listener, times(1)).onTransactionReady(eq(sync2), any());
+        verify(listener, times(0)).onTransactionReady(eq(sync1), any());
+
+        parentWC.onSyncFinishedDrawing();
+        bse.onSurfacePlacement();
+
+        verify(listener, times(1)).onTransactionReady(eq(sync1), any());
+    }
+
+    @Test
+    public void testDependencyCycle() {
+        TestWindowContainer parentWC = new TestWindowContainer(mWm, true /* waiter */);
+        TestWindowContainer childWC = new TestWindowContainer(mWm, true /* waiter */);
+        TestWindowContainer childWC2 = new TestWindowContainer(mWm, true /* waiter */);
+        TestWindowContainer childWC3 = new TestWindowContainer(mWm, true /* waiter */);
+        parentWC.addChild(childWC, POSITION_TOP);
+        childWC.mVisibleRequested = true;
+        childWC.mFillsParent = true;
+        childWC2.mVisibleRequested = true;
+        childWC2.mFillsParent = true;
+        childWC3.mVisibleRequested = true;
+        childWC3.mFillsParent = true;
+
+        final BLASTSyncEngine bse = createTestBLASTSyncEngine();
+
+        BLASTSyncEngine.TransactionReadyListener listener = mock(
+                BLASTSyncEngine.TransactionReadyListener.class);
+
+        // This is non-parallel, so it is waiting on the child as-well
+        int sync1 = startSyncSet(bse, listener);
+        bse.addToSyncSet(sync1, parentWC);
+        bse.setReady(sync1);
+
+        // Sync 2 depends on sync1 AND childWC2
+        int sync2 = startSyncSet(bse, listener, true /* parallel */);
+        bse.addToSyncSet(sync2, childWC);
+        bse.addToSyncSet(sync2, childWC2);
+        bse.setReady(sync2);
+
+        // Sync 3 depends on sync2 AND childWC3
+        int sync3 = startSyncSet(bse, listener, true /* parallel */);
+        bse.addToSyncSet(sync3, childWC2);
+        bse.addToSyncSet(sync3, childWC3);
+        bse.setReady(sync3);
+
+        // Now make sync1 depend on WC3 (which would make it depend on sync3). This would form
+        // a cycle, so it should instead move childWC3 into sync1.
+        bse.addToSyncSet(sync1, childWC3);
+
+        // Sync3 should no-longer have childWC3 as a root-member since a window can currently only
+        // be directly watched by 1 syncgroup maximum (due to implementation of isSyncFinished).
+        assertFalse(bse.getSyncSet(sync3).mRootMembers.contains(childWC3));
+
+        childWC3.onSyncFinishedDrawing();
+        childWC2.onSyncFinishedDrawing();
+        parentWC.onSyncFinishedDrawing();
+        bse.onSurfacePlacement();
+
+        // make sure sync3 hasn't run even though all its (original) members are ready
+        verify(listener, times(0)).onTransactionReady(anyInt(), any());
+
+        // Now finish the last container and make sure everything finishes (didn't "deadlock" due
+        // to a dependency cycle.
+        childWC.onSyncFinishedDrawing();
+        bse.onSurfacePlacement();
+
+        InOrder readyOrder = Mockito.inOrder(listener);
+        readyOrder.verify(listener).onTransactionReady(eq(sync1), any());
+        readyOrder.verify(listener).onTransactionReady(eq(sync2), any());
+        readyOrder.verify(listener).onTransactionReady(eq(sync3), any());
+    }
+
     static int startSyncSet(BLASTSyncEngine engine,
             BLASTSyncEngine.TransactionReadyListener listener) {
-        return engine.startSyncSet(listener, BLAST_TIMEOUT_DURATION, "Test");
+        return engine.startSyncSet(listener, BLAST_TIMEOUT_DURATION, "Test", false /* parallel */);
+    }
+
+    static int startSyncSet(BLASTSyncEngine engine,
+            BLASTSyncEngine.TransactionReadyListener listener, boolean parallel) {
+        return engine.startSyncSet(listener, BLAST_TIMEOUT_DURATION, "Test", parallel);
     }
 
     static class TestWindowContainer extends WindowContainer {
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 653b52b..0dac346 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1196,7 +1196,8 @@
 
         player.start();
         player.finish();
-        app.getTask().finishSync(mWm.mTransactionFactory.get(), false /* cancel */);
+        app.getTask().finishSync(mWm.mTransactionFactory.get(), app.getTask().getSyncGroup(),
+                false /* cancel */);
 
         // The open transition is finished. Continue to play seamless display change transition,
         // so the previous async rotation controller should still exist.
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 984b868..4530963 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -394,7 +394,7 @@
         assertTrue(token.isVisible());
 
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
-        token.finishSync(t, false /* cancel */);
+        token.finishSync(t, token.getSyncGroup(), false /* cancel */);
         transit.onTransactionReady(transit.getSyncId(), t);
         dc.mTransitionController.finishTransition(transit);
         assertFalse(wallpaperWindow.isVisible());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index d19c996..600681f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1006,7 +1006,8 @@
         BLASTSyncEngine.TransactionReadyListener transactionListener =
                 mock(BLASTSyncEngine.TransactionReadyListener.class);
 
-        final int id = bse.startSyncSet(transactionListener, BLAST_TIMEOUT_DURATION, "Test");
+        final int id = bse.startSyncSet(transactionListener, BLAST_TIMEOUT_DURATION, "Test",
+                false /* parallel */);
         bse.addToSyncSet(id, task);
         bse.setReady(id);
         bse.onSurfacePlacement();
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 07dc1c6..18d0c5a 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -49,6 +49,7 @@
 import android.telephony.TelephonyManager;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 
 import java.io.FileDescriptor;
@@ -129,6 +130,9 @@
     private final Function<SoundTrigger.StatusListener, SoundTriggerModule> mModuleProvider;
     private final Supplier<List<ModuleProperties>> mModulePropertiesProvider;
 
+    @GuardedBy("mLock")
+    private boolean mIsDetached = false;
+
     SoundTriggerHelper(Context context,
             @NonNull Function<SoundTrigger.StatusListener, SoundTriggerModule> moduleProvider,
             int moduleId,
@@ -184,7 +188,7 @@
      * recognition.
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    int startGenericRecognition(UUID modelId, GenericSoundModel soundModel,
+    public int startGenericRecognition(UUID modelId, GenericSoundModel soundModel,
             IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
             boolean runInBatterySaverMode) {
         MetricsLogger.count(mContext, "sth_start_recognition", 1);
@@ -195,6 +199,9 @@
         }
 
         synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             ModelData modelData = getOrCreateGenericModelDataLocked(modelId);
             if (modelData == null) {
                 Slog.w(TAG, "Irrecoverable error occurred, check UUID / sound model data.");
@@ -214,7 +221,7 @@
      * @param callback The callback for the recognition events related to the given keyphrase.
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    int startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
+    public int startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
             IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
             boolean runInBatterySaverMode) {
         synchronized (mLock) {
@@ -223,6 +230,10 @@
                 return STATUS_ERROR;
             }
 
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
+
             if (DBG) {
                 Slog.d(TAG, "startKeyphraseRecognition for keyphraseId=" + keyphraseId
                         + " soundModel=" + soundModel + ", callback=" + callback.asBinder()
@@ -311,7 +322,7 @@
      * for the recognition.
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    int startRecognition(SoundModel soundModel, ModelData modelData,
+    private int startRecognition(SoundModel soundModel, ModelData modelData,
             IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
             int keyphraseId, boolean runInBatterySaverMode) {
         synchronized (mLock) {
@@ -385,7 +396,7 @@
      *
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback) {
+    public int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback) {
         synchronized (mLock) {
             MetricsLogger.count(mContext, "sth_stop_recognition", 1);
             if (callback == null || modelId == null) {
@@ -393,7 +404,9 @@
                         modelId);
                 return STATUS_ERROR;
             }
-
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             ModelData modelData = mModelDataMap.get(modelId);
             if (modelData == null || !modelData.isGenericModel()) {
                 Slog.w(TAG, "Attempting stopRecognition on invalid model with id:" + modelId);
@@ -418,7 +431,7 @@
      *
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback) {
+    public int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback) {
         synchronized (mLock) {
             MetricsLogger.count(mContext, "sth_stop_recognition", 1);
             if (callback == null) {
@@ -426,7 +439,9 @@
                         keyphraseId);
                 return STATUS_ERROR;
             }
-
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
             if (modelData == null || !modelData.isKeyphraseModel()) {
                 Slog.w(TAG, "No model exists for given keyphrase Id " + keyphraseId);
@@ -538,6 +553,11 @@
     }
 
     public ModuleProperties getModuleProperties() {
+        synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
+        }
         for (ModuleProperties moduleProperties : mModulePropertiesProvider.get()) {
             if (moduleProperties.getId() == mModuleId) {
                 return moduleProperties;
@@ -547,7 +567,7 @@
         return null;
     }
 
-    int unloadKeyphraseSoundModel(int keyphraseId) {
+    public int unloadKeyphraseSoundModel(int keyphraseId) {
         synchronized (mLock) {
             MetricsLogger.count(mContext, "sth_unload_keyphrase_sound_model", 1);
             ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
@@ -555,7 +575,9 @@
                     || !modelData.isKeyphraseModel()) {
                 return STATUS_ERROR;
             }
-
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             // Stop recognition if it's the current one.
             modelData.setRequested(false);
             int status = updateRecognitionLocked(modelData, false);
@@ -574,12 +596,15 @@
         }
     }
 
-    int unloadGenericSoundModel(UUID modelId) {
+    public int unloadGenericSoundModel(UUID modelId) {
         synchronized (mLock) {
             MetricsLogger.count(mContext, "sth_unload_generic_sound_model", 1);
             if (modelId == null || mModule == null) {
                 return STATUS_ERROR;
             }
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             ModelData modelData = mModelDataMap.get(modelId);
             if (modelData == null || !modelData.isGenericModel()) {
                 Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" +
@@ -615,19 +640,25 @@
         }
     }
 
-    boolean isRecognitionRequested(UUID modelId) {
+    public boolean isRecognitionRequested(UUID modelId) {
         synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             ModelData modelData = mModelDataMap.get(modelId);
             return modelData != null && modelData.isRequested();
         }
     }
 
-    int getGenericModelState(UUID modelId) {
+    public int getGenericModelState(UUID modelId) {
         synchronized (mLock) {
             MetricsLogger.count(mContext, "sth_get_generic_model_state", 1);
             if (modelId == null || mModule == null) {
                 return STATUS_ERROR;
             }
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             ModelData modelData = mModelDataMap.get(modelId);
             if (modelData == null || !modelData.isGenericModel()) {
                 Slog.w(TAG, "GetGenericModelState error: Invalid generic model id:" +
@@ -647,19 +678,20 @@
         }
     }
 
-    int getKeyphraseModelState(UUID modelId) {
-        Slog.w(TAG, "GetKeyphraseModelState error: Not implemented");
-        return STATUS_ERROR;
-    }
-
-    int setParameter(UUID modelId, @ModelParams int modelParam, int value) {
+    public int setParameter(UUID modelId, @ModelParams int modelParam, int value) {
         synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             return setParameterLocked(mModelDataMap.get(modelId), modelParam, value);
         }
     }
 
-    int setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value) {
+    public int setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value) {
         synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             return setParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam, value);
         }
     }
@@ -678,14 +710,20 @@
         return mModule.setParameter(modelData.getHandle(), modelParam, value);
     }
 
-    int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
+    public int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
         synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             return getParameterLocked(mModelDataMap.get(modelId), modelParam);
         }
     }
 
-    int getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
+    public int getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
         synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             return getParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
         }
     }
@@ -707,15 +745,21 @@
     }
 
     @Nullable
-    ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
+    public ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
         synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             return queryParameterLocked(mModelDataMap.get(modelId), modelParam);
         }
     }
 
     @Nullable
-    ModelParamRange queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
+    public ModelParamRange queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
         synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             return queryParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
         }
     }
@@ -1115,12 +1159,14 @@
      */
     public void detach() {
         synchronized (mLock) {
+            if (mIsDetached) return;
             for (ModelData model : mModelDataMap.values()) {
                 forceStopAndUnloadModelLocked(model, null);
             }
             mModelDataMap.clear();
             internalClearGlobalStateLocked();
             if (mModule != null) {
+                mIsDetached = true;
                 mModule.detach();
                 mModule = null;
             }
@@ -1289,7 +1335,7 @@
      * @param modelData Model data to be used for recognition
      * @return True if device state allows recognition to run, false if not.
      */
-    boolean isRecognitionAllowedByPowerState(ModelData modelData) {
+    private boolean isRecognitionAllowedByPowerState(ModelData modelData) {
         return mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED
                 || (mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY
                 && modelData.shouldRunInBatterySaverMode());
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 790be8d..46e634f 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -1663,6 +1663,11 @@
             }
 
             @Override
+            public void detach() {
+                mSoundTriggerHelper.detach();
+            }
+
+            @Override
             public int unloadKeyphraseModel(int keyphraseId) {
                 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
             }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionBinderProxy.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionBinderProxy.java
index dd9fee3..0ef2f06 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionBinderProxy.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionBinderProxy.java
@@ -69,4 +69,9 @@
     public SoundTrigger.ModelParamRange queryParameter(int i, int i1) throws RemoteException {
         return mDelegate.queryParameter(i, i1);
     }
+
+    @Override
+    public void detach() throws RemoteException {
+        mDelegate.detach();
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java
index c0c3e6f..0f8a945 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java
@@ -113,6 +113,15 @@
                 "This object isn't intended to be used as a Binder.");
     }
 
+    @Override
+    public void detach() {
+        try {
+            mDelegate.detach();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     // TODO: Share this code with SoundTriggerMiddlewarePermission.
     private boolean isHoldingPermissions() {
         try {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
index aadb38d..2e05e20 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
@@ -81,7 +81,12 @@
     void informRestartProcessLocked() {
         Slog.v(TAG, "informRestartProcessLocked");
         mUpdateStateAfterStartFinished.set(false);
-        //TODO(b/261783819): Starts detection in VisualQueryDetectionService.
+        try {
+            mCallback.onProcessRestarted();
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to communicate #onProcessRestarted", e);
+            notifyOnDetectorRemoteException();
+        }
     }
 
     void setVisualQueryDetectionAttentionListenerLocked(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 1d7b966..bb50c79 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1856,6 +1856,11 @@
                         "This object isn't intended to be used as a Binder.");
             }
 
+            @Override
+            public void detach() {
+                mSession.detach();
+            }
+
             private int unloadKeyphraseModel(int keyphraseId) {
                 final long caller = Binder.clearCallingIdentity();
                 try {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7abae18..f27c23b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -7640,6 +7640,25 @@
         public static final String KEY_EMERGENCY_SCAN_TIMER_SEC_INT =
                 KEY_PREFIX + "emergency_scan_timer_sec_int";
 
+        /**
+         * The timer to wait for the call completion on the cellular network before attempting the
+         * call over Wi-Fi. On timer expiry, if emergency call on Wi-Fi is allowed and possible,
+         * telephony shall cancel the scan on the cellular network and place the call on Wi-Fi.
+         * If dialing over cellular network is ongoing when timer expires, dialing over Wi-Fi
+         * will be requested only when the ongoing dialing fails. If emergency call on Wi-Fi is not
+         * possible, then domain selection continues to try dialing from the radio and the timer
+         * remains expired. Later when calling over Wi-Fi is possible and dialing over cellular
+         * networks fails, calling over Wi-Fi will be requested. The timer shall be restarted from
+         * initial state if calling over Wi-Fi fails.
+         * If this value is set to {@link #REDIAL_TIMER_DISABLED}, then the timer will never be
+         * started.
+         *
+         * The default value for the timer is {@link #REDIAL_TIMER_DISABLED}.
+         * @hide
+         */
+        public static final String KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT =
+                KEY_PREFIX + "maximum_cellular_search_timer_sec_int";
+
         /** @hide */
         @IntDef(prefix = "SCAN_TYPE_",
             value = {
@@ -7734,10 +7753,12 @@
                 KEY_PREFIX + "emergency_requires_volte_enabled_bool";
 
         /**
-         * This values indicates that the cross SIM redialing timer shall be disabled.
+         * This values indicates that the cross SIM redialing timer and maximum celluar search
+         * timer shall be disabled.
          *
          * @see #KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT
          * @see #KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT
+         * @see #KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT
          * @hide
          */
         public static final int REDIAL_TIMER_DISABLED = 0;
@@ -7841,6 +7862,7 @@
             defaults.putInt(KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT, VOWIFI_REQUIRES_NONE);
             defaults.putInt(KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT, 1);
             defaults.putInt(KEY_EMERGENCY_SCAN_TIMER_SEC_INT, 10);
+            defaults.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, REDIAL_TIMER_DISABLED);
             defaults.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT, SCAN_TYPE_NO_PREFERENCE);
             defaults.putInt(KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT, 0);
             defaults.putBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL, false);
diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java
index e5e8f0a..6c069d4 100644
--- a/telephony/java/android/telephony/UiccCardInfo.java
+++ b/telephony/java/android/telephony/UiccCardInfo.java
@@ -166,7 +166,7 @@
                 + " Please Use UiccPortInfo API instead");
         }
         //always return ICCID from first port.
-        return getPorts().stream().findFirst().get().getIccId();
+        return mPortList.isEmpty() ? null : mPortList.get(0).getIccId();
     }
 
     /**
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index 1863a03b..dda7349 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -139,14 +139,16 @@
     public UiccSlotInfo(boolean isEuicc, String cardId,
             @CardStateInfo int cardStateInfo, boolean isExtendedApduSupported,
             boolean isRemovable, @NonNull List<UiccPortInfo> portList) {
-        this.mIsActive = portList.get(0).isActive();
         this.mIsEuicc = isEuicc;
         this.mCardId = cardId;
         this.mCardStateInfo = cardStateInfo;
-        this.mLogicalSlotIdx = portList.get(0).getLogicalSlotIndex();
         this.mIsExtendedApduSupported = isExtendedApduSupported;
         this.mIsRemovable = isRemovable;
         this.mPortList = portList;
+        this.mIsActive = !portList.isEmpty() && portList.get(0).isActive();
+        this.mLogicalSlotIdx = portList.isEmpty()
+                ? SubscriptionManager.INVALID_PHONE_INDEX
+                : portList.get(0).getLogicalSlotIndex();
     }
 
     /**
@@ -164,8 +166,7 @@
             throw new UnsupportedOperationException("getIsActive() is not supported by "
             + "UiccSlotInfo. Please Use UiccPortInfo API instead");
         }
-        //always return status from first port.
-        return getPorts().stream().findFirst().get().isActive();
+        return mIsActive;
     }
 
     public boolean getIsEuicc() {
@@ -202,9 +203,7 @@
             throw new UnsupportedOperationException("getLogicalSlotIdx() is not supported by "
                 + "UiccSlotInfo. Please use UiccPortInfo API instead");
         }
-        //always return logical slot index from first port.
-        //portList always have at least one element.
-        return getPorts().stream().findFirst().get().getLogicalSlotIndex();
+        return mLogicalSlotIdx;
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 282b64d..18e4c37 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3008,4 +3008,14 @@
      * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
      */
     boolean setSatelliteListeningTimeoutDuration(in long timeoutMillis);
+
+    /**
+     * This API can be used by only CTS to update satellite pointing UI app package and class names.
+     *
+     * @param packageName The package name of the satellite pointing UI app.
+     * @param className The class name of the satellite pointing UI app.
+     * @return {@code true} if the satellite pointing UI app package and class is set successfully,
+     * {@code false} otherwise.
+     */
+    boolean setSatellitePointingUiClassName(in String packageName, in String className);
 }
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 a72c12d..c5a21a8 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
@@ -250,7 +250,10 @@
             waitConditions = arrayOf(ConditionsFactory.hasPipWindow())
         )
 
-        wmHelper.StateSyncBuilder().withPipShown().waitForAndVerify()
+        wmHelper.StateSyncBuilder()
+            .withWindowSurfaceAppeared(this)
+            .withPipShown()
+            .waitForAndVerify()
     }
 
     /** Expand the PIP window back to full screen via intent and wait until the app is visible */