Merge changes from topic "activity-starter" into udc-dev

* changes:
  Add ActivityStarterImpl
  Add use_new_activity_starter feature flag
  Remove centralsurfaces activitystarter invocations
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..27f5545 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) {
@@ -9601,6 +9599,7 @@
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             } catch (IllegalArgumentException ex) {
+                Log.e(TAG, "IllegalArgumentException checking isPackageSuspended", ex);
                 throw new NameNotFoundException(packageName);
             }
         }
diff --git a/core/java/android/app/admin/LockTaskPolicy.java b/core/java/android/app/admin/LockTaskPolicy.java
index f5d1cb4..b671d57 100644
--- a/core/java/android/app/admin/LockTaskPolicy.java
+++ b/core/java/android/app/admin/LockTaskPolicy.java
@@ -38,6 +38,7 @@
     /**
      * @hide
      */
+    // We default on the power button menu, in order to be consistent with pre-P behaviour
     public static final int DEFAULT_LOCK_TASK_FLAG =
             DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
 
@@ -72,18 +73,28 @@
     /**
      * @hide
      */
-    public LockTaskPolicy(@NonNull Set<String> packages) {
-        Objects.requireNonNull(packages);
-        mPackages.addAll(packages);
+    public LockTaskPolicy(@Nullable Set<String> packages) {
+        if (packages != null) {
+            mPackages.addAll(packages);
+        }
         setValue(this);
     }
 
     /**
      * @hide
      */
-    public LockTaskPolicy(@NonNull Set<String> packages, int flags) {
-        Objects.requireNonNull(packages);
-        mPackages = new HashSet<>(packages);
+    public LockTaskPolicy(int flags) {
+        mFlags = flags;
+        setValue(this);
+    }
+
+    /**
+     * @hide
+     */
+    public LockTaskPolicy(@Nullable Set<String> packages, int flags) {
+        if (packages != null) {
+            mPackages.addAll(packages);
+        }
         mFlags = flags;
         setValue(this);
     }
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/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index ed6a88f..70b72c8 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -304,7 +304,9 @@
                 return;
             }
             case DO_SET_STYLUS_WINDOW_IDLE_TIMEOUT: {
-                inputMethod.setStylusWindowIdleTimeoutForTest((long) msg.obj);
+                if (isValid(inputMethod, target, "DO_SET_STYLUS_WINDOW_IDLE_TIMEOUT")) {
+                    inputMethod.setStylusWindowIdleTimeoutForTest((long) msg.obj);
+                }
                 return;
             }
         }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 7383e63..9f9c222 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -493,7 +493,7 @@
          * @hide
          */
         @TestApi
-        public static final int RESOURCES_SDK_INT = SDK_INT;
+        public static final int RESOURCES_SDK_INT = SDK_INT + ACTIVE_CODENAMES.length;
 
         /**
          * The current lowest supported value of app target SDK. Applications targeting
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/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index e31adcf..f2373fb 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -341,6 +341,9 @@
     @Nullable
     public DisplayShape displayShape;
 
+    /**
+     * Refresh rate range limitation based on the current device layout
+     */
     @Nullable
     public SurfaceControl.RefreshRateRange layoutLimitedRefreshRate;
 
@@ -354,7 +357,7 @@
      * RefreshRateRange limitation for @Temperature.ThrottlingStatus
      */
     @NonNull
-    public SparseArray<SurfaceControl.RefreshRateRange> refreshRateThermalThrottling =
+    public SparseArray<SurfaceControl.RefreshRateRange> thermalRefreshRateThrottling =
             new SparseArray<>();
 
     public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
@@ -434,7 +437,7 @@
                 && Objects.equals(displayShape, other.displayShape)
                 && Objects.equals(layoutLimitedRefreshRate, other.layoutLimitedRefreshRate)
                 && BrightnessSynchronizer.floatEquals(hdrSdrRatio, other.hdrSdrRatio)
-                && refreshRateThermalThrottling.contentEquals(other.refreshRateThermalThrottling);
+                && thermalRefreshRateThrottling.contentEquals(other.thermalRefreshRateThrottling);
     }
 
     @Override
@@ -491,7 +494,7 @@
         displayShape = other.displayShape;
         layoutLimitedRefreshRate = other.layoutLimitedRefreshRate;
         hdrSdrRatio = other.hdrSdrRatio;
-        refreshRateThermalThrottling = other.refreshRateThermalThrottling;
+        thermalRefreshRateThrottling = other.thermalRefreshRateThrottling;
     }
 
     public void readFromParcel(Parcel source) {
@@ -554,7 +557,7 @@
         displayShape = source.readTypedObject(DisplayShape.CREATOR);
         layoutLimitedRefreshRate = source.readTypedObject(SurfaceControl.RefreshRateRange.CREATOR);
         hdrSdrRatio = source.readFloat();
-        refreshRateThermalThrottling = source.readSparseArray(null,
+        thermalRefreshRateThrottling = source.readSparseArray(null,
                 SurfaceControl.RefreshRateRange.class);
     }
 
@@ -616,7 +619,7 @@
         dest.writeTypedObject(displayShape, flags);
         dest.writeTypedObject(layoutLimitedRefreshRate, flags);
         dest.writeFloat(hdrSdrRatio);
-        dest.writeSparseArray(refreshRateThermalThrottling);
+        dest.writeSparseArray(thermalRefreshRateThrottling);
     }
 
     @Override
@@ -884,8 +887,8 @@
         } else {
             sb.append(hdrSdrRatio);
         }
-        sb.append(", refreshRateThermalThrottling ");
-        sb.append(refreshRateThermalThrottling);
+        sb.append(", thermalRefreshRateThrottling ");
+        sb.append(thermalRefreshRateThrottling);
         sb.append("}");
         return sb.toString();
     }
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/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 80a3e70..d749b91 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -223,7 +223,8 @@
                 break;
             }
             case DO_TIME_SHIFT_SET_MODE: {
-                mTvInputSessionImpl.timeShiftSetMode((Integer) msg.obj);
+                mTvInputSessionImpl.timeShiftSetMode(msg.arg1);
+                break;
             }
             case DO_TIME_SHIFT_ENABLE_POSITION_TRACKING: {
                 mTvInputSessionImpl.timeShiftEnablePositionTracking((Boolean) msg.obj);
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/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/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/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/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 0f370ac..b141db1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -199,7 +199,8 @@
                     if (mShouldShowComplications) {
                         return (requiredTypes & getAvailableComplicationTypes()) == requiredTypes;
                     }
-                    return (requiredTypes & mSupportedTypes) == requiredTypes;
+                    final int typesToAlwaysShow = mSupportedTypes & getAvailableComplicationTypes();
+                    return (requiredTypes & typesToAlwaysShow) == requiredTypes;
                 })
                 .collect(Collectors.toCollection(HashSet::new))
                 : mComplications);
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 96c6ffb..17e66a7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -110,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/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index d3fe2c5..8c0cfba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -23,6 +23,7 @@
 import android.text.TextUtils;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.Dumpable;
@@ -74,7 +75,9 @@
 
     // Executor that will show the next message after a delay
     private final DelayableExecutor mExecutor;
-    @Nullable private ShowNextIndication mShowNextIndicationRunnable;
+
+    @VisibleForTesting
+    @Nullable ShowNextIndication mShowNextIndicationRunnable;
 
     // List of indication types to show. The next indication to show is always at index 0
     private final List<Integer> mIndicationQueue = new ArrayList<>();
@@ -111,6 +114,12 @@
         cancelScheduledIndication();
     }
 
+    /** Destroy ViewController, removing any listeners. */
+    public void destroy() {
+        super.destroy();
+        onViewDetached();
+    }
+
     /**
      * Update the indication type with the given String.
      * @param type of indication
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..cce708e 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;
@@ -119,6 +120,7 @@
     private final QSLogger mLogger;
     private final FooterActionsController mFooterActionsController;
     private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
+    private final FooterActionsViewBinder mFooterActionsViewBinder;
     private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner;
     private boolean mShowCollapsedOnKeyguard;
     private boolean mLastKeyguardAndExpanded;
@@ -176,6 +178,7 @@
             DumpManager dumpManager, QSLogger qsLogger,
             FooterActionsController footerActionsController,
             FooterActionsViewModel.Factory footerActionsViewModelFactory,
+            FooterActionsViewBinder footerActionsViewBinder,
             LargeScreenShadeInterpolator largeScreenShadeInterpolator,
             FeatureFlags featureFlags) {
         mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
@@ -192,6 +195,7 @@
         mDumpManager = dumpManager;
         mFooterActionsController = footerActionsController;
         mFooterActionsViewModelFactory = footerActionsViewModelFactory;
+        mFooterActionsViewBinder = footerActionsViewBinder;
         mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
     }
 
@@ -284,7 +288,7 @@
 
         if (!ComposeFacade.INSTANCE.isComposeAvailable()) {
             Log.d(TAG, "Binding the View implementation of the QS footer actions");
-            FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
+            mFooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
                     mListeningAndVisibilityLifecycleOwner);
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
index 1921586..9c9ad33 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
@@ -33,27 +33,27 @@
 import com.android.systemui.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.people.ui.view.PeopleViewBinder.bind
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import javax.inject.Inject
 import kotlin.math.roundToInt
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
 
 /** A ViewBinder for [FooterActionsViewBinder]. */
-object FooterActionsViewBinder {
+@SysUISingleton
+class FooterActionsViewBinder @Inject constructor() {
     /** Create a view that can later be [bound][bind] to a [FooterActionsViewModel]. */
-    @JvmStatic
     fun create(context: Context): LinearLayout {
         return LayoutInflater.from(context).inflate(R.layout.footer_actions, /* root= */ null)
             as LinearLayout
     }
 
     /** Bind [view] to [viewModel]. */
-    @JvmStatic
     fun bind(
         view: LinearLayout,
         viewModel: FooterActionsViewModel,
@@ -98,6 +98,11 @@
         var previousForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null
         var previousUserSwitcher: FooterActionsButtonViewModel? = null
 
+        // Set the initial visibility on the View directly so that we don't briefly show it for a
+        // few frames before [viewModel.isVisible] is collected.
+        view.isInvisible = !viewModel.isVisible.value
+
+        // Listen for ViewModel updates when the View is attached.
         view.repeatWhenAttached {
             val attachedScope = this.lifecycleScope
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index 3a9098a..b3596a2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -64,7 +64,7 @@
      * the UI should still participate to the layout it is included in (i.e. in the View world it
      * should be INVISIBLE, not GONE).
      */
-    private val _isVisible = MutableStateFlow(true)
+    private val _isVisible = MutableStateFlow(false)
     val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
 
     /** The alpha the UI rendering this ViewModel should have. */
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 d63754c..dfc9bcf 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/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 142689e..ea5a1c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -335,6 +335,9 @@
             R.id.keyguard_indication_text_bottom);
         mInitialTextColorState = mTopIndicationView != null
                 ? mTopIndicationView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
+        if (mRotateTextViewController != null) {
+            mRotateTextViewController.destroy();
+        }
         mRotateTextViewController = new KeyguardIndicationRotateTextViewController(
                 mLockScreenIndicationView,
                 mExecutor,
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 c5f64b0..88d9d8f 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/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/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 e468a59..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;
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/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/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 54cccc0..4a9bf4eb 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.ActivityStarter;
@@ -4191,10 +4191,7 @@
             mCentralSurfaces.resetUserExpandedStates();
             clearTemporaryViews();
             clearUserLockedViews();
-            if (mSwipeHelper.isSwiping()) {
-                mSwipeHelper.resetSwipeState();
-                updateContinuousShadowDrawing();
-            }
+            cancelActiveSwipe();
         }
     }
 
@@ -4266,6 +4263,9 @@
             if (!mIsExpanded) {
                 mGroupExpansionManager.collapseGroups();
                 mExpandHelper.cancelImmediately();
+                if (!mIsExpansionChanging) {
+                    cancelActiveSwipe();
+                }
             }
             updateNotificationAnimationStates();
             updateChronometers();
@@ -4748,13 +4748,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)
@@ -6125,7 +6119,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 12348a6..aa5aed7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -457,7 +457,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;
@@ -465,7 +465,7 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @VisibleForTesting
     DozeServiceHost mDozeServiceHost;
-    private LightRevealScrim mLightRevealScrim;
+    private final LightRevealScrim mLightRevealScrim;
     private PowerButtonReveal mPowerButtonReveal;
 
     private boolean mWakeUpComingFromTouch;
@@ -773,6 +773,7 @@
             ScrimController scrimController,
             Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
+            AuthRippleController authRippleController,
             DozeServiceHost dozeServiceHost,
             PowerManager powerManager,
             ScreenPinningRequest screenPinningRequest,
@@ -815,6 +816,7 @@
             IDreamManager dreamManager,
             Lazy<CameraLauncher> cameraLauncherLazy,
             Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy,
+            LightRevealScrim lightRevealScrim,
             AlternateBouncerInteractor alternateBouncerInteractor,
             UserTracker userTracker,
             Provider<FingerprintManager> fingerprintManager
@@ -871,6 +873,7 @@
         mScreenPinningRequest = screenPinningRequest;
         mDozeScrimController = dozeScrimController;
         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
+        mAuthRippleController = authRippleController;
         mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy;
         mVolumeComponent = volumeComponent;
         mCommandQueue = commandQueue;
@@ -937,6 +940,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)) {
@@ -1331,8 +1335,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());
@@ -1640,10 +1642,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(
@@ -1672,8 +1671,6 @@
         mPresenter = mCentralSurfacesComponent.getNotificationPresenter();
         mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter();
         mNotificationShelfController = mCentralSurfacesComponent.getNotificationShelfController();
-        mAuthRippleController = mCentralSurfacesComponent.getAuthRippleController();
-        mAuthRippleController.init();
 
         mHeadsUpManager.addListener(mCentralSurfacesComponent.getStatusBarHeadsUpChangeListener());
 
@@ -1687,11 +1684,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() {
@@ -3682,9 +3674,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/ViewController.java b/packages/SystemUI/src/com/android/systemui/util/ViewController.java
index 0dd5788..1f118d1 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/ViewController.java
@@ -109,6 +109,11 @@
         }
     }
 
+    /** Destroy ViewController, removing any listeners. */
+    public void destroy() {
+        mView.removeOnAttachStateChangeListener(mOnAttachStateListener);
+    }
+
     /**
      * Called when the view is attached and a call to {@link #init()} has been made in either order.
      */
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/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/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index f143c467..7b41605 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -376,6 +376,34 @@
         }
     }
 
+    @Test
+    public void testHomeControlsDoNotShowIfNotAvailable_featureEnabled() {
+        when(mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)).thenReturn(true);
+
+        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
+        stateController.setShouldShowComplications(true);
+
+        final Complication homeControlsComplication = Mockito.mock(Complication.class);
+        when(homeControlsComplication.getRequiredTypeAvailability())
+                .thenReturn(Complication.COMPLICATION_TYPE_HOME_CONTROLS);
+
+        stateController.addComplication(homeControlsComplication);
+
+        final DreamOverlayStateController.Callback callback =
+                Mockito.mock(DreamOverlayStateController.Callback.class);
+
+        stateController.addCallback(callback);
+        mExecutor.runAllReady();
+
+        // No home controls since it is not available.
+        assertThat(stateController.getComplications()).doesNotContain(homeControlsComplication);
+
+        stateController.setAvailableComplicationTypes(Complication.COMPLICATION_TYPE_HOME_CONTROLS
+                | Complication.COMPLICATION_TYPE_WEATHER);
+        mExecutor.runAllReady();
+        assertThat(stateController.getComplications()).contains(homeControlsComplication);
+    }
+
     private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) {
         return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags);
     }
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/KeyguardIndicationRotateTextViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
index c3b0e5226..d934f76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
@@ -23,9 +23,11 @@
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -88,6 +90,54 @@
     }
 
     @Test
+    public void onViewDetached_removesStatusBarStateListener() {
+        mController.onViewDetached();
+        verify(mStatusBarStateController).removeCallback(mStatusBarStateListener);
+    }
+
+    @Test
+    public void onViewDetached_removesAllScheduledIndications() {
+        // GIVEN show next indication runnable is set
+        final KeyguardIndicationRotateTextViewController.ShowNextIndication mockShowNextIndication =
+                mock(KeyguardIndicationRotateTextViewController.ShowNextIndication.class);
+        mController.mShowNextIndicationRunnable = mockShowNextIndication;
+
+        // WHEN the view is detached
+        mController.onViewDetached();
+
+        // THEN delayed execution is cancelled & runnable set to null
+        verify(mockShowNextIndication).cancelDelayedExecution();
+        assertNull(mController.mShowNextIndicationRunnable);
+    }
+
+    @Test
+    public void destroy_removesStatusBarStateListener() {
+        mController.destroy();
+        verify(mStatusBarStateController).removeCallback(mStatusBarStateListener);
+    }
+
+    @Test
+    public void destroy_removesOnAttachStateChangeListener() {
+        mController.destroy();
+        verify(mView).removeOnAttachStateChangeListener(any());
+    }
+
+    @Test
+    public void destroy_removesAllScheduledIndications() {
+        // GIVEN show next indication runnable is set
+        final KeyguardIndicationRotateTextViewController.ShowNextIndication mockShowNextIndication =
+                mock(KeyguardIndicationRotateTextViewController.ShowNextIndication.class);
+        mController.mShowNextIndicationRunnable = mockShowNextIndication;
+
+        // WHEN the controller is destroyed
+        mController.destroy();
+
+        // THEN delayed execution is cancelled & runnable set to null
+        verify(mockShowNextIndication).cancelDelayedExecution();
+        assertNull(mController.mShowNextIndicationRunnable);
+    }
+
+    @Test
     public void testInitialState_noIndication() {
         assertFalse(mController.hasIndications());
     }
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/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 0ab0e2b..87ca9df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -106,6 +106,7 @@
     @Mock private QSSquishinessController mSquishinessController;
     @Mock private FooterActionsViewModel mFooterActionsViewModel;
     @Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
+    @Mock private FooterActionsViewBinder mFooterActionsViewBinder;
     @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
     @Mock private FeatureFlags mFeatureFlags;
     private View mQsFragmentView;
@@ -558,6 +559,7 @@
                 mock(QSLogger.class),
                 mock(FooterActionsController.class),
                 mFooterActionsViewModelFactory,
+                mFooterActionsViewBinder,
                 mLargeScreenShadeInterpolator,
                 mFeatureFlags);
     }
@@ -584,7 +586,7 @@
         when(mQsFragmentView.findViewById(R.id.header)).thenReturn(mHeader);
         when(mQsFragmentView.findViewById(android.R.id.edit)).thenReturn(new View(mContext));
         when(mQsFragmentView.findViewById(R.id.qs_footer_actions)).thenAnswer(
-                invocation -> FooterActionsViewBinder.create(mContext));
+                invocation -> new FooterActionsViewBinder().create(mContext));
     }
 
     private void setUpInflater() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 59f0d96..2cc6709 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -376,13 +376,13 @@
     @Test
     fun isVisible() {
         val underTest = utils.footerActionsViewModel()
-        assertThat(underTest.isVisible.value).isTrue()
-
-        underTest.onVisibilityChangeRequested(visible = false)
         assertThat(underTest.isVisible.value).isFalse()
 
         underTest.onVisibilityChangeRequested(visible = true)
         assertThat(underTest.isVisible.value).isTrue()
+
+        underTest.onVisibilityChangeRequested(visible = false)
+        assertThat(underTest.isVisible.value).isFalse()
     }
 
     @Test
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/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 4438b98..f7fcab1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -325,6 +325,21 @@
     }
 
     @Test
+    public void createController_setIndicationAreaAgain_destroysPreviousRotateTextViewController() {
+        // GIVEN a controller with a mocked rotate text view controlller
+        final KeyguardIndicationRotateTextViewController mockedRotateTextViewController =
+                mock(KeyguardIndicationRotateTextViewController.class);
+        createController();
+        mController.mRotateTextViewController = mockedRotateTextViewController;
+
+        // WHEN a new indication area is set
+        mController.setIndicationArea(mIndicationArea);
+
+        // THEN the previous rotateTextViewController is destroyed
+        verify(mockedRotateTextViewController).destroy();
+    }
+
+    @Test
     public void createController_addsAlignmentListener() {
         createController();
 
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/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 9d1fa69..6a0e3c6 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
@@ -257,6 +257,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/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/java/com/android/server/SoundTriggerInternal.java b/services/core/java/com/android/server/SoundTriggerInternal.java
index e6c1750..f184574 100644
--- a/services/core/java/com/android/server/SoundTriggerInternal.java
+++ b/services/core/java/com/android/server/SoundTriggerInternal.java
@@ -52,11 +52,6 @@
     // Enumerate possible STModules to attach to
     List<ModuleProperties> listModuleProperties(Identity originatorIdentity);
 
-    /**
-     * Dumps service-wide information.
-     */
-    void dump(FileDescriptor fd, PrintWriter pw, String[] args);
-
     interface Session {
         /**
          * Starts recognition for the given keyphraseId.
@@ -142,13 +137,14 @@
                 @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);
-
-        /**
-         * Dumps session-wide information.
-         */
-        void dump(FileDescriptor fd, PrintWriter pw, String[] args);
     }
 }
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/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index a57dd40..7cdea8d 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -87,10 +87,11 @@
     private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3;  // unconfirmed
 
     private static final int MSG_CONFIGURE_SAFE_MEDIA = SAFE_MEDIA_VOLUME_MSG_START + 1;
-    private static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 2;
-    private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 3;
-    private static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 4;
-    /*package*/ static final int MSG_CSD_UPDATE_ATTENUATION = SAFE_MEDIA_VOLUME_MSG_START + 5;
+    private static final int MSG_CONFIGURE_SAFE_MEDIA_FORCED = SAFE_MEDIA_VOLUME_MSG_START + 2;
+    private static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 3;
+    private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 4;
+    private static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 5;
+    /*package*/ static final int MSG_CSD_UPDATE_ATTENUATION = SAFE_MEDIA_VOLUME_MSG_START + 6;
 
     private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
 
@@ -611,8 +612,7 @@
     }
 
     /*package*/ void configureSafeMedia(boolean forced, String caller) {
-        int msg = MSG_CONFIGURE_SAFE_MEDIA;
-
+        int msg = forced ? MSG_CONFIGURE_SAFE_MEDIA_FORCED : MSG_CONFIGURE_SAFE_MEDIA;
         mAudioHandler.removeMessages(msg);
 
         long time = 0;
@@ -622,7 +622,7 @@
         }
 
         mAudioHandler.sendMessageAtTime(
-                mAudioHandler.obtainMessage(msg, /*arg1=*/forced ? 1 : 0, /*arg2=*/0, caller),
+                mAudioHandler.obtainMessage(msg, /*arg1=*/0, /*arg2=*/0, caller),
                 time);
     }
 
@@ -664,8 +664,10 @@
 
     /*package*/ void handleMessage(Message msg) {
         switch (msg.what) {
+            case MSG_CONFIGURE_SAFE_MEDIA_FORCED:
             case MSG_CONFIGURE_SAFE_MEDIA:
-                onConfigureSafeMedia((msg.arg1 == 1), (String) msg.obj);
+                onConfigureSafeMedia((msg.what == MSG_CONFIGURE_SAFE_MEDIA_FORCED),
+                        (String) msg.obj);
                 break;
             case MSG_PERSIST_SAFE_VOLUME_STATE:
                 onPersistSafeVolumeState(msg.arg1);
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/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index dab00d8..0b6d1c8 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -181,6 +181,19 @@
      */
     private String mThermalBrightnessThrottlingDataId;
 
+    /**
+     * Refresh rate range limitation based on the current device layout
+     */
+    @Nullable
+    private SurfaceControl.RefreshRateRange mLayoutLimitedRefreshRate;
+
+    /**
+     * RefreshRateRange limitation for @Temperature.ThrottlingStatus
+     */
+    @NonNull
+    private SparseArray<SurfaceControl.RefreshRateRange> mThermalRefreshRateThrottling =
+            new SparseArray<>();
+
     public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
         mDisplayId = displayId;
         mLayerStack = layerStack;
@@ -339,24 +352,24 @@
      */
     public void updateLayoutLimitedRefreshRateLocked(
             @Nullable SurfaceControl.RefreshRateRange layoutLimitedRefreshRate) {
-        if (!Objects.equals(layoutLimitedRefreshRate, mBaseDisplayInfo.layoutLimitedRefreshRate)) {
-            mBaseDisplayInfo.layoutLimitedRefreshRate = layoutLimitedRefreshRate;
-            mInfo.set(null);
+        if (!Objects.equals(layoutLimitedRefreshRate, mLayoutLimitedRefreshRate)) {
+            mLayoutLimitedRefreshRate = layoutLimitedRefreshRate;
+            mDirty = true;
         }
     }
     /**
-     * Updates refreshRateThermalThrottling
+     * Updates thermalRefreshRateThrottling
      *
-     * @param refreshRanges new refreshRateThermalThrottling ranges limited by layout or default
+     * @param refreshRanges new thermalRefreshRateThrottling ranges limited by layout or default
      */
     public void updateThermalRefreshRateThrottling(
             @Nullable SparseArray<SurfaceControl.RefreshRateRange> refreshRanges) {
         if (refreshRanges == null) {
             refreshRanges = new SparseArray<>();
         }
-        if (!mBaseDisplayInfo.refreshRateThermalThrottling.contentEquals(refreshRanges)) {
-            mBaseDisplayInfo.refreshRateThermalThrottling = refreshRanges;
-            mInfo.set(null);
+        if (!mThermalRefreshRateThrottling.contentEquals(refreshRanges)) {
+            mThermalRefreshRateThrottling = refreshRanges;
+            mDirty = true;
         }
     }
 
@@ -499,6 +512,9 @@
                 mBaseDisplayInfo.removeMode = Display.REMOVE_MODE_DESTROY_CONTENT;
             }
 
+            mBaseDisplayInfo.layoutLimitedRefreshRate = mLayoutLimitedRefreshRate;
+            mBaseDisplayInfo.thermalRefreshRateThrottling = mThermalRefreshRateThrottling;
+
             mPrimaryDisplayDeviceInfo = deviceInfo;
             mInfo.set(null);
             mDirty = false;
@@ -952,6 +968,8 @@
         pw.println("mDisplayGroupName=" + mDisplayGroupName);
         pw.println("mThermalBrightnessThrottlingDataId=" + mThermalBrightnessThrottlingDataId);
         pw.println("mLeadDisplayId=" + mLeadDisplayId);
+        pw.println("mLayoutLimitedRefreshRate=" + mLayoutLimitedRefreshRate);
+        pw.println("mThermalRefreshRateThrottling=" + mThermalRefreshRateThrottling);
     }
 
     @Override
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..fd94be9 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;
@@ -102,10 +103,6 @@
     private static final int MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED = 7;
     private static final int MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED = 8;
 
-    // Special ID used to indicate that given vote is to be applied globally, rather than to a
-    // specific display.
-    private static final int GLOBAL_ID = -1;
-
     private static final float FLOAT_TOLERANCE = RefreshRateRange.FLOAT_TOLERANCE;
 
     private final Object mLock = new Object();
@@ -128,10 +125,6 @@
     @Nullable
     private DisplayDeviceConfig mDefaultDisplayDeviceConfig;
 
-    // A map from the display ID to the collection of votes and their priority. The latter takes
-    // the form of another map from the priority to the vote itself so that each priority is
-    // guaranteed to have exactly one vote, which is also easily and efficiently replaceable.
-    private SparseArray<SparseArray<Vote>> mVotesByDisplay;
     // A map from the display ID to the supported modes on that display.
     private SparseArray<Display.Mode[]> mSupportedModesByDisplay;
     // A map from the display ID to the default mode of that display.
@@ -145,6 +138,8 @@
 
     private final boolean mSupportsFrameRateOverride;
 
+    private final VotesStorage mVotesStorage;
+
     /**
      * The allowed refresh rate switching type. This is used by SurfaceFlinger.
      */
@@ -160,7 +155,6 @@
         mContext = context;
         mHandler = new DisplayModeDirectorHandler(handler.getLooper());
         mInjector = injector;
-        mVotesByDisplay = new SparseArray<>();
         mSupportedModesByDisplay = new SparseArray<>();
         mDefaultModeByDisplay = new SparseArray<>();
         mAppRequestObserver = new AppRequestObserver();
@@ -170,15 +164,11 @@
         mBrightnessObserver = new BrightnessObserver(context, handler, injector);
         mDefaultDisplayDeviceConfig = null;
         mUdfpsObserver = new UdfpsObserver();
-        final BallotBox ballotBox = (displayId, priority, vote) -> {
-            synchronized (mLock) {
-                updateVoteLocked(displayId, priority, vote);
-            }
-        };
-        mDisplayObserver = new DisplayObserver(context, handler, ballotBox);
-        mSensorObserver = new SensorObserver(context, ballotBox, injector);
-        mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, ballotBox);
-        mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler(),
+        mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked);
+        mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage);
+        mSensorObserver = new SensorObserver(context, mVotesStorage, injector);
+        mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, mVotesStorage);
+        mHbmObserver = new HbmObserver(injector, mVotesStorage, BackgroundThread.getHandler(),
                 mDeviceConfigDisplaySettings);
         mAlwaysRespectAppRequest = false;
         mSupportsFrameRateOverride = injector.supportsFrameRateOverride();
@@ -225,29 +215,7 @@
         mLoggingEnabled = loggingEnabled;
         mBrightnessObserver.setLoggingEnabled(loggingEnabled);
         mSkinThermalStatusObserver.setLoggingEnabled(loggingEnabled);
-    }
-
-    @NonNull
-    private SparseArray<Vote> getVotesLocked(int displayId) {
-        SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
-        final SparseArray<Vote> votes;
-        if (displayVotes != null) {
-            votes = displayVotes.clone();
-        } else {
-            votes = new SparseArray<>();
-        }
-
-        SparseArray<Vote> globalVotes = mVotesByDisplay.get(GLOBAL_ID);
-        if (globalVotes != null) {
-            for (int i = 0; i < globalVotes.size(); i++) {
-                int priority = globalVotes.keyAt(i);
-                if (votes.indexOfKey(priority) < 0) {
-                    votes.put(priority, globalVotes.valueAt(i));
-                }
-            }
-        }
-
-        return votes;
+        mVotesStorage.setLoggingEnabled(loggingEnabled);
     }
 
     private static final class VoteSummary {
@@ -406,7 +374,7 @@
     @NonNull
     public DesiredDisplayModeSpecs getDesiredDisplayModeSpecs(int displayId) {
         synchronized (mLock) {
-            SparseArray<Vote> votes = getVotesLocked(displayId);
+            SparseArray<Vote> votes = mVotesStorage.getVotes(displayId);
             Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
             Display.Mode defaultMode = mDefaultModeByDisplay.get(displayId);
             if (modes == null || defaultMode == null) {
@@ -779,10 +747,8 @@
     @VisibleForTesting
     @Nullable
     Vote getVote(int displayId, int priority) {
-        synchronized (mLock) {
-            SparseArray<Vote> votes = getVotesLocked(displayId);
-            return votes.get(priority);
-        }
+        SparseArray<Vote> votes = mVotesStorage.getVotes(displayId);
+        return votes.get(priority);
     }
 
     /**
@@ -805,18 +771,6 @@
                 final Display.Mode mode = mDefaultModeByDisplay.valueAt(i);
                 pw.println("    " + id + " -> " + mode);
             }
-            pw.println("  mVotesByDisplay:");
-            for (int i = 0; i < mVotesByDisplay.size(); i++) {
-                pw.println("    " + mVotesByDisplay.keyAt(i) + ":");
-                SparseArray<Vote> votes = mVotesByDisplay.valueAt(i);
-                for (int p = Vote.MAX_PRIORITY; p >= Vote.MIN_PRIORITY; p--) {
-                    Vote vote = votes.get(p);
-                    if (vote == null) {
-                        continue;
-                    }
-                    pw.println("      " + Vote.priorityToString(p) + " -> " + vote);
-                }
-            }
             pw.println("  mModeSwitchingType: " + switchingTypeToString(mModeSwitchingType));
             pw.println("  mAlwaysRespectAppRequest: " + mAlwaysRespectAppRequest);
             mSettingsObserver.dumpLocked(pw);
@@ -826,44 +780,10 @@
             mHbmObserver.dumpLocked(pw);
             mSkinThermalStatusObserver.dumpLocked(pw);
         }
-
+        mVotesStorage.dump(pw);
         mSensorObserver.dump(pw);
     }
 
-    private void updateVoteLocked(int priority, Vote vote) {
-        updateVoteLocked(GLOBAL_ID, priority, vote);
-    }
-
-    private void updateVoteLocked(int displayId, int priority, Vote vote) {
-        if (mLoggingEnabled) {
-            Slog.i(TAG, "updateVoteLocked(displayId=" + displayId
-                    + ", priority=" + Vote.priorityToString(priority)
-                    + ", vote=" + vote + ")");
-        }
-        if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) {
-            Slog.w(TAG, "Received a vote with an invalid priority, ignoring:"
-                    + " priority=" + Vote.priorityToString(priority)
-                    + ", vote=" + vote, new Throwable());
-            return;
-        }
-        final SparseArray<Vote> votes = getOrCreateVotesByDisplay(displayId);
-
-        if (vote != null) {
-            votes.put(priority, vote);
-        } else {
-            votes.remove(priority);
-        }
-
-        if (votes.size() == 0) {
-            if (mLoggingEnabled) {
-                Slog.i(TAG, "No votes left for display " + displayId + ", removing.");
-            }
-            mVotesByDisplay.remove(displayId);
-        }
-
-        notifyDesiredDisplayModeSpecsChangedLocked();
-    }
-
     @GuardedBy("mLock")
     private float getMaxRefreshRateLocked(int displayId) {
         Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
@@ -889,16 +809,6 @@
         }
     }
 
-    private SparseArray<Vote> getOrCreateVotesByDisplay(int displayId) {
-        if (mVotesByDisplay.indexOfKey(displayId) >= 0) {
-            return mVotesByDisplay.get(displayId);
-        } else {
-            SparseArray<Vote> votes = new SparseArray<>();
-            mVotesByDisplay.put(displayId, votes);
-            return votes;
-        }
-    }
-
     private static String switchingTypeToString(@DisplayManager.SwitchingType int type) {
         switch (type) {
             case DisplayManager.SWITCHING_TYPE_NONE:
@@ -926,7 +836,7 @@
 
     @VisibleForTesting
     void injectVotesByDisplay(SparseArray<SparseArray<Vote>> votesByDisplay) {
-        mVotesByDisplay = votesByDisplay;
+        mVotesStorage.injectVotesByDisplay(votesByDisplay);
     }
 
     @VisibleForTesting
@@ -1156,230 +1066,11 @@
     }
 
     @VisibleForTesting
-    static final class Vote {
-        // DEFAULT_RENDER_FRAME_RATE votes for render frame rate [0, DEFAULT]. As the lowest
-        // priority vote, it's overridden by all other considerations. It acts to set a default
-        // frame rate for a device.
-        public static final int PRIORITY_DEFAULT_RENDER_FRAME_RATE = 0;
-
-        // PRIORITY_FLICKER_REFRESH_RATE votes for a single refresh rate like [60,60], [90,90] or
-        // null. It is used to set a preferred refresh rate value in case the higher priority votes
-        // result is a range.
-        public static final int PRIORITY_FLICKER_REFRESH_RATE = 1;
-
-        // High-brightness-mode may need a specific range of refresh-rates to function properly.
-        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]
-        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
-        // frame rate in certain cases, mostly to preserve power.
-        // @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate
-        // @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate
-        // It votes to [preferredMinRefreshRate, preferredMaxRefreshRate].
-        public static final int PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 4;
-
-        // We split the app request into different priorities in case we can satisfy one desire
-        // without the other.
-
-        // Application can specify preferred refresh rate with below attrs.
-        // @see android.view.WindowManager.LayoutParams#preferredRefreshRate
-        // @see android.view.WindowManager.LayoutParams#preferredDisplayModeId
-        //
-        // When the app specifies a LayoutParams#preferredDisplayModeId, in addition to the
-        // refresh rate, it also chooses a preferred size (resolution) as part of the selected
-        // mode id. The app preference is then translated to APP_REQUEST_BASE_MODE_REFRESH_RATE and
-        // optionally to APP_REQUEST_SIZE as well, if a mode id was selected.
-        // The system also forces some apps like denylisted app to run at a lower refresh rate.
-        // @see android.R.array#config_highRefreshRateBlacklist
-        //
-        // When summarizing the votes and filtering the allowed display modes, these votes determine
-        // which mode id should be the base mode id to be sent to SurfaceFlinger:
-        // - APP_REQUEST_BASE_MODE_REFRESH_RATE is used to validate the vote summary. If a summary
-        //   includes a base mode refresh rate, but it is not in the refresh rate range, then the
-        //   summary is considered invalid so we could drop a lower priority vote and try again.
-        // - APP_REQUEST_SIZE is used to filter out display modes of a different size.
-        //
-        // The preferred refresh rate is set on the main surface of the app outside of
-        // DisplayModeDirector.
-        // @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded
-        public static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 5;
-        public static final int PRIORITY_APP_REQUEST_SIZE = 6;
-
-        // SETTING_PEAK_RENDER_FRAME_RATE has a high priority and will restrict the bounds of the
-        // rest of low priority voters. It votes [0, max(PEAK, MIN)]
-        public static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 7;
-
-        // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh
-        // rate to max value (same as for PRIORITY_UDFPS) on lock screen
-        public static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 8;
-
-        // For concurrent displays we want to limit refresh rate on all displays
-        public static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 9;
-
-        // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if
-        // Settings.Global.LOW_POWER_MODE is on.
-        public static final int PRIORITY_LOW_POWER_MODE = 10;
-
-        // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
-        // higher priority voters' result is a range, it will fix the rate to a single choice.
-        // It's used to avoid refresh rate switches in certain conditions which may result in the
-        // user seeing the display flickering when the switches occur.
-        public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 11;
-
-        // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
-        public static final int PRIORITY_SKIN_TEMPERATURE = 12;
-
-        // The proximity sensor needs the refresh rate to be locked in order to function, so this is
-        // set to a high priority.
-        public static final int PRIORITY_PROXIMITY = 13;
-
-        // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
-        // to function, so this needs to be the highest priority of all votes.
-        public static final int PRIORITY_UDFPS = 14;
-
-        // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
-        // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
-
-        public static final int MIN_PRIORITY = PRIORITY_DEFAULT_RENDER_FRAME_RATE;
-        public static final int MAX_PRIORITY = PRIORITY_UDFPS;
-
-        // The cutoff for the app request refresh rate range. Votes with priorities lower than this
-        // value will not be considered when constructing the app request refresh rate range.
-        public static final int APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF =
-                PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE;
-
-        /**
-         * A value signifying an invalid width or height in a vote.
-         */
-        public static final int INVALID_SIZE = -1;
-
-        /**
-         * The requested width of the display in pixels, or INVALID_SIZE;
-         */
-        public final int width;
-        /**
-         * The requested height of the display in pixels, or INVALID_SIZE;
-         */
-        public final int height;
-        /**
-         * Information about the refresh rate frame rate ranges DM would like to set the display to.
-         */
-        public final RefreshRateRanges refreshRateRanges;
-
-        /**
-         * Whether refresh rate switching should be disabled (i.e. the refresh rate range is
-         * a single value).
-         */
-        public final boolean disableRefreshRateSwitching;
-
-        /**
-         * The preferred refresh rate selected by the app. It is used to validate that the summary
-         * refresh rate ranges include this value, and are not restricted by a lower priority vote.
-         */
-        public final float appRequestBaseModeRefreshRate;
-
-        public static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) {
-            return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate, 0,
-                    Float.POSITIVE_INFINITY,
-                    minRefreshRate == maxRefreshRate, 0f);
-        }
-
-        public static Vote forRenderFrameRates(float minFrameRate, float maxFrameRate) {
-            return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, minFrameRate,
-                    maxFrameRate,
-                    false, 0f);
-        }
-
-        public static Vote forSize(int width, int height) {
-            return new Vote(width, height, 0, Float.POSITIVE_INFINITY, 0, Float.POSITIVE_INFINITY,
-                    false,
-                    0f);
-        }
-
-        public static Vote forDisableRefreshRateSwitching() {
-            return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0,
-                    Float.POSITIVE_INFINITY, true,
-                    0f);
-        }
-
-        public static Vote forBaseModeRefreshRate(float baseModeRefreshRate) {
-            return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0,
-                    Float.POSITIVE_INFINITY, false,
-                    baseModeRefreshRate);
-        }
-
-        private Vote(int width, int height,
-                float minPhysicalRefreshRate,
-                float maxPhysicalRefreshRate,
-                float minRenderFrameRate,
-                float maxRenderFrameRate,
-                boolean disableRefreshRateSwitching,
-                float baseModeRefreshRate) {
-            this.width = width;
-            this.height = height;
-            this.refreshRateRanges = new RefreshRateRanges(
-                    new RefreshRateRange(minPhysicalRefreshRate, maxPhysicalRefreshRate),
-                    new RefreshRateRange(minRenderFrameRate, maxRenderFrameRate));
-            this.disableRefreshRateSwitching = disableRefreshRateSwitching;
-            this.appRequestBaseModeRefreshRate = baseModeRefreshRate;
-        }
-
-        public static String priorityToString(int priority) {
-            switch (priority) {
-                case PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE:
-                    return "PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE";
-                case PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE:
-                    return "PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE";
-                case PRIORITY_APP_REQUEST_SIZE:
-                    return "PRIORITY_APP_REQUEST_SIZE";
-                case PRIORITY_DEFAULT_RENDER_FRAME_RATE:
-                    return "PRIORITY_DEFAULT_REFRESH_RATE";
-                case PRIORITY_FLICKER_REFRESH_RATE:
-                    return "PRIORITY_FLICKER_REFRESH_RATE";
-                case PRIORITY_FLICKER_REFRESH_RATE_SWITCH:
-                    return "PRIORITY_FLICKER_REFRESH_RATE_SWITCH";
-                case PRIORITY_HIGH_BRIGHTNESS_MODE:
-                    return "PRIORITY_HIGH_BRIGHTNESS_MODE";
-                case PRIORITY_PROXIMITY:
-                    return "PRIORITY_PROXIMITY";
-                case PRIORITY_LOW_POWER_MODE:
-                    return "PRIORITY_LOW_POWER_MODE";
-                case PRIORITY_SKIN_TEMPERATURE:
-                    return "PRIORITY_SKIN_TEMPERATURE";
-                case PRIORITY_UDFPS:
-                    return "PRIORITY_UDFPS";
-                case PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE:
-                    return "PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE";
-                case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE:
-                    return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE";
-                case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE:
-                    return "PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE";
-                case PRIORITY_LAYOUT_LIMITED_FRAME_RATE:
-                    return "PRIORITY_LAYOUT_LIMITED_FRAME_RATE";
-                default:
-                    return Integer.toString(priority);
-            }
-        }
-
-        @Override
-        public String toString() {
-            return "Vote{"
-                    + "width=" + width + ", height=" + height
-                    + ", refreshRateRanges=" + refreshRateRanges
-                    + ", disableRefreshRateSwitching=" + disableRefreshRateSwitching
-                    + ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate + "}";
-        }
-    }
-
-    @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 +1106,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 +1149,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();
@@ -1510,17 +1200,14 @@
             } else {
                 vote = null;
             }
-            updateVoteLocked(Vote.PRIORITY_LOW_POWER_MODE, vote);
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_LOW_POWER_MODE, vote);
             mBrightnessObserver.onLowPowerModeEnabledLocked(inLowPowerMode);
         }
 
         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(
@@ -1532,13 +1219,14 @@
             Vote peakVote = peakRefreshRate == 0f
                     ? null
                     : Vote.forRenderFrameRates(0f, Math.max(minRefreshRate, peakRefreshRate));
-            updateVoteLocked(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, peakVote);
-            updateVoteLocked(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+                    peakVote);
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
                     Vote.forRenderFrameRates(minRefreshRate, Float.POSITIVE_INFINITY));
             Vote defaultVote =
                     defaultRefreshRate == 0f
                             ? null : Vote.forRenderFrameRates(0f, defaultRefreshRate);
-            updateVoteLocked(Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE, defaultVote);
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE, defaultVote);
 
             float maxRefreshRate;
             if (peakRefreshRate == 0f && defaultRefreshRate == 0f) {
@@ -1622,9 +1310,9 @@
                 sizeVote = null;
             }
 
-            updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
                     baseModeRefreshRateVote);
-            updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
         }
 
         private void setAppPreferredRefreshRateRangeLocked(int displayId,
@@ -1655,11 +1343,8 @@
                 mAppPreferredRefreshRateRangeByDisplay.remove(displayId);
                 vote = null;
             }
-            synchronized (mLock) {
-                updateVoteLocked(displayId,
-                        Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
-                        vote);
-            }
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
+                    vote);
         }
 
         private Display.Mode findModeByIdLocked(int displayId, int modeId) {
@@ -1699,23 +1384,22 @@
         // calling into us already holding its own lock.
         private final Context mContext;
         private final Handler mHandler;
-        private final BallotBox mBallotBox;
+        private final VotesStorage mVotesStorage;
 
-        DisplayObserver(Context context, Handler handler, BallotBox ballotBox) {
+        DisplayObserver(Context context, Handler handler, VotesStorage votesStorage) {
             mContext = context;
             mHandler = handler;
-            mBallotBox = ballotBox;
+            mVotesStorage = votesStorage;
         }
 
         public void observe() {
-            DisplayManager dm = mContext.getSystemService(DisplayManager.class);
-            dm.registerDisplayListener(this, mHandler);
+            mInjector.registerDisplayListener(this, mHandler);
 
             // Populate existing displays
             SparseArray<Display.Mode[]> modes = new SparseArray<>();
             SparseArray<Display.Mode> defaultModes = new SparseArray<>();
             DisplayInfo info = new DisplayInfo();
-            Display[] displays = dm.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+            Display[] displays = mInjector.getDisplays();
             for (Display d : displays) {
                 final int displayId = d.getDisplayId();
                 d.getDisplayInfo(info);
@@ -1756,23 +1440,16 @@
 
         @Nullable
         private DisplayInfo getDisplayInfo(int displayId) {
-            Display d = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
-            if (d == null) {
-                // We can occasionally get a display added or changed event for a display that was
-                // subsequently removed, which means this returns null. Check this case and bail
-                // out early; if it gets re-attached we'll eventually get another call back for it.
-                return null;
-            }
             DisplayInfo info = new DisplayInfo();
-            d.getDisplayInfo(info);
-            return info;
+            // Display info might be invalid, in this case return null
+            return mInjector.getDisplayInfo(displayId, info) ? info : null;
         }
 
         private void updateLayoutLimitedFrameRate(int displayId, @Nullable DisplayInfo info) {
             Vote vote = info != null && info.layoutLimitedRefreshRate != null
                     ? Vote.forPhysicalRefreshRates(info.layoutLimitedRefreshRate.min,
                     info.layoutLimitedRefreshRate.max) : null;
-            mBallotBox.vote(displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, vote);
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, vote);
         }
 
         private void updateDisplayModes(int displayId, @Nullable DisplayInfo info) {
@@ -2094,8 +1771,8 @@
                 updateSensorStatus();
                 if (!changeable) {
                     // Revoke previous vote from BrightnessObserver
-                    updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE, null);
-                    updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, null);
+                    mVotesStorage.updateGlobalVote(Vote.PRIORITY_FLICKER_REFRESH_RATE, null);
+                    mVotesStorage.updateGlobalVote(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, null);
                 }
             }
         }
@@ -2420,8 +2097,9 @@
                 Slog.d(TAG, "Display brightness " + mBrightness + ", ambient lux " +  mAmbientLux
                         + ", Vote " + refreshRateVote);
             }
-            updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE, refreshRateVote);
-            updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, refreshRateSwitchingVote);
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_FLICKER_REFRESH_RATE, refreshRateVote);
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH,
+                    refreshRateSwitchingVote);
         }
 
         private boolean hasValidLowZone() {
@@ -2435,8 +2113,7 @@
         }
 
         private void updateDefaultDisplayState() {
-            Display display = mContext.getSystemService(DisplayManager.class)
-                    .getDisplay(Display.DEFAULT_DISPLAY);
+            Display display = mInjector.getDisplay(Display.DEFAULT_DISPLAY);
             if (display == null) {
                 return;
             }
@@ -2690,7 +2367,7 @@
             } else {
                 vote = null;
             }
-            DisplayModeDirector.this.updateVoteLocked(displayId, votePriority, vote);
+            mVotesStorage.updateVote(displayId, votePriority, vote);
         }
 
         void dumpLocked(PrintWriter pw) {
@@ -2716,7 +2393,7 @@
         private final String mProximitySensorName = null;
         private final String mProximitySensorType = Sensor.STRING_TYPE_PROXIMITY;
 
-        private final BallotBox mBallotBox;
+        private final VotesStorage mVotesStorage;
         private final Context mContext;
         private final Injector mInjector;
         @GuardedBy("mSensorObserverLock")
@@ -2728,9 +2405,9 @@
         @GuardedBy("mSensorObserverLock")
         private boolean mIsProxActive = false;
 
-        SensorObserver(Context context, BallotBox ballotBox, Injector injector) {
+        SensorObserver(Context context, VotesStorage votesStorage, Injector injector) {
             mContext = context;
-            mBallotBox = ballotBox;
+            mVotesStorage = votesStorage;
             mInjector = injector;
         }
 
@@ -2753,8 +2430,7 @@
             sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this);
 
             synchronized (mSensorObserverLock) {
-                for (Display d : mDisplayManager.getDisplays(
-                        DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
+                for (Display d : mInjector.getDisplays()) {
                     mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d));
                 }
             }
@@ -2765,8 +2441,7 @@
         }
 
         private void recalculateVotesLocked() {
-            final Display[] displays = mDisplayManager.getDisplays(
-                    DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+            final Display[] displays = mInjector.getDisplays();
             for (Display d : displays) {
                 int displayId = d.getDisplayId();
                 Vote vote = null;
@@ -2778,7 +2453,7 @@
                         vote = Vote.forPhysicalRefreshRates(rate.min, rate.max);
                     }
                 }
-                mBallotBox.vote(displayId, Vote.PRIORITY_PROXIMITY, vote);
+                mVotesStorage.updateVote(displayId, Vote.PRIORITY_PROXIMITY, vote);
             }
         }
 
@@ -2797,7 +2472,7 @@
 
         @Override
         public void onDisplayAdded(int displayId) {
-            boolean isDozeState = mInjector.isDozeState(mDisplayManager.getDisplay(displayId));
+            boolean isDozeState = mInjector.isDozeState(mInjector.getDisplay(displayId));
             synchronized (mSensorObserverLock) {
                 mDozeStateByDisplay.put(displayId, isDozeState);
                 recalculateVotesLocked();
@@ -2809,7 +2484,7 @@
             boolean wasDozeState = mDozeStateByDisplay.get(displayId);
             synchronized (mSensorObserverLock) {
                 mDozeStateByDisplay.put(displayId,
-                        mInjector.isDozeState(mDisplayManager.getDisplay(displayId)));
+                        mInjector.isDozeState(mInjector.getDisplay(displayId)));
                 if (wasDozeState != mDozeStateByDisplay.get(displayId)) {
                     recalculateVotesLocked();
                 }
@@ -2831,7 +2506,7 @@
      * DisplayManagerInternal but originate in the display-device-config file.
      */
     public class HbmObserver implements DisplayManager.DisplayListener {
-        private final BallotBox mBallotBox;
+        private final VotesStorage mVotesStorage;
         private final Handler mHandler;
         private final SparseIntArray mHbmMode = new SparseIntArray();
         private final SparseBooleanArray mHbmActive = new SparseBooleanArray();
@@ -2842,10 +2517,10 @@
 
         private DisplayManagerInternal mDisplayManagerInternal;
 
-        HbmObserver(Injector injector, BallotBox ballotBox, Handler handler,
+        HbmObserver(Injector injector, VotesStorage votesStorage, Handler handler,
                 DeviceConfigDisplaySettings displaySettings) {
             mInjector = injector;
-            mBallotBox = ballotBox;
+            mVotesStorage = votesStorage;
             mHandler = handler;
             mDeviceConfigDisplaySettings = displaySettings;
         }
@@ -2915,7 +2590,7 @@
 
         @Override
         public void onDisplayRemoved(int displayId) {
-            mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, null);
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, null);
             mHbmMode.delete(displayId);
             mHbmActive.delete(displayId);
         }
@@ -2982,7 +2657,7 @@
                 }
 
             }
-            mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote);
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote);
         }
 
         void dumpLocked(PrintWriter pw) {
@@ -3165,17 +2840,27 @@
     }
 
     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,
+                Handler handler);
+
+        void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
                 Handler handler, long flags);
 
+        Display getDisplay(int displayId);
+
         Display[] getDisplays();
 
         boolean getDisplayInfo(int displayId, DisplayInfo displayInfo);
@@ -3205,19 +2890,37 @@
         }
 
         @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);
+        }
+
+        @Override
+        public void registerDisplayListener(DisplayManager.DisplayListener listener,
+                Handler handler) {
+            getDisplayManager().registerDisplayListener(listener, handler);
+        }
+
+        @Override
         public void registerDisplayListener(DisplayManager.DisplayListener listener,
                 Handler handler, long flags) {
             getDisplayManager().registerDisplayListener(listener, handler, flags);
         }
 
         @Override
+        public Display getDisplay(int displayId) {
+            return getDisplayManager().getDisplay(displayId);
+        }
+
+        @Override
         public Display[] getDisplays() {
             return getDisplayManager().getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
         }
@@ -3225,10 +2928,13 @@
         @Override
         public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) {
             Display display = getDisplayManager().getDisplay(displayId);
-            if (display != null) {
-                return display.getDisplayInfo(displayInfo);
+            if (display == null) {
+                // We can occasionally get a display added or changed event for a display that was
+                // subsequently removed, which means this returns null. Check this case and bail
+                // out early; if it gets re-attached we'll eventually get another call back for it.
+                return false;
             }
-            return false;
+            return display.getDisplayInfo(displayInfo);
         }
 
         @Override
@@ -3282,8 +2988,4 @@
                     ServiceManager.getService(Context.THERMAL_SERVICE));
         }
     }
-
-    interface BallotBox {
-        void vote(int displayId, int priority, Vote vote);
-    }
 }
diff --git a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
index c04735d..58e1550 100644
--- a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
+++ b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
@@ -37,7 +37,7 @@
         DisplayManager.DisplayListener {
     private static final String TAG = "SkinThermalStatusObserver";
 
-    private final DisplayModeDirector.BallotBox mBallotBox;
+    private final VotesStorage mVotesStorage;
     private final DisplayModeDirector.Injector mInjector;
 
     private boolean mLoggingEnabled;
@@ -52,15 +52,15 @@
             mThermalThrottlingByDisplay = new SparseArray<>();
 
     SkinThermalStatusObserver(DisplayModeDirector.Injector injector,
-            DisplayModeDirector.BallotBox ballotBox) {
-        this(injector, ballotBox, BackgroundThread.getHandler());
+            VotesStorage votesStorage) {
+        this(injector, votesStorage, BackgroundThread.getHandler());
     }
 
     @VisibleForTesting
     SkinThermalStatusObserver(DisplayModeDirector.Injector injector,
-            DisplayModeDirector.BallotBox ballotBox, Handler handler) {
+            VotesStorage votesStorage, Handler handler) {
         mInjector = injector;
-        mBallotBox = ballotBox;
+        mVotesStorage = votesStorage;
         mHandler = handler;
     }
 
@@ -112,8 +112,8 @@
     public void onDisplayRemoved(int displayId) {
         synchronized (mThermalObserverLock) {
             mThermalThrottlingByDisplay.remove(displayId);
-            mHandler.post(() -> mBallotBox.vote(displayId,
-                    DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE, null));
+            mHandler.post(() -> mVotesStorage.updateVote(displayId,
+                    Vote.PRIORITY_SKIN_TEMPERATURE, null));
         }
         if (mLoggingEnabled) {
             Slog.d(TAG, "Display removed and voted: displayId=" + displayId);
@@ -138,7 +138,7 @@
         for (Display d : displays) {
             final int displayId = d.getDisplayId();
             d.getDisplayInfo(info);
-            localMap.put(displayId, info.refreshRateThermalThrottling);
+            localMap.put(displayId, info.thermalRefreshRateThrottling);
         }
         synchronized (mThermalObserverLock) {
             for (int i = 0; i < size; i++) {
@@ -154,7 +154,7 @@
         DisplayInfo displayInfo = new DisplayInfo();
         mInjector.getDisplayInfo(displayId, displayInfo);
         SparseArray<SurfaceControl.RefreshRateRange> throttlingMap =
-                displayInfo.refreshRateThermalThrottling;
+                displayInfo.thermalRefreshRateThrottling;
 
         synchronized (mThermalObserverLock) {
             mThermalThrottlingByDisplay.put(displayId, throttlingMap);
@@ -218,11 +218,11 @@
         SurfaceControl.RefreshRateRange foundRange = findBestMatchingRefreshRateRange(currentStatus,
                 throttlingMap);
         // if status <= currentStatus not found in the map reset vote
-        DisplayModeDirector.Vote vote = null;
+        Vote vote = null;
         if (foundRange != null) { // otherwise vote with found range
-            vote = DisplayModeDirector.Vote.forRenderFrameRates(foundRange.min, foundRange.max);
+            vote = Vote.forRenderFrameRates(foundRange.min, foundRange.max);
         }
-        mBallotBox.vote(displayId, DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE, vote);
+        mVotesStorage.updateVote(displayId, Vote.PRIORITY_SKIN_TEMPERATURE, vote);
         if (mLoggingEnabled) {
             Slog.d(TAG, "Voted: vote=" + vote + ", display =" + displayId);
         }
@@ -244,11 +244,11 @@
 
     private void fallbackReportThrottlingIfNeeded(int displayId,
             @Temperature.ThrottlingStatus int currentStatus) {
-        DisplayModeDirector.Vote vote = null;
+        Vote vote = null;
         if (currentStatus >= Temperature.THROTTLING_CRITICAL) {
-            vote = DisplayModeDirector.Vote.forRenderFrameRates(0f, 60f);
+            vote = Vote.forRenderFrameRates(0f, 60f);
         }
-        mBallotBox.vote(displayId, DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE, vote);
+        mVotesStorage.updateVote(displayId, Vote.PRIORITY_SKIN_TEMPERATURE, vote);
         if (mLoggingEnabled) {
             Slog.d(TAG, "Voted(fallback): vote=" + vote + ", display =" + displayId);
         }
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
new file mode 100644
index 0000000..a42d8f2
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -0,0 +1,237 @@
+/*
+ * 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.mode;
+
+import android.view.SurfaceControl;
+
+final class Vote {
+    // DEFAULT_RENDER_FRAME_RATE votes for render frame rate [0, DEFAULT]. As the lowest
+    // priority vote, it's overridden by all other considerations. It acts to set a default
+    // frame rate for a device.
+    static final int PRIORITY_DEFAULT_RENDER_FRAME_RATE = 0;
+
+    // PRIORITY_FLICKER_REFRESH_RATE votes for a single refresh rate like [60,60], [90,90] or
+    // null. It is used to set a preferred refresh rate value in case the higher priority votes
+    // result is a range.
+    static final int PRIORITY_FLICKER_REFRESH_RATE = 1;
+
+    // High-brightness-mode may need a specific range of refresh-rates to function properly.
+    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 [minRefreshRate, Float.POSITIVE_INFINITY]
+    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
+    // frame rate in certain cases, mostly to preserve power.
+    // @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate
+    // @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate
+    // It votes to [preferredMinRefreshRate, preferredMaxRefreshRate].
+    static final int PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 4;
+
+    // We split the app request into different priorities in case we can satisfy one desire
+    // without the other.
+
+    // Application can specify preferred refresh rate with below attrs.
+    // @see android.view.WindowManager.LayoutParams#preferredRefreshRate
+    // @see android.view.WindowManager.LayoutParams#preferredDisplayModeId
+    //
+    // When the app specifies a LayoutParams#preferredDisplayModeId, in addition to the
+    // refresh rate, it also chooses a preferred size (resolution) as part of the selected
+    // mode id. The app preference is then translated to APP_REQUEST_BASE_MODE_REFRESH_RATE and
+    // optionally to APP_REQUEST_SIZE as well, if a mode id was selected.
+    // The system also forces some apps like denylisted app to run at a lower refresh rate.
+    // @see android.R.array#config_highRefreshRateBlacklist
+    //
+    // When summarizing the votes and filtering the allowed display modes, these votes determine
+    // which mode id should be the base mode id to be sent to SurfaceFlinger:
+    // - APP_REQUEST_BASE_MODE_REFRESH_RATE is used to validate the vote summary. If a summary
+    //   includes a base mode refresh rate, but it is not in the refresh rate range, then the
+    //   summary is considered invalid so we could drop a lower priority vote and try again.
+    // - APP_REQUEST_SIZE is used to filter out display modes of a different size.
+    //
+    // The preferred refresh rate is set on the main surface of the app outside of
+    // DisplayModeDirector.
+    // @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded
+    static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 5;
+    static final int PRIORITY_APP_REQUEST_SIZE = 6;
+
+    // SETTING_PEAK_RENDER_FRAME_RATE has a high priority and will restrict the bounds of the
+    // rest of low priority voters. It votes [0, max(PEAK, MIN)]
+    static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 7;
+
+    // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh
+    // rate to max value (same as for PRIORITY_UDFPS) on lock screen
+    static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 8;
+
+    // For concurrent displays we want to limit refresh rate on all displays
+    static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 9;
+
+    // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if
+    // Settings.Global.LOW_POWER_MODE is on.
+    static final int PRIORITY_LOW_POWER_MODE = 10;
+
+    // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
+    // higher priority voters' result is a range, it will fix the rate to a single choice.
+    // It's used to avoid refresh rate switches in certain conditions which may result in the
+    // user seeing the display flickering when the switches occur.
+    static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 11;
+
+    // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
+    static final int PRIORITY_SKIN_TEMPERATURE = 12;
+
+    // The proximity sensor needs the refresh rate to be locked in order to function, so this is
+    // set to a high priority.
+    static final int PRIORITY_PROXIMITY = 13;
+
+    // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
+    // to function, so this needs to be the highest priority of all votes.
+    static final int PRIORITY_UDFPS = 14;
+
+    // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
+    // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
+
+    static final int MIN_PRIORITY = PRIORITY_DEFAULT_RENDER_FRAME_RATE;
+    static final int MAX_PRIORITY = PRIORITY_UDFPS;
+
+    // The cutoff for the app request refresh rate range. Votes with priorities lower than this
+    // value will not be considered when constructing the app request refresh rate range.
+    static final int APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF =
+            PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE;
+
+    /**
+     * A value signifying an invalid width or height in a vote.
+     */
+    static final int INVALID_SIZE = -1;
+
+    /**
+     * The requested width of the display in pixels, or INVALID_SIZE;
+     */
+    public final int width;
+    /**
+     * The requested height of the display in pixels, or INVALID_SIZE;
+     */
+    public final int height;
+    /**
+     * Information about the refresh rate frame rate ranges DM would like to set the display to.
+     */
+    public final SurfaceControl.RefreshRateRanges refreshRateRanges;
+
+    /**
+     * Whether refresh rate switching should be disabled (i.e. the refresh rate range is
+     * a single value).
+     */
+    public final boolean disableRefreshRateSwitching;
+
+    /**
+     * The preferred refresh rate selected by the app. It is used to validate that the summary
+     * refresh rate ranges include this value, and are not restricted by a lower priority vote.
+     */
+    public final float appRequestBaseModeRefreshRate;
+
+    static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) {
+        return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate, 0,
+                Float.POSITIVE_INFINITY,
+                minRefreshRate == maxRefreshRate, 0f);
+    }
+
+    static Vote forRenderFrameRates(float minFrameRate, float maxFrameRate) {
+        return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, minFrameRate,
+                maxFrameRate,
+                false, 0f);
+    }
+
+    static Vote forSize(int width, int height) {
+        return new Vote(width, height, 0, Float.POSITIVE_INFINITY, 0, Float.POSITIVE_INFINITY,
+                false,
+                0f);
+    }
+
+    static Vote forDisableRefreshRateSwitching() {
+        return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0,
+                Float.POSITIVE_INFINITY, true,
+                0f);
+    }
+
+    static Vote forBaseModeRefreshRate(float baseModeRefreshRate) {
+        return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0,
+                Float.POSITIVE_INFINITY, false,
+                baseModeRefreshRate);
+    }
+
+    private Vote(int width, int height,
+            float minPhysicalRefreshRate,
+            float maxPhysicalRefreshRate,
+            float minRenderFrameRate,
+            float maxRenderFrameRate,
+            boolean disableRefreshRateSwitching,
+            float baseModeRefreshRate) {
+        this.width = width;
+        this.height = height;
+        this.refreshRateRanges = new SurfaceControl.RefreshRateRanges(
+                new SurfaceControl.RefreshRateRange(minPhysicalRefreshRate, maxPhysicalRefreshRate),
+                new SurfaceControl.RefreshRateRange(minRenderFrameRate, maxRenderFrameRate));
+        this.disableRefreshRateSwitching = disableRefreshRateSwitching;
+        this.appRequestBaseModeRefreshRate = baseModeRefreshRate;
+    }
+
+    static String priorityToString(int priority) {
+        switch (priority) {
+            case PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE:
+                return "PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE";
+            case PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE:
+                return "PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE";
+            case PRIORITY_APP_REQUEST_SIZE:
+                return "PRIORITY_APP_REQUEST_SIZE";
+            case PRIORITY_DEFAULT_RENDER_FRAME_RATE:
+                return "PRIORITY_DEFAULT_REFRESH_RATE";
+            case PRIORITY_FLICKER_REFRESH_RATE:
+                return "PRIORITY_FLICKER_REFRESH_RATE";
+            case PRIORITY_FLICKER_REFRESH_RATE_SWITCH:
+                return "PRIORITY_FLICKER_REFRESH_RATE_SWITCH";
+            case PRIORITY_HIGH_BRIGHTNESS_MODE:
+                return "PRIORITY_HIGH_BRIGHTNESS_MODE";
+            case PRIORITY_PROXIMITY:
+                return "PRIORITY_PROXIMITY";
+            case PRIORITY_LOW_POWER_MODE:
+                return "PRIORITY_LOW_POWER_MODE";
+            case PRIORITY_SKIN_TEMPERATURE:
+                return "PRIORITY_SKIN_TEMPERATURE";
+            case PRIORITY_UDFPS:
+                return "PRIORITY_UDFPS";
+            case PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE:
+                return "PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE";
+            case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE:
+                return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE";
+            case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE:
+                return "PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE";
+            case PRIORITY_LAYOUT_LIMITED_FRAME_RATE:
+                return "PRIORITY_LAYOUT_LIMITED_FRAME_RATE";
+            default:
+                return Integer.toString(priority);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Vote: {"
+                + "width: " + width + ", height: " + height
+                + ", refreshRateRanges: " + refreshRateRanges
+                + ", disableRefreshRateSwitching: " + disableRefreshRateSwitching
+                + ", appRequestBaseModeRefreshRate: "  + appRequestBaseModeRefreshRate + "}";
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java
new file mode 100644
index 0000000..dadcebe
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/VotesStorage.java
@@ -0,0 +1,152 @@
+/*
+ * 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.mode;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+
+class VotesStorage {
+    private static final String TAG = "VotesStorage";
+    // Special ID used to indicate that given vote is to be applied globally, rather than to a
+    // specific display.
+    private static final int GLOBAL_ID = -1;
+
+    private boolean mLoggingEnabled;
+
+    private final Listener mListener;
+
+    private final Object mStorageLock = new Object();
+    // A map from the display ID to the collection of votes and their priority. The latter takes
+    // the form of another map from the priority to the vote itself so that each priority is
+    // guaranteed to have exactly one vote, which is also easily and efficiently replaceable.
+    @GuardedBy("mStorageLock")
+    private final SparseArray<SparseArray<Vote>> mVotesByDisplay = new SparseArray<>();
+
+    VotesStorage(@NonNull Listener listener) {
+        mListener = listener;
+    }
+    /** sets logging enabled/disabled for this class */
+    void setLoggingEnabled(boolean loggingEnabled) {
+        mLoggingEnabled = loggingEnabled;
+    }
+    /**
+     * gets all votes for specific display, note that global display votes are also added to result
+     */
+    @NonNull
+    SparseArray<Vote> getVotes(int displayId) {
+        SparseArray<Vote> votesLocal;
+        SparseArray<Vote> globalVotesLocal;
+        synchronized (mStorageLock) {
+            SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
+            votesLocal = displayVotes != null ? displayVotes.clone() : new SparseArray<>();
+            SparseArray<Vote> globalVotes = mVotesByDisplay.get(GLOBAL_ID);
+            globalVotesLocal = globalVotes != null ? globalVotes.clone() : new SparseArray<>();
+        }
+        for (int i = 0; i < globalVotesLocal.size(); i++) {
+            int priority = globalVotesLocal.keyAt(i);
+            if (!votesLocal.contains(priority)) {
+                votesLocal.put(priority, globalVotesLocal.valueAt(i));
+            }
+        }
+        return votesLocal;
+    }
+
+    /** updates vote storage for all displays */
+    void updateGlobalVote(int priority, @Nullable Vote vote) {
+        updateVote(GLOBAL_ID, priority, vote);
+    }
+
+    /** updates vote storage */
+    void updateVote(int displayId, int priority, @Nullable Vote vote) {
+        if (mLoggingEnabled) {
+            Slog.i(TAG, "updateVoteLocked(displayId=" + displayId
+                    + ", priority=" + Vote.priorityToString(priority)
+                    + ", vote=" + vote + ")");
+        }
+        if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) {
+            Slog.w(TAG, "Received a vote with an invalid priority, ignoring:"
+                    + " priority=" + Vote.priorityToString(priority)
+                    + ", vote=" + vote);
+            return;
+        }
+        SparseArray<Vote> votes;
+        synchronized (mStorageLock) {
+            if (mVotesByDisplay.contains(displayId)) {
+                votes = mVotesByDisplay.get(displayId);
+            } else {
+                votes = new SparseArray<>();
+                mVotesByDisplay.put(displayId, votes);
+            }
+            if (vote != null) {
+                votes.put(priority, vote);
+            } else {
+                votes.remove(priority);
+            }
+        }
+        if (mLoggingEnabled) {
+            Slog.i(TAG, "Updated votes for display=" + displayId + " votes=" + votes);
+        }
+        mListener.onChanged();
+    }
+
+    /** dump class values, for debugging */
+    void dump(@NonNull PrintWriter pw) {
+        SparseArray<SparseArray<Vote>> votesByDisplayLocal = new SparseArray<>();
+        synchronized (mStorageLock) {
+            for (int i = 0; i < mVotesByDisplay.size(); i++) {
+                votesByDisplayLocal.put(mVotesByDisplay.keyAt(i),
+                        mVotesByDisplay.valueAt(i).clone());
+            }
+        }
+        pw.println("  mVotesByDisplay:");
+        for (int i = 0; i < votesByDisplayLocal.size(); i++) {
+            SparseArray<Vote> votes = votesByDisplayLocal.valueAt(i);
+            if (votes.size() == 0) {
+                continue;
+            }
+            pw.println("    " + votesByDisplayLocal.keyAt(i) + ":");
+            for (int p = Vote.MAX_PRIORITY; p >= Vote.MIN_PRIORITY; p--) {
+                Vote vote = votes.get(p);
+                if (vote == null) {
+                    continue;
+                }
+                pw.println("      " + Vote.priorityToString(p) + " -> " + vote);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void injectVotesByDisplay(SparseArray<SparseArray<Vote>> votesByDisplay) {
+        synchronized (mStorageLock) {
+            mVotesByDisplay.clear();
+            for (int i = 0; i < votesByDisplay.size(); i++) {
+                mVotesByDisplay.put(votesByDisplay.keyAt(i), votesByDisplay.valueAt(i));
+            }
+        }
+    }
+
+    interface Listener {
+        void onChanged();
+    }
+}
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/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index 048308e..48c346a 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -199,8 +199,11 @@
         }
         if (brightness.isPresent()) {
             int brightnessValue = Math.max(0, Math.min(MAX_BRIGHTNESS, brightness.getAsInt()));
-            int brightnessLevel = Arrays.binarySearch(BRIGHTNESS_VALUE_FOR_LEVEL, brightnessValue);
-            updateBacklightState(inputDevice.getId(), keyboardBacklight, brightnessLevel,
+            int index = Arrays.binarySearch(BRIGHTNESS_VALUE_FOR_LEVEL, brightnessValue);
+            if (index < 0) {
+                index = Math.min(NUM_BRIGHTNESS_CHANGE_STEPS, -(index + 1));
+            }
+            updateBacklightState(inputDevice.getId(), keyboardBacklight, index,
                     false /* isTriggeredByKeyPress */);
             if (DEBUG) {
                 Slog.d(TAG, "Restoring brightness level " + brightness.getAsInt());
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 72c7dad..d8716b3 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -16,6 +16,8 @@
 
 package com.android.server.input;
 
+import android.annotation.AnyThread;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -99,6 +101,7 @@
     private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2;
     private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3;
     private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
+    private static final int MSG_CURRENT_IME_INFO_CHANGED = 5;
 
     private final Context mContext;
     private final NativeInputManagerService mNative;
@@ -108,16 +111,17 @@
     private final Handler mHandler;
 
     // Connected keyboards with associated keyboard layouts (either auto-detected or manually
-    // selected layout). If the mapped value is null/empty, it means that no layout has been
-    // configured for the keyboard and user might need to manually configure it from the Settings.
-    private final SparseArray<Set<String>> mConfiguredKeyboards = new SparseArray<>();
+    // selected layout).
+    private final SparseArray<KeyboardConfiguration> mConfiguredKeyboards = new SparseArray<>();
     private Toast mSwitchedKeyboardLayoutToast;
 
     // This cache stores "best-matched" layouts so that we don't need to run the matching
     // algorithm repeatedly.
     @GuardedBy("mKeyboardLayoutCache")
     private final Map<String, String> mKeyboardLayoutCache = new ArrayMap<>();
+    private final Object mImeInfoLock = new Object();
     @Nullable
+    @GuardedBy("mImeInfoLock")
     private ImeInfo mCurrentImeInfo;
 
     KeyboardLayoutManager(Context context, NativeInputManagerService nativeService,
@@ -155,26 +159,32 @@
     }
 
     @Override
+    @MainThread
     public void onInputDeviceAdded(int deviceId) {
         onInputDeviceChanged(deviceId);
-        if (useNewSettingsUi()) {
-            // Force native callback to set up keyboard layout overlay for newly added keyboards
-            reloadKeyboardLayouts();
-        }
     }
 
     @Override
+    @MainThread
     public void onInputDeviceRemoved(int deviceId) {
         mConfiguredKeyboards.remove(deviceId);
         maybeUpdateNotification();
     }
 
     @Override
+    @MainThread
     public void onInputDeviceChanged(int deviceId) {
         final InputDevice inputDevice = getInputDevice(deviceId);
         if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
             return;
         }
+        KeyboardConfiguration config = mConfiguredKeyboards.get(deviceId);
+        if (config == null) {
+            config = new KeyboardConfiguration();
+            mConfiguredKeyboards.put(deviceId, config);
+        }
+
+        boolean needToShowNotification = false;
         if (!useNewSettingsUi()) {
             synchronized (mDataStore) {
                 String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
@@ -182,54 +192,66 @@
                     layout = getDefaultKeyboardLayout(inputDevice);
                     if (layout != null) {
                         setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
-                    } else {
-                        mConfiguredKeyboards.put(inputDevice.getId(), new HashSet<>());
                     }
                 }
+                config.setCurrentLayout(layout);
+                if (layout == null) {
+                    // In old settings show notification always until user manually selects a
+                    // layout in the settings.
+                    needToShowNotification = true;
+                }
             }
         } else {
             final InputDeviceIdentifier identifier = inputDevice.getIdentifier();
             final String key = getLayoutDescriptor(identifier);
             Set<String> selectedLayouts = new HashSet<>();
-            boolean needToShowMissingLayoutNotification = false;
             for (ImeInfo imeInfo : getImeInfoListForLayoutMapping()) {
                 // Check if the layout has been previously configured
                 String layout = getKeyboardLayoutForInputDeviceInternal(identifier,
                         new ImeInfo(imeInfo.mUserId, imeInfo.mImeSubtypeHandle,
                                 imeInfo.mImeSubtype));
                 if (layout == null) {
-                    needToShowMissingLayoutNotification = true;
-                    continue;
+                    // If even one layout not configured properly, we need to ask user to configure
+                    // the keyboard properly from the Settings.
+                    selectedLayouts.clear();
+                    break;
                 }
                 selectedLayouts.add(layout);
             }
 
-            if (needToShowMissingLayoutNotification) {
-                // If even one layout not configured properly we will show configuration
-                // notification allowing user to set the keyboard layout.
-                selectedLayouts.clear();
-            }
-
             if (DEBUG) {
                 Slog.d(TAG,
                         "Layouts selected for input device: " + identifier + " -> selectedLayouts: "
                                 + selectedLayouts);
             }
-            mConfiguredKeyboards.set(inputDevice.getId(), selectedLayouts);
+
+            config.setConfiguredLayouts(selectedLayouts);
+
+            // Update current layout: If there is a change then need to reload.
+            synchronized (mImeInfoLock) {
+                String layout = getKeyboardLayoutForInputDeviceInternal(
+                        inputDevice.getIdentifier(), mCurrentImeInfo);
+                if (!Objects.equals(layout, config.getCurrentLayout())) {
+                    config.setCurrentLayout(layout);
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            }
 
             synchronized (mDataStore) {
                 try {
-                    if (!mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
-                        // No need to show the notification only if layout selection didn't change
+                    if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
+                        // Need to show the notification only if layout selection changed
                         // from the previous configuration
-                        return;
+                        needToShowNotification = true;
                     }
                 } finally {
                     mDataStore.saveIfNeeded();
                 }
             }
         }
-        maybeUpdateNotification();
+        if (needToShowNotification) {
+            maybeUpdateNotification();
+        }
     }
 
     private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
@@ -323,12 +345,14 @@
         reloadKeyboardLayouts();
     }
 
+    @AnyThread
     public KeyboardLayout[] getKeyboardLayouts() {
         final ArrayList<KeyboardLayout> list = new ArrayList<>();
         visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> list.add(layout));
         return list.toArray(new KeyboardLayout[0]);
     }
 
+    @AnyThread
     public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
             final InputDeviceIdentifier identifier) {
         if (useNewSettingsUi()) {
@@ -375,6 +399,7 @@
                 KeyboardLayout[]::new);
     }
 
+    @AnyThread
     @Nullable
     public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
         Objects.requireNonNull(keyboardLayoutDescriptor,
@@ -543,6 +568,7 @@
         return key.toString();
     }
 
+    @AnyThread
     @Nullable
     public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
         if (useNewSettingsUi()) {
@@ -566,6 +592,7 @@
         }
     }
 
+    @AnyThread
     public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
         if (useNewSettingsUi()) {
@@ -592,6 +619,7 @@
         }
     }
 
+    @AnyThread
     public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
         if (useNewSettingsUi()) {
             Slog.e(TAG, "getEnabledKeyboardLayoutsForInputDevice API not supported");
@@ -608,6 +636,7 @@
         }
     }
 
+    @AnyThread
     public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
         if (useNewSettingsUi()) {
@@ -635,6 +664,7 @@
         }
     }
 
+    @AnyThread
     public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
         if (useNewSettingsUi()) {
@@ -667,6 +697,7 @@
         }
     }
 
+    @AnyThread
     public void switchKeyboardLayout(int deviceId, int direction) {
         if (useNewSettingsUi()) {
             Slog.e(TAG, "switchKeyboardLayout API not supported");
@@ -675,7 +706,7 @@
         mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
     }
 
-    // Must be called on handler.
+    @MainThread
     private void handleSwitchKeyboardLayout(int deviceId, int direction) {
         final InputDevice device = getInputDevice(deviceId);
         if (device != null) {
@@ -713,23 +744,14 @@
     }
 
     @Nullable
+    @AnyThread
     public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
         String keyboardLayoutDescriptor;
         if (useNewSettingsUi()) {
-            InputDevice inputDevice = getInputDevice(identifier);
-            if (inputDevice == null) {
-                // getKeyboardLayoutOverlay() called before input device added completely. Need
-                // to wait till the device is added which will call reloadKeyboardLayouts()
-                return null;
+            synchronized (mImeInfoLock) {
+                keyboardLayoutDescriptor = getKeyboardLayoutForInputDeviceInternal(identifier,
+                        mCurrentImeInfo);
             }
-            if (mCurrentImeInfo == null) {
-                // Haven't received onInputMethodSubtypeChanged() callback from IMMS. Will reload
-                // keyboard layouts once we receive the callback.
-                return null;
-            }
-
-            keyboardLayoutDescriptor = getKeyboardLayoutForInputDeviceInternal(identifier,
-                    mCurrentImeInfo);
         } else {
             keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
         }
@@ -755,6 +777,7 @@
         return result;
     }
 
+    @AnyThread
     @Nullable
     public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
@@ -773,6 +796,7 @@
         return layout;
     }
 
+    @AnyThread
     public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
             @Nullable InputMethodSubtype imeSubtype,
@@ -783,8 +807,8 @@
         }
         Objects.requireNonNull(keyboardLayoutDescriptor,
                 "keyboardLayoutDescriptor must not be null");
-        String key = createLayoutKey(identifier, userId,
-                InputMethodSubtypeHandle.of(imeInfo, imeSubtype));
+        String key = createLayoutKey(identifier,
+                new ImeInfo(userId, InputMethodSubtypeHandle.of(imeInfo, imeSubtype), imeSubtype));
         synchronized (mDataStore) {
             try {
                 // Key for storing into data store = <device descriptor>,<userId>,<subtypeHandle>
@@ -803,6 +827,7 @@
         }
     }
 
+    @AnyThread
     public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
             @Nullable InputMethodSubtype imeSubtype) {
@@ -815,8 +840,8 @@
     }
 
     private KeyboardLayout[] getKeyboardLayoutListForInputDeviceInternal(
-            InputDeviceIdentifier identifier, ImeInfo imeInfo) {
-        String key = createLayoutKey(identifier, imeInfo.mUserId, imeInfo.mImeSubtypeHandle);
+            InputDeviceIdentifier identifier, @Nullable ImeInfo imeInfo) {
+        String key = createLayoutKey(identifier, imeInfo);
 
         // Fetch user selected layout and always include it in layout list.
         String userSelectedLayout;
@@ -826,7 +851,7 @@
 
         final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>();
         String imeLanguageTag;
-        if (imeInfo.mImeSubtype == null) {
+        if (imeInfo == null || imeInfo.mImeSubtype == null) {
             imeLanguageTag = "";
         } else {
             ULocale imeLocale = imeInfo.mImeSubtype.getPhysicalKeyboardHintLanguageTag();
@@ -866,6 +891,7 @@
         return potentialLayouts.toArray(new KeyboardLayout[0]);
     }
 
+    @AnyThread
     public void onInputMethodSubtypeChanged(@UserIdInt int userId,
             @Nullable InputMethodSubtypeHandle subtypeHandle,
             @Nullable InputMethodSubtype subtype) {
@@ -879,25 +905,45 @@
             }
             return;
         }
-        if (mCurrentImeInfo == null || !subtypeHandle.equals(mCurrentImeInfo.mImeSubtypeHandle)
-                || mCurrentImeInfo.mUserId != userId) {
-            mCurrentImeInfo = new ImeInfo(userId, subtypeHandle, subtype);
-            mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
-            if (DEBUG) {
-                Slog.d(TAG, "InputMethodSubtype changed: userId=" + userId
-                        + " subtypeHandle=" + subtypeHandle);
+        synchronized (mImeInfoLock) {
+            if (mCurrentImeInfo == null || !subtypeHandle.equals(mCurrentImeInfo.mImeSubtypeHandle)
+                    || mCurrentImeInfo.mUserId != userId) {
+                mCurrentImeInfo = new ImeInfo(userId, subtypeHandle, subtype);
+                mHandler.sendEmptyMessage(MSG_CURRENT_IME_INFO_CHANGED);
+                if (DEBUG) {
+                    Slog.d(TAG, "InputMethodSubtype changed: userId=" + userId
+                            + " subtypeHandle=" + subtypeHandle);
+                }
+            }
+        }
+    }
+
+    @MainThread
+    private void onCurrentImeInfoChanged() {
+        synchronized (mImeInfoLock) {
+            for (int i = 0; i < mConfiguredKeyboards.size(); i++) {
+                InputDevice inputDevice = Objects.requireNonNull(
+                        getInputDevice(mConfiguredKeyboards.keyAt(i)));
+                String layout = getKeyboardLayoutForInputDeviceInternal(inputDevice.getIdentifier(),
+                        mCurrentImeInfo);
+                KeyboardConfiguration config = mConfiguredKeyboards.valueAt(i);
+                if (!Objects.equals(layout, config.getCurrentLayout())) {
+                    config.setCurrentLayout(layout);
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                    return;
+                }
             }
         }
     }
 
     @Nullable
     private String getKeyboardLayoutForInputDeviceInternal(InputDeviceIdentifier identifier,
-            ImeInfo imeInfo) {
+            @Nullable ImeInfo imeInfo) {
         InputDevice inputDevice = getInputDevice(identifier);
         if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
             return null;
         }
-        String key = createLayoutKey(identifier, imeInfo.mUserId, imeInfo.mImeSubtypeHandle);
+        String key = createLayoutKey(identifier, imeInfo);
         String layout;
         synchronized (mDataStore) {
             layout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key);
@@ -923,11 +969,7 @@
 
     @Nullable
     private static String getDefaultKeyboardLayoutBasedOnImeInfo(InputDevice inputDevice,
-            ImeInfo imeInfo, KeyboardLayout[] layoutList) {
-        if (imeInfo.mImeSubtypeHandle == null) {
-            return null;
-        }
-
+            @Nullable ImeInfo imeInfo, KeyboardLayout[] layoutList) {
         Arrays.sort(layoutList);
 
         // Check <VendorID, ProductID> matching for explicitly declared custom KCM files.
@@ -961,12 +1003,12 @@
             }
         }
 
-        InputMethodSubtype subtype = imeInfo.mImeSubtype;
-        // Can't auto select layout based on IME if subtype or language tag is null
-        if (subtype == null) {
+        if (imeInfo == null || imeInfo.mImeSubtypeHandle == null || imeInfo.mImeSubtype == null) {
+            // Can't auto select layout based on IME info is null
             return null;
         }
 
+        InputMethodSubtype subtype = imeInfo.mImeSubtype;
         // Check layout type, language tag information from IME for matching
         ULocale pkLocale = subtype.getPhysicalKeyboardHintLanguageTag();
         String pkLanguageTag =
@@ -1043,6 +1085,7 @@
         mNative.reloadKeyboardLayouts();
     }
 
+    @MainThread
     private void maybeUpdateNotification() {
         if (mConfiguredKeyboards.size() == 0) {
             hideKeyboardLayoutNotification();
@@ -1051,7 +1094,7 @@
         for (int i = 0; i < mConfiguredKeyboards.size(); i++) {
             // If we have a keyboard with no selected layouts, we should always show missing
             // layout notification even if there are other keyboards that are configured properly.
-            if (mConfiguredKeyboards.valueAt(i).isEmpty()) {
+            if (!mConfiguredKeyboards.valueAt(i).hasConfiguredLayouts()) {
                 showMissingKeyboardLayoutNotification();
                 return;
             }
@@ -1059,7 +1102,7 @@
         showConfiguredKeyboardLayoutNotification();
     }
 
-    // Must be called on handler.
+    @MainThread
     private void showMissingKeyboardLayoutNotification() {
         final Resources r = mContext.getResources();
         final String missingKeyboardLayoutNotificationContent = r.getString(
@@ -1084,6 +1127,7 @@
         }
     }
 
+    @MainThread
     private void showKeyboardLayoutNotification(@NonNull String intentTitle,
             @NonNull String intentContent, @Nullable InputDevice targetDevice) {
         final NotificationManager notificationManager = mContext.getSystemService(
@@ -1119,7 +1163,7 @@
                 notification, UserHandle.ALL);
     }
 
-    // Must be called on handler.
+    @MainThread
     private void hideKeyboardLayoutNotification() {
         NotificationManager notificationManager = mContext.getSystemService(
                 NotificationManager.class);
@@ -1132,6 +1176,7 @@
                 UserHandle.ALL);
     }
 
+    @MainThread
     private void showConfiguredKeyboardLayoutNotification() {
         final Resources r = mContext.getResources();
 
@@ -1144,8 +1189,8 @@
         }
 
         final InputDevice inputDevice = getInputDevice(mConfiguredKeyboards.keyAt(0));
-        final Set<String> selectedLayouts = mConfiguredKeyboards.valueAt(0);
-        if (inputDevice == null || selectedLayouts == null || selectedLayouts.isEmpty()) {
+        final KeyboardConfiguration config = mConfiguredKeyboards.valueAt(0);
+        if (inputDevice == null || !config.hasConfiguredLayouts()) {
             return;
         }
 
@@ -1153,10 +1198,11 @@
                 r.getString(
                         R.string.keyboard_layout_notification_selected_title,
                         inputDevice.getName()),
-                createConfiguredNotificationText(mContext, selectedLayouts),
+                createConfiguredNotificationText(mContext, config.getConfiguredLayouts()),
                 inputDevice);
     }
 
+    @MainThread
     private String createConfiguredNotificationText(@NonNull Context context,
             @NonNull Set<String> selectedLayouts) {
         final Resources r = context.getResources();
@@ -1199,6 +1245,9 @@
             case MSG_UPDATE_KEYBOARD_LAYOUTS:
                 updateKeyboardLayouts();
                 return true;
+            case MSG_CURRENT_IME_INFO_CHANGED:
+                onCurrentImeInfoChanged();
+                return true;
             default:
                 return false;
         }
@@ -1252,17 +1301,19 @@
         return imeInfoList;
     }
 
-    private String createLayoutKey(InputDeviceIdentifier identifier, int userId,
-            @NonNull InputMethodSubtypeHandle subtypeHandle) {
-        Objects.requireNonNull(subtypeHandle, "subtypeHandle must not be null");
-        return "layoutDescriptor:" + getLayoutDescriptor(identifier) + ",userId:" + userId
-                + ",subtypeHandle:" + subtypeHandle.toStringHandle();
+    private String createLayoutKey(InputDeviceIdentifier identifier, @Nullable ImeInfo imeInfo) {
+        if (imeInfo == null) {
+            return getLayoutDescriptor(identifier);
+        }
+        Objects.requireNonNull(imeInfo.mImeSubtypeHandle, "subtypeHandle must not be null");
+        return "layoutDescriptor:" + getLayoutDescriptor(identifier) + ",userId:" + imeInfo.mUserId
+                + ",subtypeHandle:" + imeInfo.mImeSubtypeHandle.toStringHandle();
     }
 
     private static boolean isLayoutCompatibleWithLanguageTag(KeyboardLayout layout,
             @NonNull String languageTag) {
         LocaleList layoutLocales = layout.getLocales();
-        if (layoutLocales.isEmpty()) {
+        if (layoutLocales.isEmpty() || TextUtils.isEmpty(languageTag)) {
             // KCM file doesn't have an associated language tag. This can be from
             // a 3rd party app so need to include it as a potential layout.
             return true;
@@ -1350,6 +1401,39 @@
         }
     }
 
+    private static class KeyboardConfiguration {
+        // If null or empty, it means no layout is configured for the device. And user needs to
+        // manually set up the device.
+        @Nullable
+        private Set<String> mConfiguredLayouts;
+
+        // If null, it means no layout is selected for the device.
+        @Nullable
+        private String mCurrentLayout;
+
+        private boolean hasConfiguredLayouts() {
+            return mConfiguredLayouts != null && !mConfiguredLayouts.isEmpty();
+        }
+
+        @Nullable
+        private Set<String> getConfiguredLayouts() {
+            return mConfiguredLayouts;
+        }
+
+        private void setConfiguredLayouts(Set<String> configuredLayouts) {
+            mConfiguredLayouts = configuredLayouts;
+        }
+
+        @Nullable
+        private String getCurrentLayout() {
+            return mCurrentLayout;
+        }
+
+        private void setCurrentLayout(String currentLayout) {
+            mCurrentLayout = currentLayout;
+        }
+    }
+
     private interface KeyboardLayoutVisitor {
         void visitKeyboardLayout(Resources resources,
                 int keyboardLayoutResId, KeyboardLayout layout);
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..247a5c0 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);
@@ -2993,6 +2987,7 @@
                 }
                 break;
             case KeyEvent.KEYCODE_H:
+            case KeyEvent.KEYCODE_ENTER:
                 if (event.isMetaPressed()) {
                     return handleHomeShortcuts(displayId, focusedToken, event);
                 }
@@ -4576,6 +4571,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 +4780,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/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/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 8bbcd27..c40d72c 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -807,11 +807,8 @@
                 if (under != null) {
                     under.returningOptions = safeOptions != null ? safeOptions.getOptions(r) : null;
                 }
-                // Create a transition if the activity is playing in case the current activity
-                // didn't commit invisible. That's because if this activity has changed its
-                // visibility while playing transition, there won't able to commit visibility until
-                // the running transition finish.
-                final Transition transition = r.mTransitionController.inPlayingTransition(r)
+                // Create a transition to make sure the activity change is collected.
+                final Transition transition = r.mTransitionController.isShellTransitionsEnabled()
                         && !r.mTransitionController.isCollecting()
                         ? r.mTransitionController.createTransition(TRANSIT_TO_FRONT) : null;
                 final boolean changed = r.setOccludesParent(false);
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..8bfa426 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();
@@ -2267,6 +2267,12 @@
         if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
             return WmDisplayCutout.NO_CUTOUT;
         }
+        if (displayWidth == displayHeight) {
+            Slog.w(TAG, "Ignore cutout because display size is square: " + displayWidth);
+            // Avoid UnsupportedOperationException because DisplayCutout#computeSafeInsets doesn't
+            // support square size.
+            return WmDisplayCutout.NO_CUTOUT;
+        }
         if (rotation == ROTATION_0) {
             return WmDisplayCutout.computeSafeInsets(
                     cutout, displayWidth, displayHeight);
@@ -3087,13 +3093,9 @@
 
         mIsSizeForced = mInitialDisplayWidth != width || mInitialDisplayHeight != height;
         if (mIsSizeForced) {
-            // Set some sort of reasonable bounds on the size of the display that we will try
-            // to emulate.
-            final int minSize = 200;
-            final int maxScale = 3;
-            final int maxSize = Math.max(mInitialDisplayWidth, mInitialDisplayHeight) * maxScale;
-            width = Math.min(Math.max(width, minSize), maxSize);
-            height = Math.min(Math.max(height, minSize), maxSize);
+            final Point size = getValidForcedSize(width, height);
+            width = size.x;
+            height = size.y;
         }
 
         Slog.i(TAG_WM, "Using new display size: " + width + "x" + height);
@@ -3108,6 +3110,16 @@
         mWmService.mDisplayWindowSettings.setForcedSize(this, width, height);
     }
 
+    /** Returns a reasonable size for setting forced display size. */
+    Point getValidForcedSize(int w, int h) {
+        final int minSize = 200;
+        final int maxScale = 3;
+        final int maxSize = Math.max(mInitialDisplayWidth, mInitialDisplayHeight) * maxScale;
+        w = Math.min(Math.max(w, minSize), maxSize);
+        h = Math.min(Math.max(h, minSize), maxSize);
+        return new Point(w, h);
+    }
+
     DisplayCutout loadDisplayCutout(int displayWidth, int displayHeight) {
         if (mDisplayPolicy == null || mInitialDisplayCutout == null) {
             return null;
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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8822193..40b8274 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5780,10 +5780,12 @@
         if (sizeStr != null && sizeStr.length() > 0) {
             final int pos = sizeStr.indexOf(',');
             if (pos > 0 && sizeStr.lastIndexOf(',') == pos) {
-                int width, height;
                 try {
-                    width = Integer.parseInt(sizeStr.substring(0, pos));
-                    height = Integer.parseInt(sizeStr.substring(pos + 1));
+                    final Point size = displayContent.getValidForcedSize(
+                            Integer.parseInt(sizeStr.substring(0, pos)),
+                            Integer.parseInt(sizeStr.substring(pos + 1)));
+                    final int width = size.x;
+                    final int height = size.y;
                     if (displayContent.mBaseDisplayWidth != width
                             || displayContent.mBaseDisplayHeight != height) {
                         ProtoLog.i(WM_ERROR, "FORCED DISPLAY SIZE: %dx%d", width, height);
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..40024f1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3575,7 +3575,9 @@
     void handleStartUser(int userId) {
         synchronized (getLockObject()) {
             pushScreenCapturePolicy(userId);
-            pushUserControlDisabledPackagesLocked(userId);
+            if (!isPolicyEngineForFinanceFlagEnabled()) {
+                pushUserControlDisabledPackagesLocked(userId);
+            }
         }
         pushUserRestrictions(userId);
         // When system user is started (device boot), load cache for all users.
@@ -5865,8 +5867,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);
         });
     }
 
@@ -6039,7 +6040,7 @@
     @Override
     public void lockNow(int flags, String callerPackageName, boolean parent) {
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(callerPackageName);
         } else {
             caller = getCallerIdentity();
@@ -6051,7 +6052,7 @@
             ActiveAdmin admin;
             // Make sure the caller has any active admin with the right policy or
             // the required permission.
-            if (isPermissionCheckFlagEnabled()) {
+            if (isPolicyEngineForFinanceFlagEnabled()) {
                 admin = enforcePermissionAndGetEnforcingAdmin(
                         /* admin= */ null,
                         /* permission= */ MANAGE_DEVICE_POLICY_LOCK,
@@ -7514,6 +7515,7 @@
         boolean success = false;
         try {
             if (getCurrentForegroundUserId() == userId) {
+                // TODO: We need to special case headless here as we can't switch to the system user
                 mInjector.getIActivityManager().switchUser(UserHandle.USER_SYSTEM);
             }
 
@@ -7521,7 +7523,8 @@
             if (!success) {
                 Slogf.w(LOG_TAG, "Couldn't remove user " + userId);
             } else if (isManagedProfile(userId) && !wipeSilently) {
-                sendWipeProfileNotification(wipeReasonForUser);
+                sendWipeProfileNotification(wipeReasonForUser,
+                        UserHandle.of(getProfileParentId(userId)));
             }
         } catch (RemoteException re) {
             // Shouldn't happen
@@ -7869,7 +7872,7 @@
         });
     }
 
-    private void sendWipeProfileNotification(String wipeReasonForUser) {
+    private void sendWipeProfileNotification(String wipeReasonForUser, UserHandle user) {
         Notification notification =
                 new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
                         .setSmallIcon(android.R.drawable.stat_sys_warning)
@@ -7878,7 +7881,8 @@
                         .setColor(mContext.getColor(R.color.system_notification_accent_color))
                         .setStyle(new Notification.BigTextStyle().bigText(wipeReasonForUser))
                         .build();
-        mInjector.getNotificationManager().notify(SystemMessage.NOTE_PROFILE_WIPED, notification);
+        mInjector.getNotificationManager().notifyAsUser(
+                /* tag= */ null, SystemMessage.NOTE_PROFILE_WIPED, notification, user);
     }
 
     private String getWorkProfileDeletedTitle() {
@@ -8902,13 +8906,13 @@
         }
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             // The effect of this policy is device-wide.
             enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
         } else {
@@ -8936,13 +8940,13 @@
             return false;
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             enforceCanQuery(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
         } else {
             Objects.requireNonNull(who, "ComponentName is null");
@@ -8971,7 +8975,7 @@
             caller = getCallerIdentity(who);
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             // The effect of this policy is device-wide.
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     who,
@@ -9011,13 +9015,13 @@
         }
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             // The effect of this policy is device-wide.
             enforceCanQuery(SET_TIME_ZONE, caller.getPackageName(), UserHandle.USER_ALL);
         } else {
@@ -9199,9 +9203,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) {
@@ -9314,7 +9324,7 @@
         }
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
@@ -9324,7 +9334,7 @@
         final int userHandle = caller.getUserId();
         int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
         synchronized (getLockObject()) {
-            if (isPermissionCheckFlagEnabled()) {
+            if (isPolicyEngineForFinanceFlagEnabled()) {
                 // SUPPORT USES_POLICY_DISABLE_KEYGUARD_FEATURES
                 EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
                         who, MANAGE_DEVICE_POLICY_KEYGUARD, caller.getPackageName(),
@@ -9403,7 +9413,7 @@
 
         synchronized (getLockObject()) {
             if (who != null) {
-                if (isPermissionCheckFlagEnabled()) {
+                if (isPolicyEngineForFinanceFlagEnabled()) {
                     EnforcingAdmin admin = getEnforcingAdminForCaller(
                             who, who.getPackageName());
                     Integer features = mDevicePolicyEngine.getLocalPolicySetByAdmin(
@@ -9417,7 +9427,7 @@
                 }
             }
 
-            if (isPermissionCheckFlagEnabled()) {
+            if (isPolicyEngineForFinanceFlagEnabled()) {
                 Integer features = mDevicePolicyEngine.getResolvedPolicy(
                         PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
                         affectedUserId);
@@ -10057,7 +10067,9 @@
         setNetworkLoggingActiveInternal(false);
         deleteTransferOwnershipBundleLocked(userId);
         toggleBackupServiceActive(UserHandle.USER_SYSTEM, true);
-        pushUserControlDisabledPackagesLocked(userId);
+        if (!isPolicyEngineForFinanceFlagEnabled()) {
+            pushUserControlDisabledPackagesLocked(userId);
+        }
         setGlobalSettingDeviceOwnerType(DEVICE_OWNER_TYPE_DEFAULT);
 
         if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
@@ -11612,7 +11624,7 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS);
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     who,
                     MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
@@ -12988,7 +13000,7 @@
             String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
                     who,
                     MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
@@ -13058,7 +13070,7 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         ActiveAdmin admin;
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     who,
                     MANAGE_DEVICE_POLICY_PACKAGE_STATE,
@@ -13155,7 +13167,7 @@
     public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             enforcePermission(
                     MANAGE_DEVICE_POLICY_PACKAGE_STATE,
                     caller.getPackageName(),
@@ -13371,7 +13383,7 @@
             throw new IllegalStateException("Feature flag is not enabled.");
         }
         if (isDeviceOwner(caller) || isProfileOwner(caller)) {
-            throw new IllegalStateException("Admins are not allowed to call this API.");
+            throw new SecurityException("Admins are not allowed to call this API.");
         }
         if (!mInjector.isChangeEnabled(
                 ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) {
@@ -13761,7 +13773,7 @@
             boolean hidden, boolean parent) {
         CallerIdentity caller = getCallerIdentity(who, callerPackage);
         final int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId();
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             // TODO: We need to ensure the delegate with DELEGATION_PACKAGE_ACCESS can do this
             enforcePermission(MANAGE_DEVICE_POLICY_PACKAGE_STATE, caller.getPackageName(), userId);
         } else {
@@ -13780,7 +13792,7 @@
         boolean result;
         synchronized (getLockObject()) {
             if (parent) {
-                if (!isPermissionCheckFlagEnabled()) {
+                if (!isPolicyEngineForFinanceFlagEnabled()) {
                     Preconditions.checkCallAuthorization(
                             isProfileOwnerOfOrganizationOwnedDevice(
                                     caller.getUserId()) && isManagedProfile(caller.getUserId()));
@@ -13797,7 +13809,7 @@
                 Slogf.v(LOG_TAG, "calling pm.setApplicationHiddenSettingAsUser(%s, %b, %d)",
                         packageName, hidden, userId);
             }
-            if (isPermissionCheckFlagEnabled()) {
+            if (isPolicyEngineForFinanceFlagEnabled()) {
                 EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage);
                 mDevicePolicyEngine.setLocalPolicy(
                         PolicyDefinition.APPLICATION_HIDDEN(packageName),
@@ -13836,7 +13848,7 @@
             String packageName, boolean parent) {
         CallerIdentity caller = getCallerIdentity(who, callerPackage);
         int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId();
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             // TODO: Also support DELEGATION_PACKAGE_ACCESS
             enforcePermission(MANAGE_DEVICE_POLICY_PACKAGE_STATE, caller.getPackageName(), userId);
         } else {
@@ -13848,7 +13860,7 @@
 
         synchronized (getLockObject()) {
             if (parent) {
-                if (!isPermissionCheckFlagEnabled()) {
+                if (!isPolicyEngineForFinanceFlagEnabled()) {
                     Preconditions.checkCallAuthorization(
                             isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId())
                                     && isManagedProfile(caller.getUserId()));
@@ -14037,13 +14049,13 @@
             return;
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
         synchronized (getLockObject()) {
-            if (isPermissionCheckFlagEnabled()) {
+            if (isPolicyEngineForFinanceFlagEnabled()) {
                 int affectedUser = getAffectedUser(parent);
                 EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                         who,
@@ -14106,7 +14118,7 @@
         CallerIdentity caller;
         Preconditions.checkArgumentNonnegative(userId, "Invalid userId");
         final ArraySet<String> resultSet = new ArraySet<>();
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             int affectedUser = parent ? getProfileParentId(userId) : userId;
             caller = getCallerIdentity(callerPackageName);
             if (!hasPermission(MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
@@ -14739,24 +14751,24 @@
             synchronized (getLockObject()) {
                 enforcingAdmin = enforceCanCallLockTaskLocked(who, caller.getPackageName());
             }
-            if (packages.length == 0) {
+            LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicySetByAdmin(
+                    PolicyDefinition.LOCK_TASK,
+                    enforcingAdmin,
+                    caller.getUserId());
+            LockTaskPolicy policy;
+            if (currentPolicy == null) {
+                policy = new LockTaskPolicy(Set.of(packages));
+            } else {
+                policy = new LockTaskPolicy(currentPolicy);
+                policy.setPackages(Set.of(packages));
+            }
+            if (policy.getPackages().isEmpty()
+                    && policy.getFlags() == DevicePolicyManager.LOCK_TASK_FEATURE_NONE) {
                 mDevicePolicyEngine.removeLocalPolicy(
                         PolicyDefinition.LOCK_TASK,
                         enforcingAdmin,
                         caller.getUserId());
             } else {
-                LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicySetByAdmin(
-                        PolicyDefinition.LOCK_TASK,
-                        enforcingAdmin,
-                        caller.getUserId());
-                LockTaskPolicy policy;
-                if (currentPolicy == null) {
-                    policy = new LockTaskPolicy(Set.of(packages));
-                } else {
-                    policy = new LockTaskPolicy(currentPolicy);
-                    policy.setPackages(Set.of(packages));
-                }
-
                 mDevicePolicyEngine.setLocalPolicy(
                         PolicyDefinition.LOCK_TASK,
                         enforcingAdmin,
@@ -14871,18 +14883,26 @@
                     PolicyDefinition.LOCK_TASK,
                     enforcingAdmin,
                     caller.getUserId());
+            LockTaskPolicy policy;
             if (currentPolicy == null) {
-                throw new IllegalArgumentException("Can't set a lock task flags without setting "
-                        + "lock task packages first.");
+                policy = new LockTaskPolicy(flags);
+            } else {
+                policy = new LockTaskPolicy(currentPolicy);
+                policy.setFlags(flags);
             }
-            LockTaskPolicy policy = new LockTaskPolicy(currentPolicy);
-            policy.setFlags(flags);
-
-            mDevicePolicyEngine.setLocalPolicy(
-                    PolicyDefinition.LOCK_TASK,
-                    enforcingAdmin,
-                    policy,
-                    caller.getUserId());
+            if (policy.getPackages().isEmpty()
+                    && policy.getFlags() == DevicePolicyManager.LOCK_TASK_FEATURE_NONE) {
+                mDevicePolicyEngine.removeLocalPolicy(
+                        PolicyDefinition.LOCK_TASK,
+                        enforcingAdmin,
+                        caller.getUserId());
+            } else {
+                mDevicePolicyEngine.setLocalPolicy(
+                        PolicyDefinition.LOCK_TASK,
+                        enforcingAdmin,
+                        policy,
+                        caller.getUserId());
+            }
         } else {
             Objects.requireNonNull(who, "ComponentName is null");
             synchronized (getLockObject()) {
@@ -14940,18 +14960,34 @@
                     continue;
                 }
 
-                final List<String> lockTaskPackages = getUserData(userId).mLockTaskPackages;
-                // TODO(b/278438525): handle in the policy engine
-                if (!lockTaskPackages.isEmpty()) {
-                    Slogf.d(LOG_TAG,
-                            "User id " + userId + " not affiliated. Clearing lock task packages");
-                    setLockTaskPackagesLocked(userId, Collections.<String>emptyList());
-                }
-                final int lockTaskFeatures = getUserData(userId).mLockTaskFeatures;
-                if (lockTaskFeatures != DevicePolicyManager.LOCK_TASK_FEATURE_NONE){
-                    Slogf.d(LOG_TAG,
-                            "User id " + userId + " not affiliated. Clearing lock task features");
-                    setLockTaskFeaturesLocked(userId, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+                if (isPolicyEngineForFinanceFlagEnabled()) {
+                    Map<EnforcingAdmin, PolicyValue<LockTaskPolicy>> policies =
+                            mDevicePolicyEngine.getLocalPoliciesSetByAdmins(
+                                    PolicyDefinition.LOCK_TASK, userId);
+                    Set<EnforcingAdmin> admins = new HashSet<>(policies.keySet());
+                    for (EnforcingAdmin admin : admins) {
+                        if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
+                            mDevicePolicyEngine.removeLocalPolicy(
+                                    PolicyDefinition.LOCK_TASK, admin, userId);
+                        }
+                    }
+                } else {
+                    final List<String> lockTaskPackages = getUserData(userId).mLockTaskPackages;
+                    // TODO(b/278438525): handle in the policy engine
+                    if (!lockTaskPackages.isEmpty()) {
+                        Slogf.d(LOG_TAG,
+                                "User id " + userId
+                                        + " not affiliated. Clearing lock task packages");
+                        setLockTaskPackagesLocked(userId, Collections.<String>emptyList());
+                    }
+                    final int lockTaskFeatures = getUserData(userId).mLockTaskFeatures;
+                    if (lockTaskFeatures != DevicePolicyManager.LOCK_TASK_FEATURE_NONE) {
+                        Slogf.d(LOG_TAG,
+                                "User id " + userId
+                                        + " not affiliated. Clearing lock task features");
+                        setLockTaskFeaturesLocked(userId,
+                                DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+                    }
                 }
             }
         });
@@ -15449,12 +15485,12 @@
     public boolean setStatusBarDisabled(ComponentName who, String callerPackageName,
             boolean disabled) {
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             enforcePermission(MANAGE_DEVICE_POLICY_STATUS_BAR, caller.getPackageName(),
                     UserHandle.USER_ALL);
         } else {
@@ -15465,11 +15501,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 (!isPolicyEngineForFinanceFlagEnabled()) {
+                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 +15560,23 @@
     @Override
     public boolean isStatusBarDisabled(String callerPackage) {
         final CallerIdentity caller = getCallerIdentity(callerPackage);
-        Preconditions.checkCallAuthorization(
-                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
+        if (isPolicyEngineForFinanceFlagEnabled()) {
+            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 (!isPolicyEngineForFinanceFlagEnabled()) {
+                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;
@@ -16177,17 +16222,13 @@
                         deviceOwner.second);
                 return result;
             }
-        } else if (DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)
-                || DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction)) {
+        } else if (DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction)) {
             synchronized (getLockObject()) {
                 final DevicePolicyData policy = getUserData(userId);
                 final int N = policy.mAdminList.size();
                 for (int i = 0; i < N; i++) {
                     final ActiveAdmin admin = policy.mAdminList.get(i);
-                    if ((admin.disableCamera &&
-                            DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction))
-                            || (admin.disableScreenCapture && DevicePolicyManager
-                            .POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction))) {
+                    if (admin.disableScreenCapture) {
                         result = new Bundle();
                         result.putInt(Intent.EXTRA_USER_ID, userId);
                         result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
@@ -16195,17 +16236,44 @@
                         return result;
                     }
                 }
-                // For the camera, a device owner on a different user can disable it globally,
-                // so we need an additional check.
-                if (result == null
-                        && DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) {
-                    final ActiveAdmin admin = getDeviceOwnerAdminLocked();
-                    if (admin != null && admin.disableCamera) {
-                        result = new Bundle();
-                        result.putInt(Intent.EXTRA_USER_ID, mOwners.getDeviceOwnerUserId());
-                        result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
-                                admin.info.getComponent());
-                        return result;
+            }
+        } else if (DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) {
+            if (isPolicyEngineForFinanceFlagEnabled()) {
+                PolicyDefinition<Boolean> policyDefinition =
+                        PolicyDefinition.getPolicyDefinitionForUserRestriction(
+                                UserManager.DISALLOW_CAMERA);
+                Boolean value = mDevicePolicyEngine.getResolvedPolicy(policyDefinition, userId);
+                if (value != null && value) {
+                    result = new Bundle();
+                    result.putInt(Intent.EXTRA_USER_ID, userId);
+                    return result;
+                }
+            } else {
+                synchronized (getLockObject()) {
+                    final DevicePolicyData policy = getUserData(userId);
+                    final int N = policy.mAdminList.size();
+                    for (int i = 0; i < N; i++) {
+                        final ActiveAdmin admin = policy.mAdminList.get(i);
+                        if (admin.disableCamera) {
+                            result = new Bundle();
+                            result.putInt(Intent.EXTRA_USER_ID, userId);
+                            result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+                                    admin.info.getComponent());
+                            return result;
+                        }
+                    }
+                    // For the camera, a device owner on a different user can disable it globally,
+                    // so we need an additional check.
+                    if (result == null
+                            && DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) {
+                        final ActiveAdmin admin = getDeviceOwnerAdminLocked();
+                        if (admin != null && admin.disableCamera) {
+                            result = new Bundle();
+                            result.putInt(Intent.EXTRA_USER_ID, mOwners.getDeviceOwnerUserId());
+                            result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+                                    admin.info.getComponent());
+                            return result;
+                        }
                     }
                 }
             }
@@ -16662,7 +16730,7 @@
             }
         }
         EnforcingAdmin enforcingAdmin;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
@@ -16833,7 +16901,7 @@
     public int getPermissionGrantState(ComponentName admin, String callerPackage,
             String packageName, String permission) throws RemoteException {
         final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             enforceCanQuery(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, caller.getPackageName(),
                     caller.getUserId());
         } else {
@@ -16881,6 +16949,7 @@
                     } else {
                         granted = PackageManager.PERMISSION_GRANTED;
                     }
+
                 }
             }
         }
@@ -18964,14 +19033,14 @@
             throw new IllegalArgumentException("token must be at least 32-byte long");
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(admin, callerPackageName);
         } else {
             caller = getCallerIdentity(admin);
         }
         final int userId = caller.getUserId();
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19027,7 +19096,7 @@
             return false;
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(admin, callerPackageName);
         } else {
             caller = getCallerIdentity(admin);
@@ -19035,7 +19104,7 @@
         final int userId = caller.getUserId();
         boolean result = false;
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19074,14 +19143,14 @@
             return false;
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(admin, callerPackageName);
         } else {
             caller = getCallerIdentity(admin);
         }
         int userId = caller.getUserId();
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19984,6 +20053,7 @@
         if (!mHasFeature) {
             return;
         }
+
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(packageNames, "Package names is null");
         final CallerIdentity caller = getCallerIdentity(who);
@@ -20000,9 +20070,12 @@
             saveSettingsLocked(caller.getUserId());
         }
         logSetCrossProfilePackages(who, packageNames);
-        final CrossProfileApps crossProfileApps = mContext.getSystemService(CrossProfileApps.class);
+        final CrossProfileApps crossProfileApps =
+                mContext.createContextAsUser(
+                        caller.getUserHandle(), /* flags= */ 0)
+                        .getSystemService(CrossProfileApps.class);
         mInjector.binderWithCleanCallingIdentity(
-                () -> crossProfileApps.resetInteractAcrossProfilesAppOps(
+        () -> crossProfileApps.resetInteractAcrossProfilesAppOps(
                         previousCrossProfilePackages, new HashSet<>(packageNames)));
     }
 
@@ -22753,6 +22826,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 +22862,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 +22889,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 +22899,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 +22919,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/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
index 0f6f3c5..20bd2d7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
@@ -42,10 +42,6 @@
     void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
             @NonNull LockTaskPolicy value) throws IOException {
         Objects.requireNonNull(value);
-        if (value.getPackages() == null || value.getPackages().isEmpty()) {
-            throw new IllegalArgumentException("Error saving LockTaskPolicy to file, lock task "
-                    + "packages must be present");
-        }
         serializer.attribute(
                 /* namespace= */ null,
                 ATTR_PACKAGES,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 12a8a75..289098e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -26,6 +26,7 @@
 import android.app.admin.PackagePolicyKey;
 import android.app.admin.PolicyKey;
 import android.app.admin.UserRestrictionPolicyKey;
+import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentFilter;
@@ -38,6 +39,7 @@
 import android.permission.AdminPermissionControlParams;
 import android.permission.PermissionControllerManager;
 import android.provider.Settings;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.server.LocalServices;
@@ -151,11 +153,15 @@
 
     static boolean setUserControlDisabledPackages(
             @Nullable Set<String> packages, int userId) {
-        Binder.withCleanCallingIdentity(() ->
-                LocalServices.getService(PackageManagerInternal.class)
-                        .setOwnerProtectedPackages(
-                                userId,
-                                packages == null ? null : packages.stream().toList()));
+        Binder.withCleanCallingIdentity(() -> {
+            LocalServices.getService(PackageManagerInternal.class)
+                    .setOwnerProtectedPackages(
+                            userId,
+                            packages == null ? null : packages.stream().toList());
+            LocalServices.getService(UsageStatsManagerInternal.class)
+                            .setAdminProtectedPackages(
+                            packages == null ? null : new ArraySet(packages), userId);
+        });
         return true;
     }
 
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..34b88b0 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1511,6 +1511,7 @@
      * Validates that when the device owner is removed, the reset password token is cleared
      */
     @Test
+    @Ignore("b/277916462")
     public void testClearDeviceOwner_clearResetPasswordToken() throws Exception {
         mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
@@ -2601,6 +2602,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetApplicationHiddenWithDO() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -2626,6 +2628,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetApplicationHiddenWithPOOfOrganizationOwnedDevice() throws Exception {
         final int MANAGED_PROFILE_USER_ID = CALLER_USER_HANDLE;
         final int MANAGED_PROFILE_ADMIN_UID =
@@ -3267,31 +3270,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 +3304,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);
     }
 
@@ -4372,6 +4375,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeZoneEnabledModifiesSetting() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -4383,6 +4387,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeZoneEnabledWithPOOnUser0() throws Exception {
         mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         setupProfileOwnerOnUser0();
@@ -4394,6 +4399,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeZoneEnabledFailWithPONotOnUser0() throws Exception {
         setupProfileOwner();
         assertExpectException(SecurityException.class, null,
@@ -4403,6 +4409,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeZoneEnabledWithPOOfOrganizationOwnedDevice() throws Exception {
         setupProfileOwner();
         configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
@@ -5376,6 +5383,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testResetPasswordWithToken() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -5410,6 +5418,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void resetPasswordWithToken_NumericPin() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -5430,6 +5439,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void resetPasswordWithToken_EmptyPassword() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -7250,6 +7260,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testCanProfileOwnerResetPasswordWhenLocked() throws Exception {
         setDeviceEncryptionPerUser();
         setupProfileOwner();
@@ -7313,6 +7324,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAccountTypesWithManagementDisabledOnManagedProfile() throws Exception {
         setupProfileOwner();
 
@@ -7332,6 +7344,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAccountTypesWithManagementDisabledOnOrgOwnedManagedProfile()
             throws Exception {
         mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS);
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/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index ff89be7..5ea3029 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -17,6 +17,8 @@
 package com.android.server.display;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
@@ -26,6 +28,7 @@
 
 import android.app.PropertyInvalidatedCache;
 import android.graphics.Point;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.Surface;
@@ -47,6 +50,7 @@
     private static final int LAYER_STACK = 0;
     private static final int DISPLAY_WIDTH = 100;
     private static final int DISPLAY_HEIGHT = 200;
+    private static final int MODE_ID = 1;
 
     private LogicalDisplay mLogicalDisplay;
     private DisplayDevice mDisplayDevice;
@@ -65,6 +69,9 @@
         mDisplayDeviceInfo.height = DISPLAY_HEIGHT;
         mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
         mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
+        mDisplayDeviceInfo.modeId = MODE_ID;
+        mDisplayDeviceInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, /* refreshRate= */ 60)};
         when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(mDisplayDeviceInfo);
 
         // Disable binder caches in this process.
@@ -168,14 +175,34 @@
     }
 
     @Test
-    public void testLayoutLimitedRefreshRateNotClearedAfterUpdate() {
-        SurfaceControl.RefreshRateRange refreshRateRange = new SurfaceControl.RefreshRateRange(1,
-                2);
-        mLogicalDisplay.updateLayoutLimitedRefreshRateLocked(refreshRateRange);
-        mLogicalDisplay.updateDisplayGroupIdLocked(1);
+    public void testUpdateLayoutLimitedRefreshRate() {
+        SurfaceControl.RefreshRateRange layoutLimitedRefreshRate =
+                new SurfaceControl.RefreshRateRange(0, 120);
+        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
+        mLogicalDisplay.updateLayoutLimitedRefreshRateLocked(layoutLimitedRefreshRate);
+        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
+        // Display info should only be updated when updateLocked is called
+        assertEquals(info2, info1);
 
-        DisplayInfo result = mLogicalDisplay.getDisplayInfoLocked();
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
+        assertNotEquals(info3, info2);
+        assertEquals(layoutLimitedRefreshRate, info3.layoutLimitedRefreshRate);
+    }
 
-        assertEquals(refreshRateRange, result.layoutLimitedRefreshRate);
+    @Test
+    public void testUpdateRefreshRateThermalThrottling() {
+        SparseArray<SurfaceControl.RefreshRateRange> refreshRanges = new SparseArray<>();
+        refreshRanges.put(0, new SurfaceControl.RefreshRateRange(0, 120));
+        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
+        mLogicalDisplay.updateThermalRefreshRateThrottling(refreshRanges);
+        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
+        // Display info should only be updated when updateLocked is called
+        assertEquals(info2, info1);
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
+        assertNotEquals(info3, info2);
+        assertTrue(refreshRanges.contentEquals(info3.thermalRefreshRateThrottling));
     }
 }
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..e492252 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
@@ -27,7 +27,7 @@
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
 
-import static com.android.server.display.mode.DisplayModeDirector.Vote.INVALID_SIZE;
+import static com.android.server.display.mode.Vote.INVALID_SIZE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -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;
@@ -93,7 +94,6 @@
 import com.android.server.display.TestUtils;
 import com.android.server.display.mode.DisplayModeDirector.BrightnessObserver;
 import com.android.server.display.mode.DisplayModeDirector.DesiredDisplayModeSpecs;
-import com.android.server.display.mode.DisplayModeDirector.Vote;
 import com.android.server.sensors.SensorManagerInternal;
 import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
 import com.android.server.statusbar.StatusBarManagerInternal;
@@ -126,7 +126,8 @@
     private static final String TAG = "DisplayModeDirectorTest";
     private static final boolean DEBUG = false;
     private static final float FLOAT_TOLERANCE = 0.01f;
-    private static final int DISPLAY_ID = 0;
+    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+    private static final int MODE_ID = 1;
     private static final float TRANSITION_POINT = 0.763f;
 
     private static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
@@ -158,6 +159,9 @@
         LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
+        clearSmoothDisplaySetting();
+        clearForcePeakRefreshRateSetting();
     }
 
     private DisplayModeDirector createDirectorFromRefreshRateArray(
@@ -219,8 +223,7 @@
         assertThat(modeSpecs.appRequest.render.min).isEqualTo(0f);
         assertThat(modeSpecs.appRequest.render.max).isEqualTo(Float.POSITIVE_INFINITY);
 
-        int numPriorities =
-                DisplayModeDirector.Vote.MAX_PRIORITY - DisplayModeDirector.Vote.MIN_PRIORITY + 1;
+        int numPriorities =  Vote.MAX_PRIORITY - Vote.MIN_PRIORITY + 1;
 
         // Ensure vote priority works as expected. As we add new votes with higher priority, they
         // should take precedence over lower priority votes.
@@ -919,7 +922,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 +929,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 +980,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 +987,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 +1035,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 +2527,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);
 
@@ -2533,6 +2643,53 @@
         assertNull(vote);
     }
 
+    @Test
+    public void testUpdateLayoutLimitedRefreshRate_validDisplayInfo() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<DisplayListener> displayListenerCaptor =
+                ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+                any(Handler.class));
+        DisplayListener displayListener = displayListenerCaptor.getValue();
+
+        float refreshRate = 60;
+        mInjector.mDisplayInfo.layoutLimitedRefreshRate =
+                new RefreshRateRange(refreshRate, refreshRate);
+        displayListener.onDisplayChanged(DISPLAY_ID);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
+        assertVoteForPhysicalRefreshRate(vote, /* refreshRate= */ refreshRate);
+
+        mInjector.mDisplayInfo.layoutLimitedRefreshRate = null;
+        displayListener.onDisplayChanged(DISPLAY_ID);
+
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
+        assertNull(vote);
+    }
+
+    @Test
+    public void testUpdateLayoutLimitedRefreshRate_invalidDisplayInfo() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<DisplayListener> displayListenerCaptor =
+                ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+                any(Handler.class));
+        DisplayListener displayListener = displayListenerCaptor.getValue();
+
+        mInjector.mDisplayInfo.layoutLimitedRefreshRate = new RefreshRateRange(10, 10);
+        mInjector.mDisplayInfoValid = false;
+        displayListener.onDisplayChanged(DISPLAY_ID);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
+        assertNull(vote);
+    }
+
     private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
         return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
     }
@@ -2671,10 +2828,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();
     }
 
@@ -2719,11 +2896,20 @@
 
     public static class FakesInjector implements DisplayModeDirector.Injector {
         private final FakeDeviceConfig mDeviceConfig;
+        private final DisplayInfo mDisplayInfo;
+        private final Display mDisplay;
+        private boolean mDisplayInfoValid = true;
         private ContentObserver mBrightnessObserver;
-        private ContentObserver mPeakRefreshRateObserver;
+        private ContentObserver mSmoothDisplaySettingObserver;
+        private ContentObserver mForcePeakRefreshRateSettingObserver;
 
         FakesInjector() {
             mDeviceConfig = new FakeDeviceConfig();
+            mDisplayInfo = new DisplayInfo();
+            mDisplayInfo.defaultModeId = MODE_ID;
+            mDisplayInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
+                    800, 600, /* refreshRate= */ 60)};
+            mDisplay = createDisplay(DISPLAY_ID);
         }
 
         @NonNull
@@ -2732,22 +2918,37 @@
         }
 
         @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
+        public void registerDisplayListener(DisplayListener listener, Handler handler) {}
+
+        @Override
         public void registerDisplayListener(DisplayListener listener, Handler handler, long flag) {}
 
         @Override
+        public Display getDisplay(int displayId) {
+            return mDisplay;
+        }
+
+        @Override
         public Display[] getDisplays() {
-            return new Display[] { createDisplay(DISPLAY_ID) };
+            return new Display[] { mDisplay };
         }
 
         @Override
         public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) {
-            return false;
+            displayInfo.copyFrom(mDisplayInfo);
+            return mDisplayInfoValid;
         }
 
         @Override
@@ -2771,14 +2972,21 @@
         }
 
         protected Display createDisplay(int id) {
-            return new Display(DisplayManagerGlobal.getInstance(), id, new DisplayInfo(),
+            return new Display(DisplayManagerGlobal.getInstance(), id, mDisplayInfo,
                     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/display/mode/SkinThermalStatusObserverTest.java b/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
index fd1889c..9ab6ee5 100644
--- a/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
@@ -18,7 +18,6 @@
 
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
 import android.hardware.display.DisplayManager;
@@ -57,7 +56,7 @@
     private RegisteringFakesInjector mInjector = new RegisteringFakesInjector();
 
     private final TestHandler mHandler = new TestHandler(null);
-    private final FakeVoteStorage mStorage = new FakeVoteStorage();
+    private final VotesStorage mStorage = new VotesStorage(() -> {});
 
     @Before
     public void setUp() {
@@ -92,28 +91,26 @@
     public void testNotifyWithDefaultVotesForCritical() {
         // GIVEN 2 displays with no thermalThrottling config
         mObserver.observe();
-        assertEquals(0, mStorage.mVoteRegistry.size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID).size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
 
         // WHEN thermal sensor notifies CRITICAL
         mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_CRITICAL));
         mHandler.flush();
 
         // THEN 2 votes are added to storage with (0,60) render refresh rate(default behaviour)
-        assertEquals(2, mStorage.mVoteRegistry.size());
-
-        SparseArray<DisplayModeDirector.Vote> displayVotes = mStorage.mVoteRegistry.get(DISPLAY_ID);
+        SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID);
         assertEquals(1, displayVotes.size());
 
-        DisplayModeDirector.Vote vote = displayVotes.get(
-                DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE);
+        Vote vote = displayVotes.get(
+                Vote.PRIORITY_SKIN_TEMPERATURE);
         assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE);
         assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE);
 
-        SparseArray<DisplayModeDirector.Vote> otherDisplayVotes = mStorage.mVoteRegistry.get(
-                DISPLAY_ID_OTHER);
+        SparseArray<Vote> otherDisplayVotes = mStorage.getVotes(DISPLAY_ID_OTHER);
         assertEquals(1, otherDisplayVotes.size());
 
-        vote = otherDisplayVotes.get(DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE);
+        vote = otherDisplayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE);
         assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE);
         assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE);
     }
@@ -122,25 +119,29 @@
     public void testNotifyWithDefaultVotesChangeFromCriticalToSevere() {
         // GIVEN 2 displays with no thermalThrottling config AND temperature level CRITICAL
         mObserver.observe();
-        assertEquals(0, mStorage.mVoteRegistry.size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID).size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
         mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_CRITICAL));
         // WHEN thermal sensor notifies SEVERE
         mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_SEVERE));
         mHandler.flush();
         // THEN all votes with PRIORITY_SKIN_TEMPERATURE are removed from the storage
-        assertEquals(0, mStorage.mVoteRegistry.size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID).size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
     }
 
     @Test
     public void testNotifyWithDefaultVotesForSevere() {
         // GIVEN 2 displays with no thermalThrottling config
         mObserver.observe();
-        assertEquals(0, mStorage.mVoteRegistry.size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID).size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
         // WHEN thermal sensor notifies CRITICAL
         mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_SEVERE));
         mHandler.flush();
         // THEN nothing is added to the storage
-        assertEquals(0, mStorage.mVoteRegistry.size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID).size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
     }
 
     @Test
@@ -155,18 +156,20 @@
         mObserver = new SkinThermalStatusObserver(mInjector, mStorage, mHandler);
         mObserver.observe();
         mObserver.onDisplayChanged(DISPLAY_ID);
-        assertEquals(0, mStorage.mVoteRegistry.size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID).size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
         // WHEN thermal sensor notifies temperature above configured
         mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_SEVERE));
         mHandler.flush();
         // THEN vote with refreshRate from config is added to the storage
-        assertEquals(1, mStorage.mVoteRegistry.size());
-        SparseArray<DisplayModeDirector.Vote> displayVotes = mStorage.mVoteRegistry.get(DISPLAY_ID);
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
+
+        SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID);
         assertEquals(1, displayVotes.size());
-        DisplayModeDirector.Vote vote = displayVotes.get(
-                DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE);
+        Vote vote = displayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE);
         assertEquals(90, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE);
         assertEquals(120, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE);
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
     }
 
     @Test
@@ -178,14 +181,13 @@
         mObserver.onDisplayAdded(DISPLAY_ID_ADDED);
         mHandler.flush();
         // THEN 3rd vote is added to storage with (0,60) render refresh rate(default behaviour)
-        assertEquals(3, mStorage.mVoteRegistry.size());
+        assertEquals(1, mStorage.getVotes(DISPLAY_ID).size());
+        assertEquals(1, mStorage.getVotes(DISPLAY_ID_OTHER).size());
+        assertEquals(1, mStorage.getVotes(DISPLAY_ID_ADDED).size());
 
-        SparseArray<DisplayModeDirector.Vote> displayVotes = mStorage.mVoteRegistry.get(
-                DISPLAY_ID_ADDED);
-        assertEquals(1, displayVotes.size());
+        SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID_ADDED);
 
-        DisplayModeDirector.Vote vote = displayVotes.get(
-                DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE);
+        Vote vote = displayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE);
         assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE);
         assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE);
     }
@@ -200,9 +202,9 @@
         mObserver.onDisplayRemoved(DISPLAY_ID_ADDED);
         mHandler.flush();
         // THEN there are 2 votes in registry
-        assertEquals(2, mStorage.mVoteRegistry.size());
-        assertNotNull(mStorage.mVoteRegistry.get(DISPLAY_ID));
-        assertNotNull(mStorage.mVoteRegistry.get(DISPLAY_ID_OTHER));
+        assertEquals(1, mStorage.getVotes(DISPLAY_ID).size());
+        assertEquals(1, mStorage.getVotes(DISPLAY_ID_OTHER).size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_ADDED).size());
     }
 
     private static Temperature createTemperature(@Temperature.ThrottlingStatus int status) {
@@ -253,33 +255,10 @@
         public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) {
             SparseArray<SurfaceControl.RefreshRateRange> config = mOverriddenConfig.get(displayId);
             if (config != null) {
-                displayInfo.refreshRateThermalThrottling = config;
+                displayInfo.thermalRefreshRateThrottling = config;
                 return true;
             }
             return false;
         }
     }
-
-
-    private static class FakeVoteStorage implements DisplayModeDirector.BallotBox {
-        private final SparseArray<SparseArray<DisplayModeDirector.Vote>> mVoteRegistry =
-                new SparseArray<>();
-
-        @Override
-        public void vote(int displayId, int priority, DisplayModeDirector.Vote vote) {
-            SparseArray<DisplayModeDirector.Vote> votesPerDisplay = mVoteRegistry.get(displayId);
-            if (votesPerDisplay == null) {
-                votesPerDisplay = new SparseArray<>();
-                mVoteRegistry.put(displayId, votesPerDisplay);
-            }
-            if (vote == null) {
-                votesPerDisplay.remove(priority);
-            } else {
-                votesPerDisplay.put(priority, vote);
-            }
-            if (votesPerDisplay.size() == 0) {
-                mVoteRegistry.remove(displayId);
-            }
-        }
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java
new file mode 100644
index 0000000..287fdd5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.mode;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.util.SparseArray;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VotesStorageTest {
+    private static final int DISPLAY_ID = 100;
+    private static final int PRIORITY = Vote.PRIORITY_APP_REQUEST_SIZE;
+    private static final Vote VOTE = Vote.forDisableRefreshRateSwitching();
+    private static final int DISPLAY_ID_OTHER = 101;
+    private static final int PRIORITY_OTHER = Vote.PRIORITY_FLICKER_REFRESH_RATE;
+    private static final Vote VOTE_OTHER = Vote.forBaseModeRefreshRate(10f);
+
+    @Mock
+    public VotesStorage.Listener mVotesListener;
+
+    private VotesStorage mVotesStorage;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mVotesStorage = new VotesStorage(mVotesListener);
+    }
+
+    @Test
+    public void addsVoteToStorage() {
+        // WHEN updateVote is called
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+        // THEN vote is added to the storage
+        SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID);
+        assertThat(votes.size()).isEqualTo(1);
+        assertThat(votes.get(PRIORITY)).isEqualTo(VOTE);
+        assertThat(mVotesStorage.getVotes(DISPLAY_ID_OTHER).size()).isEqualTo(0);
+    }
+
+    @Test
+    public void notifiesVoteListenerIfVoteAdded() {
+        // WHEN updateVote is called
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+        // THEN listener is notified
+        verify(mVotesListener).onChanged();
+    }
+
+    @Test
+    public void addsAnotherVoteToStorageWithDifferentPriority() {
+        // GIVEN vote storage with one vote
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+        // WHEN updateVote is called with other priority
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE_OTHER);
+        // THEN another vote is added to storage
+        SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID);
+        assertThat(votes.size()).isEqualTo(2);
+        assertThat(votes.get(PRIORITY)).isEqualTo(VOTE);
+        assertThat(votes.get(PRIORITY_OTHER)).isEqualTo(VOTE_OTHER);
+        assertThat(mVotesStorage.getVotes(DISPLAY_ID_OTHER).size()).isEqualTo(0);
+    }
+
+    @Test
+    public void replacesVoteInStorageForSamePriority() {
+        // GIVEN vote storage with one vote
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+        // WHEN updateVote is called with same priority
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE_OTHER);
+        // THEN vote is replaced by other vote
+        SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID);
+        assertThat(votes.size()).isEqualTo(1);
+        assertThat(votes.get(PRIORITY)).isEqualTo(VOTE_OTHER);
+        assertThat(mVotesStorage.getVotes(DISPLAY_ID_OTHER).size()).isEqualTo(0);
+    }
+
+    @Test
+    public void removesVoteInStorageForSamePriority() {
+        // GIVEN vote storage with one vote
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+        // WHEN update is called with same priority and null vote
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, null);
+        // THEN vote removed from the storage
+        assertThat(mVotesStorage.getVotes(DISPLAY_ID).size()).isEqualTo(0);
+        assertThat(mVotesStorage.getVotes(DISPLAY_ID_OTHER).size()).isEqualTo(0);
+    }
+
+    @Test
+    public void addsGlobalDisplayVoteToStorage() {
+        // WHEN updateGlobalVote is called
+        mVotesStorage.updateGlobalVote(PRIORITY, VOTE);
+        // THEN it is added to the storage for every display
+        SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID);
+        assertThat(votes.size()).isEqualTo(1);
+        assertThat(votes.get(PRIORITY)).isEqualTo(VOTE);
+        votes = mVotesStorage.getVotes(DISPLAY_ID_OTHER);
+        assertThat(votes.size()).isEqualTo(1);
+        assertThat(votes.get(PRIORITY)).isEqualTo(VOTE);
+    }
+
+    @Test
+    public void ignoresVotesWithLowerThanMinPriority() {
+        // WHEN updateVote is called with invalid (lower than min) priority
+        mVotesStorage.updateVote(DISPLAY_ID, Vote.MIN_PRIORITY - 1, VOTE);
+        // THEN vote is not added to the storage AND listener not notified
+        assertThat(mVotesStorage.getVotes(DISPLAY_ID).size()).isEqualTo(0);
+        verify(mVotesListener, never()).onChanged();
+    }
+
+    @Test
+    public void ignoresVotesWithGreaterThanMaxPriority() {
+        // WHEN updateVote is called with invalid (greater than max) priority
+        mVotesStorage.updateVote(DISPLAY_ID, Vote.MAX_PRIORITY + 1, VOTE);
+        // THEN vote is not added to the storage AND listener not notified
+        assertThat(mVotesStorage.getVotes(DISPLAY_ID).size()).isEqualTo(0);
+        verify(mVotesListener, never()).onChanged();
+    }
+}
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/input/KeyboardBacklightControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
index 0f4d4e8..64c05dc 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -250,20 +250,24 @@
         `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
         `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
 
-        dataStore.setKeyboardBacklightBrightness(
-            keyboardWithBacklight.descriptor,
-            LIGHT_ID,
-            MAX_BRIGHTNESS
-        )
+        for (level in 1 until BRIGHTNESS_VALUE_FOR_LEVEL.size) {
+            dataStore.setKeyboardBacklightBrightness(
+                    keyboardWithBacklight.descriptor,
+                    LIGHT_ID,
+                    BRIGHTNESS_VALUE_FOR_LEVEL[level] - 1
+            )
 
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-        keyboardBacklightController.notifyUserActivity()
-        testLooper.dispatchNext()
-        assertEquals(
-            "Keyboard backlight level should be restored to the level saved in the data store",
-            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
-            lightColorMap[LIGHT_ID]
-        )
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            keyboardBacklightController.notifyUserActivity()
+            testLooper.dispatchNext()
+            assertEquals(
+                    "Keyboard backlight level should be restored to the level saved in the data " +
+                            "store",
+                    Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
+                    lightColorMap[LIGHT_ID]
+            )
+            keyboardBacklightController.onInputDeviceRemoved(DEVICE_ID)
+        }
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index ea3f3bc..d0d28c3 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -633,6 +633,30 @@
                 0,
                 keyboardLayouts.size
             )
+
+            // If IME doesn't have a corresponding language tag, then should show all available
+            // layouts no matter the script code.
+            keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo, null
+                )
+            assertNotEquals(
+                "New UI: getKeyboardLayoutListForInputDevice API should return all layouts if" +
+                    "language tag or subtype not provided",
+                0,
+                keyboardLayouts.size
+            )
+            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Latin " +
+                "layouts if language tag or subtype not provided",
+                containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Cyrillic " +
+                "layouts if language tag or subtype not provided",
+                containsLayout(
+                    keyboardLayouts,
+                    createLayoutDescriptor("keyboard_layout_russian")
+                )
+            )
         }
     }
 
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/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 8f0a5e6..bf6901e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -21,6 +21,8 @@
 import static android.view.KeyEvent.KEYCODE_C;
 import static android.view.KeyEvent.KEYCODE_CTRL_LEFT;
 import static android.view.KeyEvent.KEYCODE_E;
+import static android.view.KeyEvent.KEYCODE_ENTER;
+import static android.view.KeyEvent.KEYCODE_H;
 import static android.view.KeyEvent.KEYCODE_K;
 import static android.view.KeyEvent.KEYCODE_M;
 import static android.view.KeyEvent.KEYCODE_META_LEFT;
@@ -164,4 +166,24 @@
         sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_ALT_LEFT}, 0);
         mPhoneWindowManager.assertToggleCapsLock();
     }
+
+    /**
+     * META + H to go to homescreen
+     */
+    @Test
+    public void testMetaH() {
+        mPhoneWindowManager.overrideLaunchHome();
+        sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_H}, 0);
+        mPhoneWindowManager.assertGoToHomescreen();
+    }
+
+    /**
+     * META + ENTER to go to homescreen
+     */
+    @Test
+    public void testMetaEnter() {
+        mPhoneWindowManager.overrideLaunchHome();
+        sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_ENTER}, 0);
+        mPhoneWindowManager.assertGoToHomescreen();
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 6368f47..676bfb0 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -117,9 +117,9 @@
             throw new RuntimeException(e);
         }
 
-        for (KeyEvent event: events) {
+        for (int i = count - 1; i >= 0; i--) {
             final long eventTime = SystemClock.uptimeMillis();
-            final int keyCode = event.getKeyCode();
+            final int keyCode = keyCodes[i];
             final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode,
                     0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/,
                     InputDevice.SOURCE_KEYBOARD);
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index a2ee8a4..2665e19 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -353,6 +353,10 @@
                 () -> LocalServices.getService(eq(StatusBarManagerInternal.class)));
     }
 
+    void overrideLaunchHome() {
+        doNothing().when(mPhoneWindowManager).launchHomeFromHotKey(anyInt());
+    }
+
     /**
      * Below functions will check the policy behavior could be invoked.
      */
@@ -480,4 +484,9 @@
         transitionCaptor.getValue().onAppTransitionFinishedLocked(any());
         verify(mPhoneWindowManager).lockNow(null);
     }
+
+    void assertGoToHomescreen() {
+        waitForIdle();
+        verify(mPhoneWindowManager).launchHomeFromHotKey(anyInt());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index fb4f2ee..1cec0ef 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -41,8 +41,9 @@
 
 import android.annotation.NonNull;
 import android.app.WindowConfiguration;
-import android.content.res.Configuration;
+import android.content.ContentResolver;
 import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.Surface;
@@ -439,6 +440,7 @@
     public void testDisplayWindowSettingsAppliedOnDisplayReady() {
         // Set forced densities for two displays in DisplayWindowSettings
         final DisplayContent dc = createMockSimulatedDisplay();
+        final ContentResolver contentResolver = useFakeSettingsProvider();
         mDisplayWindowSettings.setForcedDensity(mPrimaryDisplay.getDisplayInfo(), 123,
                 0 /* userId */);
         mDisplayWindowSettings.setForcedDensity(dc.getDisplayInfo(), 456, 0 /* userId */);
@@ -450,15 +452,21 @@
         assertFalse(mPrimaryDisplay.mWaitingForConfig);
         assertFalse(dc.mWaitingForConfig);
 
+        final int invalidW = Integer.MAX_VALUE;
+        final int invalidH = Integer.MAX_VALUE;
+        // Verify that applyForcedPropertiesForDefaultDisplay() handles invalid size request.
+        Settings.Global.putString(contentResolver, Settings.Global.DISPLAY_SIZE_FORCED,
+                invalidW + "," + invalidH);
         // Notify WM that the displays are ready and check that they are reconfigured.
         mWm.displayReady();
         waitUntilHandlersIdle();
 
-        final Configuration config = new Configuration();
-        mPrimaryDisplay.computeScreenConfiguration(config);
-        assertEquals(123, config.densityDpi);
-        dc.computeScreenConfiguration(config);
-        assertEquals(456, config.densityDpi);
+        // Density is set successfully.
+        assertEquals(123, mPrimaryDisplay.getConfiguration().densityDpi);
+        assertEquals(456, dc.getConfiguration().densityDpi);
+        // Invalid size won't be applied.
+        assertNotEquals(invalidW, mPrimaryDisplay.mBaseDisplayWidth);
+        assertNotEquals(invalidH, mPrimaryDisplay.mBaseDisplayHeight);
     }
 
     @Test
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/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index f85cdf0..07244a4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -66,6 +66,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -81,6 +82,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.SparseArray;
 import android.view.Display;
@@ -109,6 +111,7 @@
 
 import com.android.internal.policy.AttributeCache;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
 
 import org.junit.After;
@@ -146,6 +149,7 @@
     WindowManagerService mWm;
     private final IWindow mIWindow = new TestIWindow();
     private Session mMockSession;
+    private boolean mUseFakeSettingsProvider;
 
     DisplayInfo mDisplayInfo = new DisplayInfo();
     DisplayContent mDefaultDisplay;
@@ -272,16 +276,9 @@
 
     @After
     public void tearDown() throws Exception {
-        // Revert back to device overrides.
-        mAtm.mWindowManager.mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
-        mAtm.mWindowManager.mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
-        mAtm.mWindowManager.mLetterboxConfiguration.resetLetterboxVerticalPositionMultiplier();
-        mAtm.mWindowManager.mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled();
-        mAtm.mWindowManager.mLetterboxConfiguration.resetIsVerticalReachabilityEnabled();
-        mAtm.mWindowManager.mLetterboxConfiguration
-                .resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
-        mAtm.mWindowManager.mLetterboxConfiguration
-                .resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
+        if (mUseFakeSettingsProvider) {
+            FakeSettingsProvider.clearSettingsProvider();
+        }
     }
 
     /**
@@ -428,6 +425,17 @@
         // Called before display is created.
     }
 
+    /** Avoid writing values to real Settings. */
+    ContentResolver useFakeSettingsProvider() {
+        mUseFakeSettingsProvider = true;
+        FakeSettingsProvider.clearSettingsProvider();
+        final FakeSettingsProvider provider = new FakeSettingsProvider();
+        // SystemServicesTestRule#setUpSystemCore has called spyOn for the ContentResolver.
+        final ContentResolver resolver = mContext.getContentResolver();
+        doReturn(provider.getIContentProvider()).when(resolver).acquireProvider(Settings.AUTHORITY);
+        return resolver;
+    }
+
     private WindowState createCommonWindow(WindowState parent, int type, String name) {
         final WindowState win = createWindow(parent, type, name);
         // Prevent common windows from been IME targets.
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 07dc1c6..efe3009 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);
         }
     }
@@ -1072,15 +1116,6 @@
         }
     }
 
-    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        synchronized (mLock) {
-            pw.print("  module properties=");
-            pw.print("  call active=");
-            pw.println(mCallActive);
-            pw.println("  SoundTrigger Power State=" + mSoundTriggerPowerSaveMode);
-        }
-    }
-
     private void initializeDeviceStateListeners() {
         if (mRecognitionRequested) {
             return;
@@ -1115,12 +1150,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 +1326,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..b6673ad 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -1663,13 +1663,13 @@
             }
 
             @Override
-            public int unloadKeyphraseModel(int keyphraseId) {
-                return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
+            public void detach() {
+                mSoundTriggerHelper.detach();
             }
 
             @Override
-            public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-                mSoundTriggerHelper.dump(fd, pw, args);
+            public int unloadKeyphraseModel(int keyphraseId) {
+                return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
             }
 
             private void clientDied() {
@@ -1692,18 +1692,6 @@
                 return listUnderlyingModuleProperties(originatorIdentity);
             }
         }
-
-        @Override
-        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            // log
-            sEventLogger.dump(pw);
-
-            // enrolled models
-            mDbHelper.dump(pw);
-
-            // stats
-            mSoundModelStatTracker.dump(pw);
-        }
     }
 
     //=================================================================
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/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 1d7b966..27f3fb3 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 {
@@ -2133,8 +2138,6 @@
                     mImpl.dumpLocked(fd, pw, args);
                 }
             }
-
-            mSoundTriggerInternal.dump(fd, pw, args);
         }
 
         @Override
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 */